Skip to content

Conversation

@1hyok
Copy link

@1hyok 1hyok commented Nov 7, 2025

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 이미지 업로드 기능 추가로 게시물 작성 시 사진 첨부 가능
    • 스낵바 알림을 통한 작업 완료/실패 피드백 제공
    • 백엔드 API 연동으로 실제 게시물 관리(조회, 생성, 수정, 삭제) 기능 구현
  • 개선사항

    • 프로젝트 구성 및 빌드 설정 최적화

rbqks529 and others added 8 commits October 31, 2025 11:33
[feat]: 5주차 실습 & 미션 구현
게시글 생성, 수정, 삭제 시 사용자에게 피드백을 제공하기 위해 Snackbar를 추가했습니다.

*   **MainActivity**: `Scaffold`와 `SnackbarHost`를 설정하여 앱 전체에서 Snackbar를 사용할 수 있도록 UI 구조를 변경했습니다.
*   **NavGraph**: 각 화면(Screen)으로 `SnackbarHostState`를 전달하도록 수정했습니다.
*   **PostCreateScreen, PostEditScreen, PostDetailScreen**: 게시글 생성, 수정, 삭제 작업이 성공적으로 완료되면 Coroutine Scope 내에서 Snackbar를 호출하여 사용자에게 "게시글이 작성되었습니다.", "게시글이 수정되었습니다.", "게시글이 삭제되었습니다."와 같은 메시지를 표시하도록 구현했습니다.
@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

안드로이드 앱의 로컬 데이터 기반 구조에서 Retrofit 기반 API 통합 구조로 전환합니다. 새로운 API 레이어, 응답 모델, 이미지 업로드 기능을 추가하고 ViewModel과 UI 화면을 API 호출로 재구성합니다.

Changes

Cohort / File(s) Summary
IDE 및 편집기 설정
\.idea/deploymentTargetSelector.xml, \.idea/gradle.xml, \.idea/markdown.xml, \.vscode/settings.json
IDE 프로젝트 메타데이터 업데이트: Gradle 모듈 경로를 중첩 경로에서 프로젝트 루트로 변경, Markdown 미리보기 패널 설정 추가, VSCode Java 자동 빌드 설정 활성화
빌드 구성
app/build.gradle.kts, gradle/libs.versions.toml
Retrofit, OkHttp, Kotlin 직렬화 플러그인 의존성 추가; BuildConfig에서 BASE_URL 노출; Compose 컴파일러 플러그인 활성화
API 계층
app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt, app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
새로운 Retrofit 서비스 인터페이스(게시글 CRUD, 이미지 업로드) 및 OkHttp/Kotlinx 직렬화 설정을 갖춘 Retrofit 클라이언트 추가
데이터 모델 - 응답
app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt, app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt, app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt, app/src/main/java/com/example/kuit6_android_api/data/model/response/ImageUploadResponse.kt
API 응답 래퍼 및 직렬화 가능한 모델(PostResponse, AuthorResponse, ImageUploadResponse) 추가
데이터 모델 - 요청
app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt
게시글 생성/수정 요청 모델 추가
레거시 모델 제거
app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
기존 Post, Author 데이터 클래스 제거
UI 화면
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt, app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt, app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostDetailScreen.kt
SnackbarHostState 매개변수 추가; 이미지 선택/업로드 흐름, 업로드 진행 상태, 완료 후 스낵바 알림 구현
UI 컴포넌트
app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt
Post에서 PostResponse로 매개변수 타입 변경
내비게이션 및 메인
app/src/main/java/com/example/kuit6_android_api/ui/navigation/NavGraph.kt, app/src/main/java/com/example/kuit6_android_api/MainActivity.kt
NavGraph에 SnackbarHostState 전달; MainActivity에서 Scaffold 및 SnackbarHost 통합, 권한 요청 로직 개선
ViewModel 및 유틸리티
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt, app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/UriUtils.kt
ViewModel을 API 호출 기반으로 재구성(더미 데이터 제거); 이미지 업로드 메서드 및 업로드 상태 추가; URI를 파일로 변환하는 유틸리티 함수 추가
문서
CLAUDE.md
Claude Code 가이드 문서 삭제

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("게시글 작성 완료")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

세부 검토 사항:

  • API 레이어 설계: ApiService 인터페이스의 엔드포인트 정의, RetrofitClient의 설정(타임아웃, 로깅, 직렬화) 검증
  • ViewModel 리팩토링: 더미 데이터 제거 후 API 호출 통합, 상태 관리(isUploading, uploadedImageUrl) 일관성 확인
  • 이미지 업로드 흐름: URI 변환, 멀티파트 요청, 성공/실패 콜백 처리의 오류 처리
  • UI 화면 통합: 세 개의 화면(PostCreateScreen, PostEditScreen, PostDetailScreen)에 걸친 SnackbarHostState 전파 및 스낵바 표시 로직
  • 데이터 모델 마이그레이션: 기존 Post/Author 제거, 새로운 Response 모델 도입으로 인한 호환성 검증
  • 권한 요청 로직: MainActivity의 shouldShowRequestPermissionRationale 분기 추가

