Skip to content

Conversation

@birdy736
Copy link

@birdy736 birdy736 commented Nov 6, 2025

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 게시글이 서버와 연동되어 실시간으로 동기화됩니다
    • 게시글 작성, 수정, 삭제 완료 시 확인 알림이 표시됩니다
    • 이미지 업로드 기능이 추가되었습니다
  • 사용성 개선

    • 사용자 인터페이스가 개선되어 더 나은 피드백을 제공합니다

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

이 PR은 더미 데이터 기반의 인메모리 로직을 Retrofit 기반 API 통신으로 전환합니다. BuildConfig에서 BASE_URL을 설정하고, ApiService 인터페이스와 RetrofitClient를 통해 API 클라이언트를 구성하며, ViewModel이 API 응답 모델을 사용하도록 리팩토링합니다. UI는 Scaffold와 SnackbarHostState를 통해 사용자 피드백을 추가합니다.

Changes

Cohort / File(s) 변경 요약
빌드 설정
app/build.gradle.kts
BuildConfig 활성화, local.properties에서 BASE_URL 읽기, Retrofit/OkHttp/Gson 라이브러리 추가
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
ApiService 인터페이스 정의 (GET/POST/PUT/DELETE /api/posts*, 이미지 업로드), RetrofitClient 싱글톤으로 Retrofit 인스턴스 및 OkHttp 설정
응답 데이터 모델
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
BaseResponse 제네릭 래퍼, PostResponse 및 AuthorResponse 응답 모델 추가
요청 데이터 모델
app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt
PostCreateRequest 데이터 클래스 추가
레거시 모델 제거
app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
Post 및 Author 데이터 클래스 삭제 (PostResponse로 대체)
UI 구조
app/src/main/java/com/example/kuit6_android_api/MainActivity.kt
Surface에서 Scaffold로 변경, SnackbarHostState 추가, 배경 수정자 적용
네비게이션
app/src/main/java/com/example/kuit6_android_api/ui/navigation/NavGraph.kt
NavGraph에 snackBarState: SnackbarHostState 파라미터 추가, 하위 화면에 전달
화면 컴포넌트
app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt, 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/PostDetailScreen.kt, app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt
Post에서 PostResponse로 모델 변경, SnackbarHostState 통합하여 작업 완료 시 피드백 표시
ViewModel
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
더미 데이터/딜레이 제거, ApiService 호출로 대체 (getPosts, getPostDetail, createPost, updatePost, deletePost), 데이터 타입을 PostResponse로 변경

Sequence Diagram(s)

sequenceDiagram
    participant UI as PostListScreen
    participant VM as PostViewModel
    participant API as ApiService
    participant Network as Retrofit/Network

    UI->>VM: getPosts()
    activate VM
    VM->>API: apiService.getPosts()
    activate API
    API->>Network: GET /api/posts
    activate Network
    Network-->>API: BaseResponse<List<PostResponse>>
    deactivate Network
    deactivate API
    VM->>VM: posts = response.data ?: emptyList()
    deactivate VM
    VM-->>UI: Update posts StateFlow
    UI->>UI: Recompose with new posts
Loading
sequenceDiagram
    participant UI as PostCreateScreen
    participant VM as PostViewModel
    participant API as ApiService
    participant Snackbar as SnackbarHostState

    UI->>UI: User submits form
    UI->>VM: createPost(author, title, content, imageUrl)
    activate VM
    VM->>API: apiService.createPost(PostCreateRequest)
    activate API
    API-->>VM: BaseResponse<PostResponse>
    deactivate API
    VM->>VM: clearUploadedImageUrl()
    deactivate VM
    VM-->>UI: onSuccess callback
    UI->>Snackbar: showSnackbar("게시글이 생성되었습니다.")
    UI->>UI: Navigate back
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

검토 시 주의 깊게 봐야 할 영역:

  • PostViewModel.kt: 더미 데이터 로직에서 API 기반 로직으로의 전환; runCatching 에러 처리 패턴, response.data null 체크, 콜백 호출 시점 확인 필요
  • 데이터 모델 전환: Post → PostResponse로의 전환이 모든 화면에서 일관되게 이루어졌는지 확인 (PostItem, PostCreateScreen, PostDetailScreen, PostEditScreen)
  • RetrofitClient.kt: BASE_URL이 BuildConfig에서 올바르게 로드되는지, OkHttp 타임아웃 및 인터셉터 설정이 적절한지 확인
  • SnackbarHostState 플로우: MainActivity에서 NavGraph로의 전달, 각 화면에서의 사용 일관성 검증
  • API 엔드포인트: ApiService의 6개 엔드포인트 시그니처가 백엔드 스펙과 일치하는지, 멀티파트 요청 구조 확인

Poem

🐰 더미 데이터 안녕, 실제 API 반갑습니다!
네트워크 호출로 춤을 춥니다, Retrofit과 함께
스낵바로 사용자 마음 따뜻하게
데이터 흐름이 새로워졌어요!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목 '6주차 미션'은 매우 모호하며 변경사항의 실제 내용을 전혀 설명하지 않습니다. 제목을 API 클라이언트 통합, 포스트 CRUD 기능 구현 등 실제 변경사항을 반영하는 구체적인 내용으로 수정하시기 바랍니다.
✅ 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.

@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: I'll review the changes in this PR now.

✅ 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)
app/src/main/java/com/example/kuit6_android_api/MainActivity.kt (1)

41-68: Scaffold 패딩 미적용

@SuppressLint로 경고를 억제하는 대신, Scaffold의 paddingValues를 실제로 적용해야 합니다. 현재 상태에서는 시스템 UI(상태 바, 네비게이션 바 등)와 콘텐츠가 겹칠 수 있습니다.

다음 diff를 적용하세요:

-    @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         // 갤러리 권한 요청
         checkAndRequestPermission()
 
         setContent {
             KUIT6_Android_APITheme {
 
                 val snackBarState = remember { SnackbarHostState() }
 
                 Scaffold(
                     modifier = Modifier
                         .fillMaxSize()
                         .background(MaterialTheme.colorScheme.background),
                     snackbarHost = {
                         SnackbarHost(snackBarState)
                     }
-                ) {
+                ) { paddingValues ->
                     val navController = rememberNavController()
 
                     NavGraph(
                         navController = navController,
                         startDestination = PostListRoute,
-                        snackBarState = snackBarState
+                        snackBarState = snackBarState,
+                        modifier = Modifier.padding(paddingValues)
                     )
                 }
             }
         }
     }

참고: NavGraphmodifier 파라미터를 받도록 업데이트해야 합니다.

🧹 Nitpick comments (5)
app/build.gradle.kts (1)

91-94: 의존성 주석 위치 조정

주석이 의존성 그룹 하단이 아닌 상단에 있는 것이 더 명확합니다.

-    implementation(libs.retrofit2.retrofit)
-
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.androidx.espresso.core)
     debugImplementation(libs.androidx.compose.ui.tooling)
     debugImplementation(libs.androidx.compose.ui.test.manifest)
 
-    //retro & okhttp
+    // Retrofit & OkHttp
+    implementation(libs.retrofit2.retrofit)
     implementation(libs.retrofit.converter.gson)
     implementation(libs.okhttp)
     implementation(libs.okhttp.logging.interceptor)
app/src/main/java/com/example/kuit6_android_api/ui/navigation/NavGraph.kt (1)

5-5: 사용하지 않는 import를 제거하세요.

MutableState가 파일 내에서 사용되지 않습니다.

-import androidx.compose.runtime.MutableState
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt (2)

37-37: 사용하지 않는 import를 제거하세요.

State가 파일 내에서 사용되지 않습니다.

-import androidx.compose.runtime.State

61-61: 사용하지 않는 변수를 제거하세요.

context 변수가 선언되었지만 사용되지 않습니다.

-    val context = LocalContext.current
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt (1)

30-34: 중복된 성공 검증 로직을 개선하세요.

response.data?.let 블록 내에서 다시 response.success를 확인하는 것은 중복됩니다. data가 null이 아닌 경우 이미 성공으로 간주할 수 있습니다.

 runCatching {
     apiService.getPosts()
 }.onSuccess { response ->
-    response.data?.let {
-        if (response.success){
-            posts = response.data
-        }
+    if (response.success && response.data != null) {
+        posts = response.data
     }
 }
📜 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 c2e2d11.

📒 Files selected for processing (15)
  • app/build.gradle.kts (4 hunks)
  • app/src/main/java/com/example/kuit6_android_api/MainActivity.kt (3 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/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 (3 hunks)
  • app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostDetailScreen.kt (5 hunks)
  • app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt (5 hunks)
  • app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt (2 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
🔇 Additional comments (11)
app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt (1)

32-38: API 모델로의 전환이 올바르게 적용됨

Post에서 PostResponse로, Author에서 AuthorResponse로의 타입 변경이 적절하게 이루어졌습니다.

app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt (1)

24-24: BASE_URL null 안전성 검증

BuildConfig.BASE_URL이 null이거나 빈 문자열일 경우 런타임 에러가 발생할 수 있습니다. build.gradle.kts의 수정과 함께 여기서도 방어 코드를 추가하는 것이 좋습니다.

build.gradle.kts에서 BASE_URL의 null 처리가 완료되었는지 확인하세요. 추가적인 안전성을 위해 다음과 같은 검증을 추가할 수 있습니다:

private val retrofit: Retrofit = Retrofit.Builder()
    .baseUrl(BuildConfig.BASE_URL.takeIf { it.isNotEmpty() } 
        ?: throw IllegalStateException("BASE_URL is not configured"))
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostDetailScreen.kt (2)

60-64: Snackbar 통합이 올바르게 구현됨

SnackbarHostStaterememberCoroutineScope를 사용한 사용자 피드백 구현이 적절합니다.


202-206: 삭제 후 Snackbar 표시 로직 양호

게시글 삭제 후 Snackbar를 표시하는 흐름이 올바르게 구현되었습니다. 코루틴 스코프를 통한 비동기 처리도 적절합니다.

app/src/main/java/com/example/kuit6_android_api/ui/navigation/NavGraph.kt (1)

19-19: 스낵바 상태 전파가 올바르게 구현되었습니다.

각 화면에 SnackbarHostState를 전달하여 사용자 피드백을 제공할 수 있도록 구현되었습니다.

Also applies to: 47-47, 59-59, 74-74

app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostEditScreen.kt (1)

59-59: 스낵바 통합이 올바르게 구현되었습니다.

게시글 수정 후 사용자 피드백을 제공하기 위한 스낵바 로직이 적절하게 추가되었습니다.

Also applies to: 67-67, 212-212

app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt (1)

58-58: 스낵바 통합이 올바르게 구현되었습니다.

게시글 작성 후 사용자 피드백을 제공하기 위한 스낵바 로직이 적절하게 추가되었습니다.

Also applies to: 66-66, 201-201

app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt (2)

9-41: API 인터페이스가 올바르게 정의되었습니다.

모든 엔드포인트가 적절한 HTTP 메서드와 경로로 정의되었으며, BaseResponse 래퍼를 사용하여 일관된 응답 구조를 제공합니다.


14-17: 기본 author 값과 빈 문자열 전달 로직을 명확히 하세요.

PostCreateScreen에서 작성자 필드는 빈 문자열("")로 초기화되며, 버튼 클릭 시 viewModel.createPost(finalAuthor, ...)로 항상 명시적으로 전달됩니다. PostViewModel.ktcreatePost() 메서드는 이 값을 apiService.createPost(author, request)로 API에 전달합니다.

Retrofit의 @Query 기본값은 파라미터를 전혀 전달하지 않을 때만 적용되므로, 현재 구현에서는 "시헌" 기본값이 실제로 사용되지 않습니다. 사용자가 빈 문자열을 전달하면 그대로 API로 전송됩니다.

  • UI에서 작성자 필드가 비어있을 때의 명시적 처리 로직 추가 필요
  • ApiService의 미사용 기본값 제거 또는 유효한 폴백 로직 구현 필요
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt (1)

24-24: API 클라이언트 초기화가 올바르게 구현되었습니다.

RetrofitClient를 통해 API 서비스를 초기화하여 네트워크 계층과의 통합이 적절하게 이루어졌습니다.

app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt (1)

5-9: Kotlin serialization 플러그인 설정이 올바르게 구성되었습니다.

검증 결과:

  • 플러그인 적용: alias(libs.plugins.kotlin.serialization)
  • 의존성 포함: implementation(libs.kotlinx.serialization.json)
  • @SerialName 어노테이션 사용이 모두 올바르게 설정됨 ✓

데이터 클래스 구조와 serialization 설정이 일관되며, 추가로 수정할 사항이 없습니다.

Comment on lines +23 to +31
// defaultConfig 블록 안에 추가
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 | 🔴 Critical

BASE_URL null 처리 필요

local.properties 파일이 없거나 BASE_URL 속성이 설정되지 않은 경우 baseUrl 변수가 null이 되어 빌드 실패가 발생합니다.

다음 diff를 적용하여 null 처리를 추가하세요:

 // defaultConfig 블록 안에 추가
 val properties = Properties()
 val localPropertiesFile = rootProject.file("local.properties")
 if (localPropertiesFile.exists()) {
     properties.load(localPropertiesFile.inputStream())
 }
-val baseUrl = properties.getProperty("BASE_URL")
+val baseUrl = properties.getProperty("BASE_URL") ?: "https://default-api-url.com/"
 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
// defaultConfig 블록 안에 추가
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\"")
// defaultConfig 블록 안에 추가
val properties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
properties.load(localPropertiesFile.inputStream())
}
val baseUrl = properties.getProperty("BASE_URL") ?: "https://default-api-url.com/"
buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 23 to 31, the code reads BASE_URL from
local.properties but does not handle the case where local.properties is missing
or BASE_URL is unset, causing baseUrl to be null and the build to fail; update
the code to guard against null by providing a safe default or failing fast with
a clear error: after reading properties, check if
properties.getProperty("BASE_URL") is null or blank and either assign a sensible
default string (e.g., empty string or a placeholder) before passing to
buildConfigField, or throw a GradleException with a clear message that BASE_URL
is required; ensure the value passed to buildConfigField is properly quoted so
it remains a valid String literal in the generated BuildConfig.

Comment on lines +12 to +14
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
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

프로덕션 환경에서 로깅 레벨 주의

HttpLoggingInterceptor.Level.BODY는 요청/응답의 전체 본문을 로그에 기록하여 민감한 정보(토큰, 개인정보 등)가 노출될 수 있습니다. 프로덕션 빌드에서는 로깅을 비활성화하거나 레벨을 낮춰야 합니다.

다음과 같이 빌드 타입에 따라 로깅 레벨을 조정하세요:

 private val loggingInterceptor = HttpLoggingInterceptor().apply {
-    level = HttpLoggingInterceptor.Level.BODY
+    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 loggingInterceptor = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
around lines 12 to 14, the HttpLoggingInterceptor is set to Level.BODY which
logs full request/response bodies and can expose sensitive data in production;
change the implementation to set the interceptor level conditionally based on
the build type (e.g., if (BuildConfig.DEBUG) use BODY or BASIC else NONE) or
remove the interceptor entirely for release builds, ensuring production builds
do not log sensitive payloads while keeping detailed logs for debug builds.

Comment on lines +3 to +8
import kotlinx.serialization.SerialName

data class AuthorResponse (
@SerialName(value = "id") val id : Long,
@SerialName(value = "username") val username: String,
@SerialName(value = "profileImageUrl") val profileImageUrl:String?
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

직렬화 라이브러리 불일치 문제

이 파일도 동일한 직렬화 라이브러리 불일치 문제가 있습니다.

Gson 사용 시:

-import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
 
 data class AuthorResponse (
-    @SerialName(value = "id") val id : Long,
-    @SerialName(value = "username") val username: String,
-    @SerialName(value = "profileImageUrl") val profileImageUrl:String?
+    @SerializedName("id") val id : Long,
+    @SerializedName("username") val username: String,
+    @SerializedName("profileImageUrl") val profileImageUrl: String?
 )
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt
around lines 3–8, the file mixes kotlinx.serialization import with Gson-style
usage and also has a class/name mismatch (file AuthResponse.kt vs class
AuthorResponse). Fix by choosing one serialization library: if using Gson,
replace kotlinx.serialization.SerialName import with
com.google.gson.annotations.SerializedName and update annotations to
@SerializedName(...); if using kotlinx.serialization, add
@kotlinx.serialization.Serializable to the data class and keep @SerialName
imports. Also rename the class to match the file (AuthResponse) and ensure
nullable types and property names remain correct.

Comment on lines +3 to +9
import kotlinx.serialization.SerialName

data class BaseResponse<T>(
@SerialName(value = "success") val success : Boolean,
@SerialName(value = "message") val message : String?,
@SerialName(value = "data") val data: T?,
@SerialName(value = "timestamp") val timestamp: String
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

직렬화 라이브러리 불일치 문제

PostResponse.kt와 동일하게, kotlinx.serialization 어노테이션이 사용되었지만 Retrofit은 Gson을 사용하도록 구성되어 있습니다.

PostResponse.kt의 리뷰 코멘트에서 제안한 해결 방법을 이 파일에도 동일하게 적용하세요.

Gson 사용 시:

-import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
 
 data class BaseResponse<T>(
-    @SerialName(value = "success") val success : Boolean,
-    @SerialName(value = "message") val message : String?,
-    @SerialName(value = "data") val data: T?,
-    @SerialName(value = "timestamp") val timestamp: String
+    @SerializedName("success") val success : Boolean,
+    @SerializedName("message") val message : String?,
+    @SerializedName("data") val data: T?,
+    @SerializedName("timestamp") val timestamp: String
 )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt
around lines 3 to 9, the class uses kotlinx.serialization annotations while the
project is configured to use Gson; replace the kotlinx.serialization import and
@SerialName annotations with Gson's com.google.gson.annotations.SerializedName
(or remove annotations if field names already match JSON), update the import to
com.google.gson.annotations.SerializedName, and ensure the annotation values
match the JSON keys ("success", "message", "data", "timestamp").

Comment on lines +3 to +12
import kotlinx.serialization.SerialName

data class PostResponse(
@SerialName(value = "id") val id : Long,
@SerialName(value = "title") val title:String,
@SerialName(value = "content") val content : String,
@SerialName(value = "imageUrl") val imageUrl:String?,
@SerialName(value = "author") val author: AuthorResponse,
@SerialName(value = "createdAt") val createdAt:String,
@SerialName(value = "updatedAt") val updatedAt:String
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

직렬화 라이브러리 불일치 문제

이 파일은 kotlinx.serialization@SerialName 어노테이션을 사용하지만, RetrofitClient.kt에서는 GsonConverterFactory를 사용하고 있습니다. 이는 런타임에 역직렬화 실패를 일으킵니다.

해결 방법 1 (권장): Gson 어노테이션 사용

 package com.example.kuit6_android_api.data.model.response
 
-import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
 
 data class PostResponse(
-    @SerialName(value = "id") val id : Long,
-    @SerialName(value = "title") val title:String,
-    @SerialName(value = "content") val content : String,
-    @SerialName(value = "imageUrl") val imageUrl:String?,
-    @SerialName(value = "author") val author: AuthorResponse,
-    @SerialName(value = "createdAt") val createdAt:String,
-    @SerialName(value = "updatedAt") val updatedAt:String
+    @SerializedName("id") val id : Long,
+    @SerializedName("title") val title: String,
+    @SerializedName("content") val content : String,
+    @SerializedName("imageUrl") val imageUrl: String?,
+    @SerializedName("author") val author: AuthorResponse,
+    @SerializedName("createdAt") val createdAt: String,
+    @SerializedName("updatedAt") val updatedAt: String
 )

해결 방법 2: kotlinx.serialization 컨버터 사용

RetrofitClient.kt에서 컨버터를 변경:

// build.gradle.kts에 추가
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")

// RetrofitClient.kt
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))

동일한 수정이 BaseResponse.ktAuthResponse.kt에도 필요합니다.

🤖 Prompt for AI Agents
app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt
lines 3-12: the file uses kotlinx.serialization @SerialName while Retrofit is
configured with Gson, causing runtime deserialization failures; either (A)
replace kotlinx.serialization annotations with Gson's @SerializedName on
PostResponse (and similarly update BaseResponse.kt and AuthResponse.kt) so they
match the Gson converter, or (B) switch Retrofit to the kotlinx.serialization
converter by adding the retrofit kotlinx-serialization converter dependency and
configuring Retrofit to use
Json.asConverterFactory("application/json".toMediaType()) so the existing
@SerialName annotations work; apply the same change consistently for
BaseResponse.kt and AuthResponse.kt.

Button(
onClick = {
val finalAuthor = author.ifBlank { "anonymous" }
val finalAuthor = author
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

🧩 Analysis chain

작성자 필드 유효성 검증을 추가하세요.

작성자 필드가 비어있을 수 있습니다. 기존에는 "anonymous" 기본값이 있었으나 제거되었습니다. API 엔드포인트에서 기본값("시헌")을 사용하는 것으로 보이지만, 사용자가 입력한 빈 문자열이 의도대로 처리되는지 확인하세요.


🏁 Script executed:

#!/bin/bash
# Description: ApiService에서 author 파라미터 처리 방식 확인

# createPost 메서드에서 author 파라미터 기본값 및 처리 확인
ast-grep --pattern $'suspend fun createPost(
  @Query("author") author: String$$$
)$$$'

Length of output: 0


추가 검증을 위해 리포지토리를 탐색하겠습니다.




---

🏁 Script executed:

```shell
#!/bin/bash
# Find ApiService file and examine author parameter handling
fd -e "\.kt$" | xargs grep -l "fun createPost" | head -5

# Also find PostCreateScreen.kt
fd -name "PostCreateScreen.kt"

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Search for createPost method definition with context
rg -n "suspend fun createPost" -A 5 -B 2 --type=kotlin

Length of output: 799


🏁 Script executed:

#!/bin/bash
# Check PostCreateScreen.kt for author field handling around line 198
cat "app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt" | sed -n '190,210p'

Length of output: 780





---

🏁 Script executed:

```shell
#!/bin/bash
# Find author variable declaration and initialization in PostCreateScreen.kt
rg -n "var author|val author" "app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt" -B 2 -A 2

Length of output: 207


🏁 Script executed:

#!/bin/bash
# Check the viewModel.createPost method signature
rg -n "fun createPost" "app/src/main/java/com/example/kuit6_android_api" -A 3 -B 2 --type=kotlin | grep -v "ApiService"

Length of output: 617


🏁 Script executed:

#!/bin/bash
# Get more context around line 198 in PostCreateScreen.kt - check lines 1-100 to find author initialization
sed -n '1,100p' "app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt" | grep -n "author"

Length of output: 52


작성자 필드 유효성 검증이 필요합니다.

작성자 필드(author)가 라인 62에서 빈 문자열("")로 초기화되고, 라인 198의 버튼 활성화 조건에서 제목과 내용만 검증합니다(title.isNotBlank() && content.isNotBlank()). 사용자가 작성자를 입력하지 않은 채 게시글을 작성할 수 있으며, 빈 문자열이 API에 명시적으로 전달되면 서버의 기본값("시헌")이 적용되지 않습니다.

조치: 다음 중 하나를 선택하세요:

  • 버튼 활성화 조건에 author.isNotBlank() 추가
  • 또는 사용자 입력 전 author에 기본값 설정
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt
around lines 62 and 198, the author field is initialized to an empty string at
line 62 but the submit button enablement at line 198 only checks title and
content, allowing empty author to be sent and bypass server default; either
require author input or apply a client-side default. Fix by choosing one: (A)
include author.isNotBlank() in the button enabled condition so the button stays
disabled until author is provided, or (B) set finalAuthor just before submit to
use the server default when author.isBlank() (e.g., replace empty author with
"시헌") and keep UI behavior unchanged; implement one of these options and ensure
the request payload never contains an empty author string.

Comment on lines 25 to 37
fun getPosts() {
viewModelScope.launch {
delay(500) // 네트워크 시뮬레이션
posts = dummyPosts.toList()
runCatching {
apiService.getPosts()
}.onSuccess { response ->
response.data?.let {
if (response.success){
posts = response.data
}
}
}
}
}
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

에러 처리 로직을 추가하세요.

runCatching을 사용하지만 onFailure 핸들러가 없어 API 호출 실패 시 사용자에게 피드백을 제공할 수 없습니다. 네트워크 오류, 타임아웃, 서버 오류 등의 상황에서 적절한 에러 처리가 필요합니다.

다음과 같이 에러 처리를 추가하는 것을 권장합니다:

 fun getPosts() {
     viewModelScope.launch {
         runCatching {
             apiService.getPosts()
         }.onSuccess { response ->
             response.data?.let {
                 if (response.success){
                     posts = response.data
                 }
             }
+        }.onFailure { error ->
+            // 에러 상태 처리 (예: 에러 메시지 노출, 로깅 등)
         }
     }
 }
📝 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
fun getPosts() {
viewModelScope.launch {
delay(500) // 네트워크 시뮬레이션
posts = dummyPosts.toList()
runCatching {
apiService.getPosts()
}.onSuccess { response ->
response.data?.let {
if (response.success){
posts = response.data
}
}
}
}
}
fun getPosts() {
viewModelScope.launch {
runCatching {
apiService.getPosts()
}.onSuccess { response ->
response.data?.let {
if (response.success){
posts = response.data
}
}
}.onFailure { error ->
// 에러 상태 처리 (예: 에러 메시지 노출, 로깅 등)
}
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
around lines 25 to 37, the runCatching call lacks an onFailure handler so API
failures produce no user feedback; add an onFailure { error -> ... } block that
logs the exception and updates an error state (LiveData/StateFlow) or exposes a
user-visible message, ensure any loading flags are cleared/updated in both
success and failure paths, and keep the existing success logic unchanged so
posts are only set when response.success is true.

Comment on lines 39 to 50
fun getPostDetail(postId: Long) {
viewModelScope.launch {
delay(300)
postDetail = dummyPosts.find { it.id == postId }
runCatching {
apiService.getPostDetail(postId)
}.onSuccess { response ->
response.data?.let{
if(response.success)
postDetail = response.data
}
}
}
}
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

🧩 Analysis chain

모든 API 호출 메서드에 일관된 에러 처리를 적용하세요.

getPostDetail, createPost, updatePost, deletePost 메서드 모두 동일한 문제가 있습니다:

  • onFailure 핸들러가 없음
  • 로딩 상태 관리가 없음
  • 에러 상태 관리가 없음

이는 사용자 경험을 저해하고 디버깅을 어렵게 만듭니다. 다음을 고려하세요:

  • 각 메서드에 onFailure 핸들러 추가
  • ViewModel에 isLoadingerrorMessage 상태 추가
  • UI에서 로딩 인디케이터와 에러 메시지 표시

Also applies to: 52-71, 73-94, 96-107


:::


🏁 Script executed:

#!/bin/bash
# Locate and inspect PostViewModel.kt
find . -name "PostViewModel.kt" -type f | head -1 | xargs cat

Length of output: 3506


🏁 Script executed:

#!/bin/bash
# Search for isLoading, errorMessage, and error handling patterns in the file
find . -name "PostViewModel.kt" -type f -exec rg -n "isLoading|errorMessage|onFailure|onSuccess" {} + | head -50

Length of output: 461


🏁 Script executed:

#!/bin/bash
# Check the structure and line count of PostViewModel.kt
find . -name "PostViewModel.kt" -type f | xargs wc -l

Length of output: 88


모든 API 호출 메서드에 일관된 에러 처리를 적용하세요.

검증 결과, 지적된 모든 문제가 확인되었습니다:

  • getPostDetail (39-50행): onFailure 핸들러 없음
  • createPost (52-71행): onFailure 핸들러 없음
  • updatePost (73-94행): onFailure 핸들러 없음
  • deletePost (96-107행): onFailure 핸들러 없음

ViewModel에 isLoadingerrorMessage 상태 속성이 존재하지 않습니다. 모든 API 메서드가 runCatching을 사용하지만 성공 케이스만 처리하고 있으며, 실패 시나리오에 대한 처리가 완전히 빠져있습니다. 다음을 수행하세요:

  • 각 메서드에 onFailure 핸들러 추가
  • ViewModel에 isLoadingerrorMessage mutableStateOf 속성 추가
  • 로딩/에러 상태 업데이트 로직 구현

@rbqks529 rbqks529 merged commit f737786 into Konkuk-KUIT:birdy736 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