-
Notifications
You must be signed in to change notification settings - Fork 10
6주차 미션 / 안드로이드 1조 정일혁 #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
6주차 미션 / 안드로이드 1조 정일혁 #17
Conversation
5주차 실습 및 미션 구현
[feat]: 5주차 실습 & 미션 구현
게시글 생성, 수정, 삭제 시 사용자에게 피드백을 제공하기 위해 Snackbar를 추가했습니다. * **MainActivity**: `Scaffold`와 `SnackbarHost`를 설정하여 앱 전체에서 Snackbar를 사용할 수 있도록 UI 구조를 변경했습니다. * **NavGraph**: 각 화면(Screen)으로 `SnackbarHostState`를 전달하도록 수정했습니다. * **PostCreateScreen, PostEditScreen, PostDetailScreen**: 게시글 생성, 수정, 삭제 작업이 성공적으로 완료되면 Coroutine Scope 내에서 Snackbar를 호출하여 사용자에게 "게시글이 작성되었습니다.", "게시글이 수정되었습니다.", "게시글이 삭제되었습니다."와 같은 메시지를 표시하도록 구현했습니다.
Walkthrough안드로이드 앱의 로컬 데이터 기반 구조에서 Retrofit 기반 API 통합 구조로 전환합니다. 새로운 API 레이어, 응답 모델, 이미지 업로드 기능을 추가하고 ViewModel과 UI 화면을 API 호출로 재구성합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PostCreateScreen
participant PostViewModel
participant UriUtils
participant RetrofitClient
participant Backend
User->>PostCreateScreen: 이미지 선택
PostCreateScreen->>UriUtils: uriToFile(context, uri)
UriUtils-->>PostCreateScreen: File
PostCreateScreen->>PostViewModel: uploadImage(context, uri)
PostViewModel->>RetrofitClient: apiService.uploadImage(file)
RetrofitClient->>Backend: POST /api/images/upload (multipart)
Backend-->>RetrofitClient: BaseResponse<Map<String, String>>
RetrofitClient-->>PostViewModel: 성공
PostViewModel->>PostViewModel: uploadedImageUrl 업데이트<br/>isUploading = false
PostViewModel-->>PostCreateScreen: onSuccess 콜백
User->>PostCreateScreen: 게시글 작성 완료
PostCreateScreen->>PostViewModel: createPost(author, title, content, imageUrl)
PostViewModel->>RetrofitClient: apiService.createPost(author, PostCreateRequest)
RetrofitClient->>Backend: POST /api/posts (JSON)
Backend-->>RetrofitClient: BaseResponse<PostResponse>
RetrofitClient-->>PostViewModel: 성공
PostViewModel->>PostViewModel: uploadedImageUrl 초기화
PostViewModel-->>PostCreateScreen: onSuccess 콜백
PostCreateScreen->>PostCreateScreen: snackBarState.showSnackbar("게시글 작성 완료")
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 세부 검토 사항:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@coderabbitai review |
|
@alswlekk: PR 리뷰를 시작하겠습니다! 코드 변경사항을 검토하고 피드백을 제공해드리겠습니다. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.idea/deploymentTargetSelector.xml (1)
1-17: IDE 설정 파일은 버전 관리에서 제외해야 합니다.
.idea/deploymentTargetSelector.xml파일은 Android Studio/IntelliJ IDEA에서 자동으로 생성되는 IDE 설정 파일입니다. 이 파일의 변경 사항은 단순히 디버깅 시 타임스탬프 업데이트일 뿐이며, 이는 PR의 실제 변경 사항과 무관합니다.
.idea/디렉토리 아래의 파일들은 일반적으로 버전 관리에서 제외되어야 하므로,.gitignore에 추가하고 이 파일을 PR에서 제거해 주시기 바랍니다.
🧹 Nitpick comments (3)
.vscode/settings.json (1)
1-3: IDE 설정 파일 커밋 정책 확인
.vscode/폴더의 설정 파일을 버전 관리에 포함하는 방식입니다. 설정값 자체(automatic)는 개발 환경에서 합리적이나, IDE 워크스페이스 설정은 일반적으로 다음과 같이 관리합니다:
- 권장:
.vscode/폴더를.gitignore에 추가하여 개발자의 로컬 IDE 설정 유지- 또는: 팀 전체가 공유해야 할 필수 설정인 경우만 커밋
현재 접근 방식이 팀의 정책에 부합하는지 확인해 주세요.
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/UriUtils.kt (2)
10-26: 캐시 파일 정리 메커니즘을 고려하세요.임시 파일이
context.cacheDir에 생성되지만 명시적인 정리 로직이 없습니다. 반복적인 이미지 업로드 시 캐시가 누적될 수 있습니다.다음 중 하나의 방법을 고려하세요:
- 업로드 성공 후 파일 삭제
- 앱 시작 시 또는 주기적으로 오래된 캐시 파일 정리
- 파일명에 고유 식별자를 사용하여 중복 방지
예시:
// 업로드 성공 후 정리 fun uriToFile(context: Context, uri: Uri): File? { return try { val contentResolver = context.contentResolver val fileName = getFileName(context, uri) ?: "image_${System.currentTimeMillis()}.jpg" val tempFile = File(context.cacheDir, fileName) contentResolver.openInputStream(uri)?.use { input -> FileOutputStream(tempFile).use { output -> input.copyTo(output) } } // 참고: 호출자가 사용 후 tempFile.delete()를 호출해야 함 tempFile } catch (e: Exception) { e.printStackTrace() null } }
22-24: 예외 처리를 개선하세요.
printStackTrace()만 사용하면 프로덕션 환경에서 에러 추적이 어렵습니다. 로깅 프레임워크(예: Logcat, Timber) 사용을 권장합니다.} catch (e: Exception) { - e.printStackTrace() + android.util.Log.e("UriUtils", "Failed to convert URI to file", e) null }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
.kotlin/errors/errors-1762482068299.logis excluded by!**/*.log
📒 Files selected for processing (23)
.idea/deploymentTargetSelector.xml(1 hunks).idea/gradle.xml(1 hunks).idea/markdown.xml(1 hunks).vscode/settings.json(1 hunks)CLAUDE.md(0 hunks)app/build.gradle.kts(4 hunks)app/src/main/java/com/example/kuit6_android_api/MainActivity.kt(4 hunks)app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt(0 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/response/ImageUploadResponse.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt(1 hunks)app/src/main/java/com/example/kuit6_android_api/ui/navigation/NavGraph.kt(5 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt(2 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt(5 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostDetailScreen.kt(4 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt(7 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt(2 hunks)app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/UriUtils.kt(1 hunks)gradle/libs.versions.toml(1 hunks)
💤 Files with no reviewable changes (2)
- app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
- CLAUDE.md
🔇 Additional comments (8)
.idea/gradle.xml (1)
12-13: 프로젝트 모듈 경로가 정상적으로 업데이트되었습니다.Gradle 모듈 설정이 프로젝트 루트(
$PROJECT_DIR$)와 앱 모듈($PROJECT_DIR$/app)을 올바르게 참조하도록 변경되었습니다. 이는 API 통합 구조로의 전환을 지원하는 필요한 구조 조정입니다..idea/markdown.xml (1)
1-8: 원본 리뷰 코멘트는 잘못되었습니다.검증 결과, 이 프로젝트는
.idea/디렉토리의 특정 설정 파일들을 의도적으로 버전 관리에 포함하고 있습니다. 현재 다음과 같은 19개의.idea/파일이 추적되고 있습니다:
compiler.xml,gradle.xml,misc.xml,vcs.xml,encodings.xmlappInsightsSettings.xml,AndroidProjectSystem.xml,markdown.xml(검토 대상 파일)- 기타 공유 구성 파일들
.gitignore는 머신 특화 파일들(workspace.xml,caches/,libraries/)만 선택적으로 제외하고, 팀 전체가 공유해야 할 IDE 구성은 버전 관리에 포함하도록 설정되어 있습니다. 이는 팀 전체의 IDE 설정 일관성을 위한 합리적인 설계 선택입니다.
.idea/markdown.xml은 다른 공유 구성 파일들과 동일한 패턴을 따르며 적절히 포함되어 있습니다.Likely an incorrect or invalid review comment.
gradle/libs.versions.toml (1)
62-62: LGTM!Kotlinx Serialization 컨버터가 올바르게 추가되었습니다. 기존 Retrofit 버전과 일관성 있게 설정되어 있습니다.
app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt (1)
6-12: LGTM!표준 API 응답 래퍼 구조가 잘 설계되었습니다. 제네릭 타입과 nullable 필드를 적절히 사용하고 있습니다.
app/src/main/java/com/example/kuit6_android_api/data/model/response/ImageUploadResponse.kt (1)
6-9: LGTM!이미지 업로드 응답 모델이 간결하고 명확하게 정의되었습니다.
app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt (1)
6-11: LGTM!포스트 생성 요청 모델이 명확하게 정의되었습니다. nullable
imageUrl로 선택적 이미지를 적절히 처리하고 있습니다.app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt (1)
32-38: LGTM!API 응답 모델로의 전환이 올바르게 적용되었습니다.
PostResponse와AuthorResponse타입 사용이 일관성 있게 이루어졌습니다.app/src/main/java/com/example/kuit6_android_api/MainActivity.kt (1)
88-96: LGTM!권한 거부 이력이 있을 때 사용자에게 설명을 제공하는 로직이 잘 추가되었습니다.
| val properties = Properties() | ||
| val localPropertiesFile = rootProject.file("local.properties") | ||
| if (localPropertiesFile.exists()) { | ||
| properties.load(localPropertiesFile.inputStream()) | ||
| } | ||
| val baseUrl = properties.getProperty("BASE_URL") | ||
| buildConfigField("String", "BASE_URL", "\"$baseUrl\"") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BASE_URL 누락 시 런타임 크래시가 발생합니다.
Line [23]~[29]에서 local.properties에 BASE_URL이 없으면 buildConfigField에 "null"이 들어가고, Retrofit.Builder().baseUrl("null") 호출 시 즉시 예외가 터집니다. 빌드 단계에서 명시적으로 검증하거나 기본값을 강제해 주세요.
- if (localPropertiesFile.exists()) {
- properties.load(localPropertiesFile.inputStream())
- }
- val baseUrl = properties.getProperty("BASE_URL")
- buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
+ if (localPropertiesFile.exists()) {
+ localPropertiesFile.inputStream().use(properties::load)
+ }
+ val baseUrl = properties.getProperty("BASE_URL")
+ ?: error("BASE_URL must be defined in local.properties")
+ buildConfigField("String", "BASE_URL", "\"$baseUrl\"")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val properties = Properties() | |
| val localPropertiesFile = rootProject.file("local.properties") | |
| if (localPropertiesFile.exists()) { | |
| properties.load(localPropertiesFile.inputStream()) | |
| } | |
| val baseUrl = properties.getProperty("BASE_URL") | |
| buildConfigField("String", "BASE_URL", "\"$baseUrl\"") | |
| val properties = Properties() | |
| val localPropertiesFile = rootProject.file("local.properties") | |
| if (localPropertiesFile.exists()) { | |
| localPropertiesFile.inputStream().use(properties::load) | |
| } | |
| val baseUrl = properties.getProperty("BASE_URL") | |
| ?: error("BASE_URL must be defined in local.properties") | |
| buildConfigField("String", "BASE_URL", "\"$baseUrl\"") |
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 23-29, the script reads BASE_URL from
local.properties but when BASE_URL is missing it writes "null" into
buildConfigField causing a runtime crash; update the block to validate the
property at build time and either provide a safe default (e.g., an empty string
or a well-known dev URL) or fail the build with a clear error. Specifically,
after loading properties check properties.getProperty("BASE_URL") for
null/blank; if null/blank call error(...) or throw GradleException with a
message instructing to set BASE_URL, otherwise use the validated value when
calling buildConfigField so the generated BuildConfig always contains a valid
non-null string.
| private val loggingInterceptor = HttpLoggingInterceptor().apply { | ||
| level = HttpLoggingInterceptor.Level.BODY | ||
| } | ||
|
|
||
| private val okHttpClient = OkHttpClient.Builder() | ||
| .addInterceptor(loggingInterceptor) | ||
| .connectTimeout(30, TimeUnit.SECONDS) | ||
| .readTimeout(30, TimeUnit.SECONDS) | ||
| .writeTimeout(30, TimeUnit.SECONDS) | ||
| .build() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그 레벨을 운영 빌드에서 낮춰주세요.
Line [13]~[22]에서 HttpLoggingInterceptor를 항상 BODY로 두면 운영 빌드에서 민감한 요청/응답 본문이 로그에 남고, 큰 payload에서는 성능까지 악화됩니다. 디버그 빌드에서만 BODY를 사용하고 운영에서는 NONE으로 낮춰주세요.
- private val loggingInterceptor = HttpLoggingInterceptor().apply {
- level = HttpLoggingInterceptor.Level.BODY
- }
+ private val loggingInterceptor = HttpLoggingInterceptor().apply {
+ level = if (BuildConfig.DEBUG) {
+ HttpLoggingInterceptor.Level.BODY
+ } else {
+ HttpLoggingInterceptor.Level.NONE
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private val loggingInterceptor = HttpLoggingInterceptor().apply { | |
| level = HttpLoggingInterceptor.Level.BODY | |
| } | |
| private val okHttpClient = OkHttpClient.Builder() | |
| .addInterceptor(loggingInterceptor) | |
| .connectTimeout(30, TimeUnit.SECONDS) | |
| .readTimeout(30, TimeUnit.SECONDS) | |
| .writeTimeout(30, TimeUnit.SECONDS) | |
| .build() | |
| private val loggingInterceptor = HttpLoggingInterceptor().apply { | |
| level = if (BuildConfig.DEBUG) { | |
| HttpLoggingInterceptor.Level.BODY | |
| } else { | |
| HttpLoggingInterceptor.Level.NONE | |
| } | |
| } | |
| private val okHttpClient = OkHttpClient.Builder() | |
| .addInterceptor(loggingInterceptor) | |
| .connectTimeout(30, TimeUnit.SECONDS) | |
| .readTimeout(30, TimeUnit.SECONDS) | |
| .writeTimeout(30, TimeUnit.SECONDS) | |
| .build() |
🤖 Prompt for AI Agents
In app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
around lines 13 to 22, the HttpLoggingInterceptor is currently always set to
Level.BODY which leaks sensitive data and harms performance in release builds;
change it to set the level conditionally based on the build type (e.g., if
(BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else
HttpLoggingInterceptor.Level.NONE) so debug builds keep BODY logging while
production builds use NONE, and ensure the interceptor is created/configured
before adding it to the OkHttpClient builder.
| package com.example.kuit6_android_api.data.model.response | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class AuthorResponse( | ||
| @SerialName("id") val id: Long, | ||
| @SerialName("username") val username: String, | ||
| @SerialName("profileImageUrl") val profileImageUrl: String? | ||
| ) No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파일명과 클래스명의 불일치를 수정하세요.
파일명은 AuthResponse.kt이지만 클래스명은 AuthorResponse입니다. Kotlin 컨벤션에 따라 파일명과 클래스명을 일치시켜야 합니다.
파일명을 AuthorResponse.kt로 변경하세요.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/kuit6_android_api/data/model/response around
lines 1-11, the file is named AuthResponse.kt but the declared data class is
AuthorResponse; rename the file to AuthorResponse.kt to match the class name and
Kotlin conventions, and update any imports/usages across the project if
necessary to reference the new filename/class name.
| Scaffold( | ||
| modifier = Modifier | ||
| .fillMaxSize() | ||
| .background( | ||
| MaterialTheme.colorScheme.background | ||
| ), | ||
| snackbarHost = { | ||
| SnackbarHost(hostState = snackBarState) | ||
| } | ||
| ) { | ||
| val navController = rememberNavController() | ||
|
|
||
| NavGraph( | ||
| navController = navController, | ||
| startDestination = PostListRoute | ||
| startDestination = PostListRoute, | ||
| snackBarState = snackBarState | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scaffold의 content padding을 적용하세요.
Scaffold가 제공하는 paddingValues를 NavGraph에 전달하지 않으면 콘텐츠가 Snackbar 아래에 가려질 수 있습니다.
다음과 같이 수정하세요:
- ) {
+ ) { paddingValues ->
val navController = rememberNavController()
NavGraph(
navController = navController,
startDestination = PostListRoute,
- snackBarState = snackBarState
+ snackBarState = snackBarState,
+ modifier = Modifier.padding(paddingValues)
)
}참고: NavGraph가 Modifier 파라미터를 받도록 시그니처를 수정해야 할 수 있습니다.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/kuit6_android_api/MainActivity.kt around lines
49 to 66, the Scaffold's content paddingValues are not forwarded to NavGraph so
UI (e.g., Snackbar) can overlap content; change the Scaffold content lambda to
capture the paddingValues parameter and pass them into NavGraph (either via a
Modifier.padding(paddingValues) or by adding a paddingValues/Modifier parameter
to NavGraph's signature), and update NavGraph definition/call sites accordingly
so the passed padding is applied to the root composable.
| Button( | ||
| onClick = { | ||
| val finalAuthor = author.ifBlank { "anonymous" } | ||
| viewModel.createPost(finalAuthor, title, content, null) { | ||
| val finalAuthor = author | ||
| viewModel.createPost(finalAuthor, title, content, viewModel.uploadedImageUrl) { | ||
| onPostCreated() | ||
| scope.launch { snackBarState.showSnackbar("게시글이 작성되었습니다.") } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스낵바가 표시되지 않는 타이밍 문제
rememberCoroutineScope()가 이 컴포저블의 생명주기에 묶여 있어서 onPostCreated() 호출로 화면이 pop되는 순간 scope가 cancel됩니다. 그 상태에서 showSnackbar를 호출하면 스낵바가 거의 항상 표시되기 전에 취소되므로 성공 알림이 사용자에게 전달되지 않습니다. 네비게이션 전에 스낵바를 처리하거나, 상위에서 유지되는 scope/이벤트를 통해 스낵바를 띄우도록 로직을 재구성해 주세요.
| LaunchedEffect(postId) { | ||
| viewModel.getPostDetail(postId) | ||
| // viewModel.getPostDetail(postId) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상세 조회 API 호출이 제거되어 있습니다
LaunchedEffect(postId) 블록에서 viewModel.getPostDetail(postId)가 주석 처리되어 있어 진입 시 상세 데이터를 가져오지 못합니다. 결과적으로 화면이 비어 있거나 이전 값만 남게 되므로 반드시 호출이 복구되어야 합니다.
-// viewModel.getPostDetail(postId)
+ viewModel.getPostDetail(postId)🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostDetailScreen.kt
around lines 66 to 68, the call to viewModel.getPostDetail(postId) inside
LaunchedEffect is commented out; restore the call by uncommenting it so the
ViewModel fetches post details on entry, ensure postId is non-null/valid before
invoking (or guard with let/if), and keep any existing error handling/loading
state logic so the UI updates correctly after the fetch.
| onClick = { | ||
| selectedImageUri = null | ||
| viewModel.clearUploadedImageUrl() | ||
| }, | ||
| modifier = Modifier | ||
| .align(Alignment.TopEnd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미지 제거 동작이 실제로 반영되지 않습니다.
Line [200] 부근에서 사용자가 이미지를 제거해도 Line [233]~[241]에서 selectedImageUri == null이면 기존 post.imageUrl을 다시 전송해 버려 서버에 “삭제” 요청이 전달되지 않습니다. 실제 삭제를 의도했다면 API에는 null을 보내도록 별도 상태를 관리해야 합니다.
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
+ var isImageRemoved by remember { mutableStateOf(false) }
@@
uri?.let {
selectedImageUri = it
+ isImageRemoved = false
@@
onClick = {
selectedImageUri = null
+ isImageRemoved = true
viewModel.clearUploadedImageUrl()
},
@@
- val imageUrl = if (selectedImageUri != null) {
- viewModel.uploadedImageUrl
- } else {
- post?.imageUrl
- }
+ val imageUrl = when {
+ isImageRemoved -> null
+ selectedImageUri != null -> viewModel.uploadedImageUrl
+ else -> it.imageUrl
+ }Also applies to: 233-241
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt
around lines 199-204 and 233-241, the remove-image button only clears
selectedImageUri but the update logic later falls back to sending post.imageUrl,
so the server never receives a deletion. Add a distinct "imageDeleted" state (in
the screen or ViewModel), set it true when the user clicks removeImage (and
still call clearUploadedImageUrl and selectedImageUri = null), set it false when
a new image is selected, and update the submit logic to send: if
selectedImageUri != null use the uploaded image URL; else if imageDeleted send
null to the API; otherwise keep the existing post.imageUrl. Ensure the
ViewModel/submit function accepts/propagates this null to the API so deletion is
performed.
| }.onSuccess { response -> | ||
| isUploading = false | ||
| if (response.success && response.data != null) { | ||
| val imageUrl = response.data["imageUrl"] | ||
| if (imageUrl != null) { | ||
| uploadedImageUrl = imageUrl | ||
| onSuccess(imageUrl) | ||
| } | ||
| } | ||
| }.onFailure { error -> | ||
| isUploading = false | ||
| onError(error.message ?: "업로드 실패") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
업로드 실패 시 사용자에게 알림이 없습니다
업로드 응답이 success = false이거나 imageUrl이 비어 있는 경우에도 onError가 호출되지 않아 사용자가 실패를 인지하지 못합니다. 실패 분기에서 콜백을 호출해 UI가 적절히 대응하도록 해 주세요.
- }.onSuccess { response ->
- isUploading = false
- if (response.success && response.data != null) {
- val imageUrl = response.data["imageUrl"]
- if (imageUrl != null) {
- uploadedImageUrl = imageUrl
- onSuccess(imageUrl)
- }
- }
- }.onFailure { error ->
- isUploading = false
- onError(error.message ?: "업로드 실패")
- }
+ }.onSuccess { response ->
+ isUploading = false
+ if (response.success && response.data != null) {
+ val imageUrl = response.data["imageUrl"]
+ if (imageUrl != null) {
+ uploadedImageUrl = imageUrl
+ onSuccess(imageUrl)
+ return@onSuccess
+ }
+ }
+ onError("업로드 응답이 올바르지 않습니다.")
+ }.onFailure { error ->
+ isUploading = false
+ onError(error.message ?: "업로드 실패")
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| }.onSuccess { response -> | |
| isUploading = false | |
| if (response.success && response.data != null) { | |
| val imageUrl = response.data["imageUrl"] | |
| if (imageUrl != null) { | |
| uploadedImageUrl = imageUrl | |
| onSuccess(imageUrl) | |
| } | |
| } | |
| }.onFailure { error -> | |
| isUploading = false | |
| onError(error.message ?: "업로드 실패") | |
| }.onSuccess { response -> | |
| isUploading = false | |
| if (response.success && response.data != null) { | |
| val imageUrl = response.data["imageUrl"] | |
| if (imageUrl != null) { | |
| uploadedImageUrl = imageUrl | |
| onSuccess(imageUrl) | |
| return@onSuccess | |
| } | |
| } | |
| onError("업로드 응답이 올바르지 않습니다.") | |
| }.onFailure { error -> | |
| isUploading = false | |
| onError(error.message ?: "업로드 실패") | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
around lines 145 to 156, the success branch silently ignores cases where
response.success is false or response.data["imageUrl"] is null so the UI never
learns of the failure; update the success handler to detect these failure
conditions, set isUploading = false, and call onError(...) with a clear message
(use response.message or a default like "업로드 실패" when message/imageUrl is
missing) so the UI can react; ensure the existing onFailure branch behavior
remains unchanged.
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항