Poem

🐰 Retrofit의 새 길을 따라
더미 데이터는 안녕히 👋
API와 함께 춤을 추고
이미지도 업로드하며 🎨
스낵바가 축하해요! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive 제목이 한국어로 작성되었으며 '6주차 미션 / 안드로이드 1조 정일혁'으로 특정 과제와 개발자를 지칭하지만, 변경사항의 실제 내용(API 통합, 데이터 모델 리팩토링 등)을 설명하지 않습니다. API 클라이언트 설정 및 데이터 모델 리팩토링과 같은 기술적 변경사항을 명확하게 반영하는 더 구체적인 제목으로 변경하시기 바랍니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@1hyok 1hyok changed the title Jeong ilhyuk/week6 6주차 미션 / 안드로이드 1조 정일혁 Nov 7, 2025
@alswlekk
Copy link
Contributor

alswlekk commented Nov 7, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)

  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

@alswlekk: PR 리뷰를 시작하겠습니다! 코드 변경사항을 검토하고 피드백을 제공해드리겠습니다.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a 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에 생성되지만 명시적인 정리 로직이 없습니다. 반복적인 이미지 업로드 시 캐시가 누적될 수 있습니다.

다음 중 하나의 방법을 고려하세요:

  1. 업로드 성공 후 파일 삭제
  2. 앱 시작 시 또는 주기적으로 오래된 캐시 파일 정리
  3. 파일명에 고유 식별자를 사용하여 중복 방지

예시:

// 업로드 성공 후 정리
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

📥 Commits

Reviewing files that changed from the base of the PR and between bd77f9d and 7b4f7b3.

⛔ Files ignored due to path filters (1)
  • .kotlin/errors/errors-1762482068299.log is 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.xml
  • appInsightsSettings.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 응답 모델로의 전환이 올바르게 적용되었습니다. PostResponseAuthorResponse 타입 사용이 일관성 있게 이루어졌습니다.

app/src/main/java/com/example/kuit6_android_api/MainActivity.kt (1)

88-96: LGTM!

권한 거부 이력이 있을 때 사용자에게 설명을 제공하는 로직이 잘 추가되었습니다.

Comment on lines +23 to +29
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\"")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +13 to +22
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()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

로그 레벨을 운영 빌드에서 낮춰주세요.

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.

Suggested change
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.

Comment on lines +1 to +11
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

파일명과 클래스명의 불일치를 수정하세요.

파일명은 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.

Comment on lines +49 to 66
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(
MaterialTheme.colorScheme.background
),
snackbarHost = {
SnackbarHost(hostState = snackBarState)
}
) {
val navController = rememberNavController()

NavGraph(
navController = navController,
startDestination = PostListRoute
startDestination = PostListRoute,
snackBarState = snackBarState
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scaffold의 content padding을 적용하세요.

Scaffold가 제공하는 paddingValuesNavGraph에 전달하지 않으면 콘텐츠가 Snackbar 아래에 가려질 수 있습니다.

다음과 같이 수정하세요:

-                ) {
+                ) { paddingValues ->
                     val navController = rememberNavController()
 
                     NavGraph(
                         navController = navController,
                         startDestination = PostListRoute,
-                        snackBarState = snackBarState
+                        snackBarState = snackBarState,
+                        modifier = Modifier.padding(paddingValues)
                     )
                 }

참고: NavGraphModifier 파라미터를 받도록 시그니처를 수정해야 할 수 있습니다.

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.

Comment on lines 270 to 276
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("게시글이 작성되었습니다.") }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

스낵바가 표시되지 않는 타이밍 문제

rememberCoroutineScope()가 이 컴포저블의 생명주기에 묶여 있어서 onPostCreated() 호출로 화면이 pop되는 순간 scope가 cancel됩니다. 그 상태에서 showSnackbar를 호출하면 스낵바가 거의 항상 표시되기 전에 취소되므로 성공 알림이 사용자에게 전달되지 않습니다. 네비게이션 전에 스낵바를 처리하거나, 상위에서 유지되는 scope/이벤트를 통해 스낵바를 띄우도록 로직을 재구성해 주세요.

Comment on lines 66 to 68
LaunchedEffect(postId) {
viewModel.getPostDetail(postId)
// viewModel.getPostDetail(postId)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

상세 조회 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.

Comment on lines +199 to 204
onClick = {
selectedImageUri = null
viewModel.clearUploadedImageUrl()
},
modifier = Modifier
.align(Alignment.TopEnd)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

이미지 제거 동작이 실제로 반영되지 않습니다.

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.

Comment on lines +145 to +156
}.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 ?: "업로드 실패")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

업로드 실패 시 사용자에게 알림이 없습니다

업로드 응답이 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.

Suggested change
}.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.

@rbqks529 rbqks529 merged commit 045ed9c into Konkuk-KUIT:FutureIsGoodSir Nov 11, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants