Skip to content

[Feat] 카카오 소셜 로그인#76

Merged
Nico1eKim merged 22 commits intoTHIP-TextHip:developfrom
JJUYAAA:feat/#68-social_login_kakao
Aug 18, 2025
Merged

[Feat] 카카오 소셜 로그인#76
Nico1eKim merged 22 commits intoTHIP-TextHip:developfrom
JJUYAAA:feat/#68-social_login_kakao

Conversation

@JJUYAAA
Copy link
Member

@JJUYAAA JJUYAAA commented Aug 12, 2025

➕ 이슈 링크


🔎 작업 내용

  • 카카오 소셜 로그인 구현했습니다.
  • 카카오 sdk를 활용해 자체적으로 로그인을 하고, 로그인 과정에서 얻은 사용자 고유 식별 ID를 body에 넣어 <신규 사용자 여부 확인> 요청을 보냅니다.
  • 응답으로 토큰 (신규 사용자인 경우 임시토큰 / 기존 사용자인 경우 액세스토큰) 을 받아옵니다.
  • 기존 사용자인 경우에는 피드 스크린으로 이동합니다.
  • 신규 사용자인 경우에는 회원가입 첫 화면으로 이동합니다.

📸 스크린샷

신규 사용자

bandicam.2025-08-12.19-33-14-745.mp4
bandicam.2025-08-12.19-33-47-408.mp4

기존 사용자

bandicam.2025-08-12.19-41-38-393.mp4

서버 통신 완료

image image

😢 해결하지 못한 과제

  • 구글 로그인 진행중입니다. 따로 pr 올리겠습니다!


📢 리뷰어들에게

  • 지난주 말씀드렸던 제 IDE에서 BuildConfig 인식을 하지 못하는 오류가 해결되지 않습니다..ㅜㅜ 그래서 기능 구현시에 "BASE_URL"과 "NATIVE_APP_KEY"는 하드코딩으로 주입시켜 개발 진행했습니다.
  • 커밋에는 포함되지 않도록 변경 후 커밋 완료했습니다.
  • 머지되면 각자 로컬의 local.properties에 톡방에 공유해드린 NATIVE_APP_KEY를 작성 후 BuildConfig 이슈가 없는지 확인 부탁드립니다.

Summary by CodeRabbit

  • New Features

    • 카카오 로그인 통합: SDK 초기화, 카카오 인증 흐름과 서버 연동으로 간편 로그인 지원 및 토큰 저장/관리 추가.
  • Refactor

    • 앱 진입부를 네비게이션 기반으로 재구성: Splash → Login → Signup 등 화면 흐름 및 중앙 네비게이션 도입.
  • Chores

    • 인터넷 권한 및 인증 리다이렉트 설정 추가, 빌드 설정에 앱 키 연동, 카카오 SDK·DataStore·라이브러리 의존성 추가.

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Walkthrough

카카오 소셜 로그인 연동을 위해 SDK 초기화, Retrofit AuthService 및 AuthRepository, LoginViewModel/UI(스플래시·로그인·회원가입)와 네비게이션, DataStore 기반 TokenManager, 빌드 설정(키·의존성·레포지토리) 및 매니페스트 리디렉션 액티비티를 추가/수정했습니다.

Changes

Cohort / File(s) Summary
Build config & repos
app/build.gradle.kts, gradle.properties, gradle/libs.versions.toml, settings.gradle.kts
BuildConfig NATIVE_APP_KEY 추가, Kakao/Datastore/lifecycle-runtime-compose 의존성 및 Kakao Maven 레포지토리 등록, buildConfig 활성화 프로퍼티 추가
Manifest & OAuth Redirect
app/src/main/AndroidManifest.xml
INTERNET 권한 추가, com.kakao.sdk.AppKey 메타데이터 및 AuthCodeHandlerActivity(exported)와 kakao{NATIVE_APP_KEY} 스킴 인텐트필터 등록
Application init
app/src/main/java/com/texthip/thip/ThipApplication.kt
애플리케이션 시작 시 KakaoSdk 초기화(KakaoSdk.init(context, BuildConfig.NATIVE_APP_KEY)) 추가 (예외 처리 포함)
DI: Service providers
app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt, .../NetworkModule.kt
Retrofit용 AuthService 제공자(provideAuthService) 추가 및 import 추가
Auth API models & service
.../data/model/auth/request/AuthRequest.kt, .../data/model/auth/response/AuthResponse.kt, app/src/main/java/.../data/service/AuthService.kt
AuthRequest, AuthResponse 직렬화 모델 추가 및 /oauth2/users POST 정의한 AuthService 인터페이스 추가
Repository
app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt
Kakao SDK로 로그인 수행 → Kakao 사용자 ID 추출 → AuthService 호출 흐름을 구현한 AuthRepository 추가
ViewModel & UI state
app/src/main/java/com/texthip/thip/ui/signin/viewmodel/KakaoLoginViewModel.kt
LoginUiState(Idle/Loading/Success/Error)와 LoginViewModel 추가, kakaoLogin(context)·clearLoginState() 구현
Token persistence
app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt
DataStore 기반 다중 토큰(APP/TEMP/ACCESS/REFRESH) 저장·조회·삭제 API 추가
Navigation & entrypoint
app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt, app/src/main/java/com/texthip/thip/MainActivity.kt
Splash/Login/Signup 라우트 추가 및 RootNavHost 기반 내비게이션 진입점으로 전환
Signin screens
app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt, .../LoginScreen.kt, .../SignupNicknameScreen.kt
Splash → 지연 후 Login 네비게이션, Login은 ViewModel 상태에 따른 토큰 저장·분기 처리, 화면 시그니처가 NavController를 받도록 변경
IDE setting
.idea/gradle.xml
IntelliJ 프로젝트 설정: gradleJvm 값을 #GRADLE_LOCAL_JAVA_HOMEjbr-21로 변경

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Splash as SplashScreen
  participant Login as LoginScreen
  participant VM as LoginViewModel
  participant Repo as AuthRepository
  participant Kakao as Kakao SDK
  participant AuthSvc as AuthService
  participant Backend as Backend API
  participant Token as TokenManager
  participant Nav as NavController

  User->>Splash: 앱 실행
  Splash-->>Nav: 3초 후 Login으로 navigate

  User->>Login: 카카오 로그인 클릭
  Login->>VM: kakaoLogin(context)
  VM->>Repo: loginWithKakao(context)
  Repo->>Kakao: 카카오 로그인/사용자 정보 요청
  Kakao-->>Repo: 사용자 ID 반환
  Repo->>AuthSvc: POST /oauth2/users (oauth2Id)
  AuthSvc->>Backend: 서버 요청
  Backend-->>AuthSvc: AuthResponse(token,isNewUser)
  AuthSvc-->>Repo: 응답 전달
  Repo-->>VM: Result<AuthResponse?>
  VM-->>Login: Success / Error 상태 방출
  Login->>Token: saveToken(token) (성공 시)
  alt isNewUser == true
    Login-->>Nav: Signup으로 navigate
  else
    Login-->>Nav: Feed로 navigate
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
카카오sdk 활용해 로그인 수행 (THIP2025-201 / #68)
사용자 식별 ID 추출해 서버 api 연동 (THIP2025-201 / #68)
응답값에 따라 화면 분기처리 (THIP2025-201 / #68)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Gradle JDK 변경 .idea/gradle.xml (gradleJvm value 변경) IDE 환경 설정 변경으로 소셜 로그인 연동 요구사항과 직접 관련 없음.

Possibly related PRs

Suggested labels

🍀 refactor, ✅ OK merge

Suggested reviewers

  • rbqks529

Poem

"나는 토끼, 코드 밭을 살금살금, 🐇
카카오 문 두드리면 사용자가 쏙— 들어오네.
토큰은 DataStore 주머니에 살포시 숨기고,
스플래시 건너뛴 뒤 화면은 부드럽게 갈림.
당근처럼 달콤한 로그인 축하해!"

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 11

🔭 Outside diff range comments (1)
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)

64-65: 네비게이션 로직 누락

닉네임 검증 성공 시 다음 화면으로 이동하는 네비게이션 로직이 구현되지 않았습니다. navController 파라미터를 받았지만 사용하지 않고 있습니다.

다음과 같이 네비게이션 로직을 추가하세요:

 } else {
     showWarning = false
     warningMessageResId = null
-    // 다음 페이지로 이동
+    // 다음 페이지로 이동
+    navController.navigate(/* 다음 회원가입 단계 라우트 */)
 }
🧹 Nitpick comments (9)
settings.gradle.kts (1)

19-20: Kakao 전용 레포지토리의 Content Filter 추가로 공급망/충돌 리스크 축소

Kakao Dev Repo는 퍼블릭 그룹 번들을 제공하여 광범위한 아티팩트를 노출합니다. 레포지토리 콘텐츠 필터를 적용해 com.kakao.sdk 그룹으로 한정하면, 불필요한 아티팩트 해상 및 잠재적 버전 충돌/공급망 리스크를 줄일 수 있습니다.

적용 제안(diff):

-        maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/")
+        maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/") {
+            content {
+                includeGroup("com.kakao.sdk")
+            }
+        }
app/src/main/java/com/texthip/thip/data/model/auth/request/AuthRequest.kt (1)

5-8: 요청 스키마 확장성 고려: provider 분리 검토 및 불필요한 SerialName 제거 가능

  • 현재 oauth2Id에 공급자 프리픽스(kakao_...)를 포함시키는 대신, API가 허용한다면 provider 필드를 분리하면 확장성(구글/카카오 추가)과 서버의 명확성이 좋아집니다.
  • 필드명이 JSON과 동일하므로 @SerialName은 생략 가능합니다(선호/팀 컨벤션에 따름).

선택적 리팩터 제안(모델 확장 예시):

enum class SocialProvider { KAKAO, GOOGLE }

@Serializable
data class AuthRequest(
    val provider: SocialProvider,
    val oauth2Id: String
)

@SerialName 제거만 적용(diff):

-    @SerialName("oauth2Id") val oauth2Id: String
+    val oauth2Id: String

추가로, 위 변경 시 사용되지 않는 import kotlinx.serialization.SerialName도 제거하세요.

app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (2)

19-24: preferencesDataStore 위임 프로퍼티는 파일 최상위로 이동하는 것을 권장합니다.

권장 패턴은 val Context.dataStore by preferencesDataStore(name = "...")를 파일 최상위에 두는 것입니다. 현재 @singleton으로 리스크는 낮지만, 모듈 전반에서 일관된 싱글톤 인스턴스를 보장하기 위해 관용 패턴을 따르는 것이 좋습니다.

예시(파일 상단, 클래스 밖):

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")

그리고 클래스 내부의 동일 선언은 제거하세요.


26-31: 액세스 토큰은 평문 저장 대신 암호화를 고려하세요.

DataStore(Preferences)는 평문 저장입니다. 토큰 민감도에 따라 AndroidX Security Crypto(Keystore 기반)로 암호화하여 저장하거나, 최소한 전송/로그 시 마스킹을 적용하세요.

원하시면 Security Crypto를 이용한 암호화 저장 구현(마스터키 생성, AES-GCM 변환기 적용) 패치를 제안드리겠습니다. 필요하신가요?

app/src/main/java/com/texthip/thip/data/di/NetworkModule.kt (1)

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

AuthService는 이 모듈에서 사용되지 않습니다. 정리하지 않으면 빌드 도구(ktlint/detekt) 경고를 유발할 수 있습니다.

적용 diff:

-import com.texthip.thip.data.service.AuthService

추가로, 인증 토큰을 다루는 요청/응답이 포함되므로 HttpLoggingInterceptor.Level.BODY는 release 빌드에서 비활성화하거나 민감정보 마스킹을 권장합니다.

app/build.gradle.kts (1)

96-100: 직접 문자열 의존성 대신 버전 카탈로그로 관리하세요.

Kakao SDK와 DataStore를 버전 카탈로그로 옮기면 버전/의존성 관리가 용이해집니다.

예시:

  • gradle/libs.versions.toml
    • [versions]에 kakao = "2.21.6", datastorePreferences = "1.1.1" 추가
    • [libraries]에
      • kakao-v2-all = { module = "com.kakao.sdk:v2-all", version.ref = "kakao" }
      • androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
  • 본 파일 변경(diff):
-    implementation("com.kakao.sdk:v2-all:2.21.6")
+    implementation(libs.kakao.v2.all)

-    implementation ("androidx.datastore:datastore-preferences:1.1.1")
+    implementation(libs.androidx.datastore.preferences)
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)

54-67: TODO 주석에 따른 서버 연동 구현 필요

Line 55의 TODO 주석이 있는 서버 연동 로직 구현이 필요합니다. 현재는 테스트용 시뮬레이션 코드만 있습니다.

닉네임 중복 확인 API 연동 코드를 생성하시겠습니까? 아니면 이 작업을 추적할 새 이슈를 생성하시겠습니까?

app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt (1)

60-71: 널 단언(!!) 제거 및 가독성 향상

user?.id != null 체크 후 user.id!!를 사용하는 패턴은 불필요한 단언입니다. let으로 안전하게 처리해 주세요.

-            } else if (user?.id != null) {
-                continuation.resume(user.id!!)
+            } else if (user?.id != null) {
+                continuation.resume(user.id)
app/src/main/java/com/texthip/thip/ui/signin/screen/LoginScreen.kt (1)

54-54: 주석 정리 권장

//by 오류로 인해 변경함과 같은 이력성 주석은 혼선을 줍니다. 필요 시 커밋 메시지로 남기고 코드에서는 제거를 권장합니다.

-    val uiState = viewModel.uiState.collectAsStateWithLifecycle().value //by 오류로 인해 변경함
+    val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b233b41 and f39127a.

📒 Files selected for processing (20)
  • .idea/gradle.xml (1 hunks)
  • app/build.gradle.kts (3 hunks)
  • app/src/main/AndroidManifest.xml (2 hunks)
  • app/src/main/java/com/texthip/thip/MainActivity.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ThipApplication.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/di/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/model/auth/request/AuthRequest.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/model/auth/response/AuthResponse.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/service/AuthService.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/screen/LoginScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/viewmodel/KakaoLoginViewModel.kt (1 hunks)
  • gradle.properties (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
  • settings.gradle.kts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/src/main/java/com/texthip/thip/ui/signin/screen/LoginScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/MainActivity.kt (8)
app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt (1)
  • SplashScreen (32-71)
app/src/main/java/com/texthip/thip/ui/signin/screen/LoginScreen.kt (1)
  • LoginScreen (48-101)
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)
  • SigninNicknameScreen (33-99)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
  • feedNavigation (10-23)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
  • groupNavigation (39-217)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt (1)
  • searchNavigation (9-13)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (1)
  • myPageNavigation (10-19)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/CommonNavigation.kt (1)
  • commonNavigation (14-29)
🔇 Additional comments (15)
app/src/main/java/com/texthip/thip/data/model/auth/response/AuthResponse.kt (2)

6-10: 모델 스키마 적절함 (token, isNewUser) — LGTM

서버 응답 요건(신규/기존 사용자 분기, 토큰 반환)에 부합하는 간결한 모델입니다. 직렬화 애노테이션 사용도 일관적입니다.


6-10: Kotlinx Serialization 플러그인 및 Retrofit 컨버터 설정 재확인 요청

  • libs.versions.toml에서 kotlinx-serialization-jsonretrofit2-kotlinx-serialization-converter 의존성 등록이 확인되었습니다.
  • NetworkModule.kt에서 json.asConverterFactory("application/json".toMediaType()) 호출로 컨버터도 Retrofit에 잘 적용된 것을 확인했습니다.
  • Gradle 스크립트(예: plugins { id("org.jetbrains.kotlin.plugin.serialization") } 또는 kotlin("plugin.serialization"))에 kotlinx.serialization 플러그인이 실제로 적용되어 있는지 다시 한번 검토해 주세요.
  • 보안 주의: 발급된 토큰이 로그나 크래시 리포팅에 노출되지 않도록 반드시 마스킹하거나 제외해 주세요.
gradle/libs.versions.toml (1)

62-62: 라이브러리 항목 추가는 적절합니다. 다만 상단 버전 별칭을 안정 버전으로 조정 후 반영하세요.

라이브러리 선언 자체는 문제 없습니다. 위 코멘트대로 버전 별칭을 안정화한 뒤 그대로 사용하면 됩니다.

app/src/main/java/com/texthip/thip/data/service/AuthService.kt (1)

9-15: BaseResponse import 경로 확인 완료: 문제 없습니다

AuthService.kt에서 import한
com.texthip.thip.data.model.base.BaseResponse
가 실제 정의된 패키지(com.texthip.thip.data.model.base)와 일치하므로 별도 수정이 필요 없습니다.

app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (1)

10-18: 인증 관련 라우트 추가 확인

Splash, Login, Signup 라우트가 적절하게 추가되었습니다. 카카오 소셜 로그인 플로우를 위한 네비게이션 구조가 잘 구성되었습니다.

app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt (1)

35-39: AuthService 프로바이더 메서드 추가 확인

AuthService를 제공하는 메서드가 올바르게 추가되었습니다. Retrofit을 통한 의존성 주입이 적절하게 구성되었습니다.

app/src/main/AndroidManifest.xml (2)

5-5: INTERNET 권한 추가 적절

카카오 로그인 및 서버 통신을 위해 필수 권한입니다.


23-35: AuthCodeHandlerActivity 선언 적절

카카오 OAuth 리다이렉트를 수신하기 위해 필요한 설정이며, exported/intent-filter 구성도 적절합니다.

app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt (2)

21-32: 카카오 로그인 → 서버 확인 플로우 구성 적절

  • Kakao SDK로 사용자 ID 확보 후 서버에 신규/기존 여부 확인하는 흐름이 명확합니다.
  • runCatching { ... }.getOrThrow() 조합으로 예외를 Result로 감싸는 처리도 타당합니다.

35-58: 카카오톡 로그인 실패 시 계정 로그인으로 안전하게 폴백하세요

현재는 loginWithKakaoTalk에서 오류가 나면 즉시 예외로 종료합니다. 사용자 취소(Cancelled)가 아닌 경우에는 loginWithKakaoAccount로 폴백하는 것이 권장 패턴입니다. 또한 코루틴 취소 시 콜백 이후 추가 resume 방지를 위해 continuation.isActive 체크도 안전합니다.
[Suggest_essential_refactor]
아래와 같이 보완을 제안합니다:

@@
-            UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
-                if (error != null) {
-                    continuation.resumeWithException(error)
-                } else if (token != null) {
-                    // 로그인 성공 시 사용자 정보 요청
-                    fetchUserInfo(continuation)
-                }
-            }
+            UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
+                if (error != null) {
+                    // 사용자 취소는 그대로 에러 전달
+                    val isCancelled = error::class.java.name.contains("ClientError") &&
+                        (error.message ?: "").contains("Cancelled", ignoreCase = true)
+                    if (isCancelled) {
+                        if (continuation.isActive) continuation.resumeWithException(error)
+                    } else {
+                        // 그 외 오류는 카카오계정 로그인으로 폴백
+                        UserApiClient.instance.loginWithKakaoAccount(context) { accountToken, accountError ->
+                            if (accountError != null) {
+                                if (continuation.isActive) continuation.resumeWithException(accountError)
+                            } else if (accountToken != null) {
+                                fetchUserInfo(continuation)
+                            }
+                        }
+                    }
+                } else if (token != null) {
+                    fetchUserInfo(continuation)
+                }
+            }

추가로, 취소 처리 시 리스너 정리를 원하시면 다음도 고려해 주세요(선택):

continuation.invokeOnCancellation {
    // 필요 시 콜백 기반 API 취소/정리 로직 추가
}
app/src/main/java/com/texthip/thip/MainActivity.kt (1)

30-31: RootNavHost 도입과 네비게이션 모듈화 구성 좋습니다

Typed routes를 활용한 그래프 구성, 인증/메인 섹션 분리 모두 명확합니다. 네비게이션 허브로의 책임도 적절합니다.

app/src/main/java/com/texthip/thip/ui/signin/screen/LoginScreen.kt (2)

92-99: 로딩 인디케이터 오버레이 처리 적절

전체 화면 오버레이로 Loading 상태를 명확히 표시합니다.


71-73: popUpTo 제네릭 타입 사용 검토

현재 코드에서 navigation-compose 의존성 버전을 자동으로 찾을 수 없어 수동 확인이 필요합니다.
아래 사항을 확인 후 반영해 주세요.

• build.gradle(.kts)에서
androidx.navigation:navigation-compose 버전이 2.8.0 이상인지 확인
• 만약 2.8.0 이상이면, popUpTo도 제네릭 형태로 통일
• 2.8.0 미만이라면, 반대로 문자열 라우트 방식으로 일관되게 사용

제안하는 코드 변경 예시:

- navController.navigate(destination) {
-     popUpTo(CommonRoutes.Login) { inclusive = true }
- }
+ navController.navigate(destination) {
+     popUpTo<CommonRoutes.Login> { inclusive = true }
+ }
app/src/main/java/com/texthip/thip/ui/signin/viewmodel/KakaoLoginViewModel.kt (2)

16-21: LoginUiState 정의 명확하고 적절

Idle/Loading를 data object로, Success/Error를 data class로 분리하여 상태 표현이 깔끔합니다.


31-49: 에러/성공 결과 매핑 로직 명확

onSuccess/onFailure로 결과를 UI 상태로 전환하는 흐름이 깔끔하며, 기본 에러 메시지 폴백 처리도 적절합니다. 로그에는 민감정보가 포함되지 않도록 유지해 주세요.

<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="gradleJvm" value="jbr-21" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

IDE 전용 설정(.idea) 커밋 지양 및 JDK는 Gradle Toolchain으로 관리 권장

.idea/gradle.xml의 JDK 설정은 로컬 IDE 환경 의존성이므로 VCS에 포함하지 않는 것이 좋습니다. 팀/CI 환경과의 충돌을 야기할 수 있어요. 해당 변경은 되돌리거나 .gitignore로 관리하고, JDK 버전은 Gradle Toolchain으로 일관되게 지정하는 방식을 권장합니다.

Line 9에 대해 되돌림 제안:

-        <option name="gradleJvm" value="jbr-21" />
+        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />

추가 제안(다른 파일에 적용):

  • Kotlin DSL 예시 (각 모듈 build.gradle.kts)
kotlin {
  jvmToolchain(21)
}
android {
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
  }
}
🤖 Prompt for AI Agents
In .idea/gradle.xml around line 9, remove or revert the IDE-specific JDK setting
("gradleJvm" value) from version control because it ties the repo to a local IDE
configuration; instead, revert this file change (or restore the original), add
.idea/ files to .gitignore if not already ignored, and enforce the JDK version
via Gradle Toolchain in your build scripts (e.g., configure jvmToolchain/compile
options in each module) so JDK is managed consistently across developer and CI
environments.

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField("String", "BASE_URL", "\"${properties["BASE_URL"]}\"")
buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["NATIVE_APP_KEY"]}\"")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

local.properties 미설정 시 “null” 문자열이 주입될 수 있습니다. 강제 검증 로직으로 빌드를 fail-fast 하세요.

현재 properties["NATIVE_APP_KEY"]가 없으면 "null" 문자열이 들어갈 수 있습니다. 빌드 시점에 존재 여부를 검증하고 없으면 명시적으로 실패시키는 것이 안전합니다.

선택 범위 내 변경(diff):

-        buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["NATIVE_APP_KEY"]}\"")
+        buildConfigField("String", "NATIVE_APP_KEY", "\"$nativeAppKey\"")

선택 범위 밖에 필요한 보조 코드(상단 프로퍼티 로드 직후):

val nativeAppKey = properties.getProperty("NATIVE_APP_KEY")
    ?: throw GradleException("Missing NATIVE_APP_KEY in local.properties")
🤖 Prompt for AI Agents
In app/build.gradle.kts around line 30, the buildConfigField uses
properties["NATIVE_APP_KEY"] which can inject the literal "null" when the key is
absent; add a fail-fast check immediately after loading properties: read the
value with properties.getProperty("NATIVE_APP_KEY"), and if it's null throw a
GradleException("Missing NATIVE_APP_KEY in local.properties"), then use that
validated variable in buildConfigField so the build explicitly fails when the
key is not set.

implementation(libs.kotlinx.serialization.json)
implementation(libs.coil.compose)
implementation(libs.foundation)
implementation(libs.androidx.lifecycle.runtime.compose)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

알파 채널 의존성 사용 주의: lifecycle-runtime-compose는 안정 버전으로 정렬 권장

여기서 사용하는 libs.androidx.lifecycle.runtime.compose가 toml에서 2.10.0-alpha01로 지정되어 있습니다. 다른 라이프사이클 라이브러리와 안정 버전으로 맞추는 게 안전합니다. toml의 lifecycleRuntimeCompose 버전을 안정 버전으로 내리고, 여기 의존성은 그대로 alias를 사용하세요.

위 파일 코멘트에 남긴 웹 검색으로 최신 안정 버전을 확인해 주세요.


먼저 libs.versions.toml에서 lifecycleRuntimeCompose가 어떤 버전으로 지정되어 있는지 확인하고, 이어서 최신 안정 버전을 웹에서 조회합니다.


🏁 Script executed:

#!/bin/bash
# 버전 카탈로그 파일 경로 찾기
catalog=$(fd --hidden --ignore-case "libs.versions.toml" | head -n1)
if [[ -n "$catalog" ]]; then
  echo "== $catalog 에서 lifecycleRuntimeCompose 버전 추출 =="
  rg "lifecycleRuntimeCompose" -n "$catalog"
else
  echo "Error: libs.versions.toml 파일을 찾을 수 없습니다."
fi

Length of output: 525


🌐 Web query:

androidx.lifecycle:lifecycle-runtime-compose latest stable version

💡 Result:

The latest stable version is 2.9.2 (released July 16, 2025). (developer.android.com, mvnrepository.com)

Gradle (Groovy):
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.9.2"

Gradle (Kotlin DSL):
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.9.2")

This artifact is published on Google’s Maven repository, so ensure you have google() in your repositories. (mvnrepository.com, developer.android.com)

Would you like the Maven XML snippet or the corresponding lifecycle/viewmodel-compose version info as well?

Citations:


안정 버전 사용 권장: lifecycle-runtime-compose 2.9.2로 변경

아래와 같이 버전 카탈로그를 안정 버전으로 롤백하고, build.gradle.kts에서는 기존 alias 사용을 유지하세요.

• gradle/libs.versions.toml (28행)

- lifecycleRuntimeCompose = "2.10.0-alpha01"
+ lifecycleRuntimeCompose = "2.9.2"

• app/build.gradle.kts (72행)

implementation(libs.androidx.lifecycle.runtime.compose)
🤖 Prompt for AI Agents
In app/build.gradle.kts around line 72, keep the existing alias usage
(implementation(libs.androidx.lifecycle.runtime.compose)) but update the version
in gradle/libs.versions.toml (around line 28) to the stable
lifecycle-runtime-compose 2.9.2; edit the TOML entry to set the version to
"2.9.2" so the alias continues to resolve to the stable release without changing
build.gradle.kts.

Comment on lines +19 to +21
<meta-data
android:name="com.kakao.sdk.AppKey"
android:value="${NATIVE_APP_KEY}" />
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Manifest placeholder 주입 경로 확인 필요 (${NATIVE_APP_KEY})

Manifest의 meta-data 값에 ${NATIVE_APP_KEY} placeholder를 사용하고 있는데, app/build.gradle(.kts)의 defaultConfig.manifestPlaceholders["NATIVE_APP_KEY"]에 실제 값이 매핑되지 않으면 런타임에 그대로 문자열로 남아 Kakao SDK 초기화가 실패합니다. BuildConfig 필드와 Manifest placeholder는 별개이므로, manifestPlaceholders를 명시적으로 설정했는지 확인해 주세요. 필요하다면 설정 스니펫 제공 가능합니다.

다음 스크립트로 설정 여부를 점검해 주세요:


🏁 Script executed:

#!/bin/bash
# manifestPlaceholders에 NATIVE_APP_KEY 정의 여부 확인
fd build.gradle.kts -a | while read -r f; do
  echo ">>> $f"
  rg -n "manifestPlaceholders" "$f" -A 5 -n || true
  rg -n "NATIVE_APP_KEY" "$f" -A 2 -n || true
done

Length of output: 663


Manifest placeholder 설정 누락: NATIVE_APP_KEY

app/build.gradle.ktsdefaultConfigmanifestPlaceholders가 정의되어 있지 않아, ${NATIVE_APP_KEY}가 런타임에 치환되지 않고 그대로 남습니다. 아래 위치에 매핑을 추가해주세요.

• 수정 대상

  • 파일: app/build.gradle.kts
  • 섹션: android { defaultConfig { … } }

• 예시 스니펫 (Kotlin DSL)

android {
    defaultConfig {
        // 기존 buildConfigField는 그대로 유지
        buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["NATIVE_APP_KEY"]}\"")

        // manifestPlaceholders 설정 추가
        manifestPlaceholders += mapOf(
            "NATIVE_APP_KEY" to properties["NATIVE_APP_KEY"] as String
        )
    }
}

위와 같이 설정하면 AndroidManifest.xml

<meta-data
    android:name="com.kakao.sdk.AppKey"
    android:value="${NATIVE_APP_KEY}" />

가 실제 앱 키로 정상 치환됩니다.

🤖 Prompt for AI Agents
In app/src/main/AndroidManifest.xml around lines 19-21 the meta-data value uses
${NATIVE_APP_KEY} but app/build.gradle.kts's android.defaultConfig is missing a
manifestPlaceholders entry so the placeholder isn't replaced at runtime; open
app/build.gradle.kts and inside android { defaultConfig { ... } } add a
manifestPlaceholders mapping that binds "NATIVE_APP_KEY" to your project
property (e.g., properties["NATIVE_APP_KEY"] as String), keeping any existing
buildConfigField entries intact so the manifest placeholder is properly
substituted at build time.

Comment on lines +36 to +46
LaunchedEffect(key1 = Unit) {
//3초 delay
delay(3000L)

// 로그인 화면으로 이동
navController.navigate(CommonRoutes.Login) {
popUpTo(CommonRoutes.Splash) {
inclusive = true
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

네비게이션 타이밍 고려 사항

3초 고정 딜레이는 네트워크 상태나 초기화 시간과 무관하게 실행됩니다. 실제 초기화 작업이 3초보다 오래 걸리면 문제가 발생할 수 있습니다.

초기화 상태를 확인한 후 네비게이션하도록 개선하는 것을 고려하세요:

// ViewModel이나 Repository에서 초기화 상태를 관리하고
// 초기화 완료 시 최소 딜레이 이후 네비게이션
LaunchedEffect(key1 = Unit) {
    val startTime = System.currentTimeMillis()
    // 초기화 작업 수행 또는 상태 확인
    val elapsedTime = System.currentTimeMillis() - startTime
    val remainingDelay = maxOf(0, 3000L - elapsedTime)
    delay(remainingDelay)
    
    navController.navigate(CommonRoutes.Login) {
        popUpTo(CommonRoutes.Splash) {
            inclusive = true
        }
    }
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt around
lines 36 to 46, replace the fixed 3s delay with a check of an
initialization/completion state from the ViewModel (or repository) and ensure
navigation occurs only after initialization is complete and at least the minimum
splash duration has elapsed: record start time, await the ViewModel's
initComplete flag (or suspend until initialization finishes), compute elapsed
time and delay the remaining time if any to reach 3000ms, then call
navController.navigate(CommonRoutes.Login) with the same
popUpTo(CommonRoutes.Splash){ inclusive = true } behavior.

Comment on lines +23 to +24
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true 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.

💡 Verification agent

🧩 Analysis chain

BuildConfig 전역 속성 키 케이스 확인 필요 + 모듈 단위 명시 권장

  • Line 23: android.nonTransitiveRClass=true 설정은 성능/빌드 속도 측면에서 유지 권장합니다.
  • Line 24: android.defaults.buildfeatures.buildconfig=true 키의 케이스가 AGP가 인식하는 형태와 다를 수 있습니다. 일반적으로는 android.defaults.buildFeatures.buildConfig=true 형태를 사용합니다. 케이스가 맞지 않으면 속성이 무시되어 BuildConfig가 생성되지 않을 수 있습니다. 또한 애플리케이션 모듈에서는 기본적으로 BuildConfig가 생성되므로, 필요 시 라이브러리 모듈 대상이거나 확실성을 위해 모듈의 DSL에서 명시하는 것을 권장합니다.

제안(diff):

-android.defaults.buildfeatures.buildconfig=true
+android.defaults.buildFeatures.buildConfig=true

추가 제안(다른 파일에 적용 — app/build.gradle.kts 등 모듈 DSL에 명시):

android {
  buildFeatures {
    buildConfig = true
  }
}

정확한 Gradle 속성 키를 확인하기 위해 웹 검색을 요청드립니다(AGP 8+ 기준):


🌐 Web query:

What is the correct gradle.properties key to enable BuildConfig generation by default for Android Gradle Plugin 8+? Is it `android.defaults.buildFeatures.buildConfig=true`?

💡 Result:

No. For AGP 8+, the global gradle.properties key is android.defaults.buildfeatures.buildconfig=true (all lowercase, no dots between words like “buildFeatures” or “buildConfig”). However, that property is now deprecated and the default for BuildConfig is false in AGP 8, with removal planned in AGP 9.[1][5][10]

What to use instead:

  • Prefer enabling per-module in the module’s build.gradle(.kts):
    • Groovy:
      android { buildFeatures { buildConfig = true } }[4][5]
    • Kotlin DSL:
      android { buildFeatures { buildConfig = true } }[5]

Notes:

  • The older API reference pages show the global property name exactly as android.defaults.buildfeatures.buildconfig=true, but those defaults (true) are outdated; AGP 8 changed the default to false for performance.[2][10]
  • Android Studio will warn that the global gradle.properties flag is deprecated and suggest migrating BuildConfig usage into build.gradle files.[1][9]

Citations:


Deprecated된 BuildConfig 전역 속성 제거 및 모듈 단위 설정 마이그레이션 권장

  • gradle.properties

    • android.nonTransitiveRClass=true – 성능·빌드 속도 최적화 측면에서 유지 권장
    • android.defaults.buildfeatures.buildconfig=true – AGP 8+에서 올바른 all-lowercase 키이나 더 이상 권장되지 않고 AGP 9에서 제거 예정이므로 전역 설정 대신 모듈별 DSL로 전환하세요.
  • 모듈별 build.gradle(.kts)에 아래 설정 추가:

    android {
      buildFeatures {
        buildConfig = true
      }
    }

제안(diff):

- android.defaults.buildfeatures.buildconfig=true   // AGP 8+에서는 deprecated, AGP 9 제거 예정
🤖 Prompt for AI Agents
In gradle.properties around lines 23-24, remove the deprecated global property
"android.defaults.buildfeatures.buildconfig=true" and keep
"android.nonTransitiveRClass=true"; then migrate the BuildConfig setting into
each module's build.gradle(.kts) by adding the buildFeatures.buildConfig = true
entry inside the android { } DSL for each module that needs BuildConfig
generated. Ensure you update all module build files accordingly and delete the
global buildconfig key from gradle.properties.

retrofit = "3.0.0"
retrofitKotlinSerializationConverter = "1.0.0"
androidxComposeNavigation = "2.8.2"
lifecycleRuntimeCompose = "2.10.0-alpha01"
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

lifecycle-runtime-compose를 알파 버전에 고정하는 것은 버전 호환성 리스크가 큽니다. 안정 버전으로 정렬하세요.

현재 lifecycle-runtime-compose를 2.10.0-alpha01로 추가했고, 다른 라이프사이클 아티팩트는 2.8.7/2.9.1을 사용 중입니다. 알파(2.10.x)와 안정(2.8.x/2.9.x)을 혼용하면 미묘한 런타임/바이너리 호환성 문제가 발생할 수 있습니다. viewmodel-compose(2.9.1)나 lifecycle-runtime-ktx(2.8.7)와 같은 계열의 안정 버전으로 정렬하는 것을 권장합니다.

최신 안정 버전을 확인해 주세요. 아래 질의를 웹 검색으로 실행해 주시면 제가 버전 정렬을 제안드리겠습니다.


🌐 Web query:

What is the latest stable version of androidx.lifecycle:lifecycle-runtime-compose?

💡 Result:

The latest stable version is 2.9.2.[1]

  • MavenCentral/MvnRepository lists 2.9.2 as the most recent stable release of androidx.lifecycle:lifecycle-runtime-compose (dated Jul 16, 2025).[1]
  • JetBrains’ mirrored multiplatform coordinates also show 2.9.1 as a recent stable, but 2.9.2 is newer per the official artifact listing.[3][7]

Citations:


lifecycleRuntimeCompose 버전 안정화 필요: 2.10.0-alpha01 → 2.9.2로 변경

최신 안정 릴리즈(2.9.2)[1] 사용을 권장드립니다. 알파(2.10.x)와 안정(2.8.x/2.9.x)를 혼용할 경우 런타임/바이너리 호환성 문제가 발생할 수 있습니다.

  • 파일: gradle/libs.versions.toml
    • 변경 전: lifecycleRuntimeCompose = "2.10.0-alpha01"
    • 변경 후: lifecycleRuntimeCompose = "2.9.2"

[1] https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-runtime-compose

🤖 Prompt for AI Agents
In gradle/libs.versions.toml around line 28, the lifecycleRuntimeCompose
dependency is pinned to an alpha release ("2.10.0-alpha01"); change it to the
stable release "2.9.2" to avoid runtime/binary compatibility issues by replacing
the version string accordingly so the file reads lifecycleRuntimeCompose =
"2.9.2".

@Nico1eKim Nico1eKim merged commit 0082859 into THIP-TextHip:develop Aug 18, 2025
1 check was pending
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: 4

🔭 Outside diff range comments (3)
app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (3)

62-67: 단발 조회(getTempToken)에도 예외 복구 로직을 동일하게 적용하세요.

first() 이전의 스트림에 catch를 붙여 IO 에러로 인한 크래시를 방지하세요.

적용 diff:

-    suspend fun getTempToken(): String? {
-        return context.dataStore.data.map { prefs ->
-            prefs[TEMP_TOKEN_KEY]
-        }.first() // Flow에서 첫 번째 값을 한번만 읽어옴
-    }
+    suspend fun getTempToken(): String? {
+        return context.dataStore.data
+            .catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
+            .map { prefs -> prefs[TEMP_TOKEN_KEY] }
+            .first()
+    }

78-82: getAccessToken도 동일하게 예외 복구를 적용하고, 반환 타입 표기를 import된 Flow로 일관화하세요.

현재 kotlinx.coroutines.flow.Flow로 FQCN을 사용하고 있습니다. 상단에 Flow import가 있으므로 일관되게 Flow<String?>로 표기하세요. 또한 catch로 IO 예외를 복구하세요.

적용 diff:

-    fun getAccessToken(): kotlinx.coroutines.flow.Flow<String?> {
-        return context.dataStore.data.map { prefs ->
-            prefs[ACCESS_TOKEN_KEY]
-        }
-    }
+    fun getAccessToken(): Flow<String?> {
+        return context.dataStore.data
+            .catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
+            .map { prefs -> prefs[ACCESS_TOKEN_KEY] }
+    }

48-53: 중복 companion object 제거 안내

위 코멘트 적용 시(상단 companion으로 키 통합), 본 블록은 삭제해야 합니다. 그대로 두면 컴파일 에러가 납니다.

♻️ Duplicate comments (1)
app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (1)

36-41: DataStore 스트림 예외 처리(catch) 누락 — IOException 시 크래시 방지 로직 추가 권장

이전에 동일 지적이 있었습니다. dataStore.data는 IO 시 예외를 던질 수 있습니다. catchIOException을 복구하고 그 외 예외는 재던지세요.

적용 diff:

-    fun getToken(): Flow<String?> {
-        return context.dataStore.data.map { prefs ->
-            prefs[APP_TOKEN_KEY]
-        }
-    }
+    fun getToken(): Flow<String?> {
+        return context.dataStore.data
+            .catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
+            .map { prefs -> prefs[APP_TOKEN_KEY] }
+    }

선택 범위 외 필요한 import:

import java.io.IOException
import kotlinx.coroutines.flow.catch
import androidx.datastore.preferences.core.emptyPreferences
🧹 Nitpick comments (5)
app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (2)

10-17: 하위 타입의 @serializable 중복 표기는 선택 사항입니다

sealed base(CommonRoutes)가 @Serializable이면 직계 하위 타입은 별도 표기 없이도 직렬화 지원이 됩니다. 중복 표기를 줄여 간결하게 갈 수 있어요. 변경 시 아래처럼 정리 가능합니다(선택 사항).

-    @Serializable
     data object Splash : CommonRoutes()

-    @Serializable
     data object Login : CommonRoutes()

-    @Serializable
     data object Signup : CommonRoutes()

10-17: 릴리즈 빌드 난독화 대비 ProGuard/R8 룰 추가 권장

현재 확인 결과:

  • app/proguard-rules.pro: 경로 타입 보존(keepnames) 관련 룰 미등록
  • app/build.gradle.kts: isMinifyEnabled = false (난독화 비활성화)

▶️ 향후 릴리즈 빌드에서 난독화를 활성화(isMinifyEnabled = true)할 경우, @Serializable 데이터 객체와 CommonRoutes 타입 이름(FQCN)이 보존되도록 아래 룰을 추가하시길 권장합니다.

# Kotlinx Serialization/Typed Navigation 안전장치
-keepattributes *Annotation*, InnerClasses, EnclosingMethod
-keep class kotlinx.serialization.** { *; }
-keepclassmembers class **$serializer { *; }
-keep @kotlinx.serialization.Serializable class ** { *; }

# 라우트 타입 이름 보존 (패키지 경로는 필요에 맞게 조정)
-keepnames class com.texthip.thip.ui.navigator.routes.** { *; }
app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (2)

10-10: 추가 import 정리 제안

예외 복구를 적용하려면 아래 import가 필요합니다. 또한 FQCN 대신 이미 import된 Flow 타입을 일관되게 사용하세요.

선택 범위 외 필요한 import:

import java.io.IOException
import kotlinx.coroutines.flow.catch
import androidx.datastore.preferences.core.emptyPreferences

1-90: 보안/설계 권고: 토큰은 암호화 저장 고려

Access/Refresh Token은 민감 정보입니다. Preferences DataStore는 기본적으로 평문 저장이므로, 다음 중 하나를 고려하세요.

  • Jetpack Security Crypto(EncryptedSharedPreferences)로 토큰을 암호화 저장
  • Proto DataStore + AES/GCM 변환기(Transform) 적용
  • 최소한 파일 시스템 접근 위험을 낮추기 위한 OS 레벨 보호 + 앱 백업 제외 설정

또한 메서드 네이밍을 명확히 해 혼동을 줄이세요:

  • getTokengetAppToken (기존 레거시 토큰이라면 @deprecated로 명시)
  • 사용되는 토큰 타입과 라우팅(신규/기존 회원)에 대한 KDoc 주석을 추가해 의도를 문서화

필요 시, 위 암호화/주석/네이밍 일괄 패치를 제안 드릴 수 있습니다.

app/src/main/java/com/texthip/thip/ThipApplication.kt (1)

3-7: 초기화 실패 시 안전한 처리와 로깅 정비, 빈 키 방어 추가 제안

try-catch 적용은 적절합니다. 다만 다음 3가지를 보완하면 현장 안정성이 높아집니다.

  • printStackTrace 대신 Log.e(또는 Crashlytics)로 에러를 기록
  • BuildConfig.NATIVE_APP_KEY가 빈 문자열/누락인 경우 조기 반환(불필요한 예외 방지)
  • Application을 전달할 때 applicationContext를 명시(의도 명확화)

아래처럼 변경을 제안드립니다.

@@
-import com.kakao.sdk.common.KakaoSdk
+import com.kakao.sdk.common.KakaoSdk
+import android.util.Log
@@
-        // 카카오 SDK 초기화
-        try {
-            KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY)
-        }catch (e: Exception){
-            e.printStackTrace()
-        }
+        // 카카오 SDK 초기화
+        if (BuildConfig.NATIVE_APP_KEY.isBlank()) {
+            Log.e("ThipApplication", "NATIVE_APP_KEY가 비어 있어 Kakao SDK 초기화를 건너뜁니다.")
+            return
+        }
+        try {
+            KakaoSdk.init(applicationContext, BuildConfig.NATIVE_APP_KEY)
+        } catch (e: Exception) {
+            Log.e("ThipApplication", "Kakao SDK 초기화 실패: ${e.message}", e)
+        }

추가로, 로그인 흐름에서 SDK 준비 상태를 확인할 수 있도록 플래그를 노출하는 것도 권장합니다(선택). 클래스 내부에 다음을 두고, 성공/실패 지점에서 값을 설정하세요.

companion object {
    @Volatile var isKakaoSdkInitialized: Boolean = false
    private const val TAG = "ThipApplication"
}

그리고 위 diff에서 Log 태그 문자열 대신 TAG를 사용하면 더 일관됩니다.

필요하시면 위 변경을 반영한 전체 파일 패치를 준비해 드릴게요.

Also applies to: 14-20

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d8310aa and c5ec891.

📒 Files selected for processing (10)
  • app/build.gradle.kts (3 hunks)
  • app/src/main/AndroidManifest.xml (2 hunks)
  • app/src/main/java/com/texthip/thip/MainActivity.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ThipApplication.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/signin/viewmodel/KakaoLoginViewModel.kt (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt
  • app/build.gradle.kts
  • app/src/main/java/com/texthip/thip/ui/signin/viewmodel/KakaoLoginViewModel.kt
  • gradle/libs.versions.toml
  • app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/texthip/thip/MainActivity.kt
🔇 Additional comments (5)
app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (2)

10-17: 타입드 네비게이션 라우트 추가 좋습니다

Kotlinx Serialization 기반의 typed destination 패턴에 맞춰 Splash/Login/Signup을 @Serializable data object로 확장한 점이 적절합니다. 기존 CommonRoutes 체계와도 일관성이 있습니다.


21-21: 말미 개행 추가 변경 사항은 무해합니다

파일 말미 개행 추가/정리는 기능에 영향 없습니다. 현재 상태 유지해도 좋습니다.

app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (2)

29-34: LGTM — 토큰 저장 로직 적절합니다.

edit {} 사용과 키 접근 모두 정상입니다.


84-89: LGTM — 전체 토큰 삭제(clearTokens) 구현 적절합니다.

prefs.clear()로 전체 키 삭제 동작은 의도에 부합합니다. 위의 DataStore 단일화가 선행되어야 의미가 일관됩니다.

app/src/main/java/com/texthip/thip/ThipApplication.kt (1)

11-12: 초기화 위치와 super 호출 적절합니다

Application.onCreate에서 SDK를 초기화하고 super.onCreate()를 선행 호출한 점, @HiltAndroidApp 사용 흐름 모두 적합합니다.

Comment on lines +22 to +23
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Context.dataStore를 클래스 내부에서 다시 정의하면 DataStore 인스턴스가 이중으로 생성될 수 있습니다. 하나로 통일하세요.

동일 파일에

  • 파일 상단(top-level) Context.dataStore (name = "thip_tokens")
  • 클래스 내부 멤버 확장 Context.dataStore (name = "thip_auth_tokens")

가 동시에 존재합니다. 호출 위치에 따라 서로 다른 파일명으로 분리된 두 개의 DataStore를 사용할 위험이 있습니다. 토큰 저장/조회가 서로 다른 저장소를 바라보면 데이터 유실·불일치가 발생합니다.

권장: 클래스 내부 정의를 제거하고, top-level 하나만 남기되 파일명을 원하는 최종값(예: "thip_auth_tokens")으로 맞추세요.

적용 diff(선택 범위 내 제거):

-    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")

선택 범위 밖(참고): top-level 정의를 다음과 같이 통일하세요.

// line 16 제안
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
22-23, there are two Context.dataStore declarations (a top-level one named
"thip_tokens" and a class-internal one named "thip_auth_tokens") which can
create two separate DataStore instances; remove the class-internal
Context.dataStore declaration so only the top-level extension remains, and
ensure the top-level preferencesDataStore uses the final filename
"thip_auth_tokens" (update its name if needed) so all token reads/writes use the
single unified DataStore.

Comment on lines +24 to +27
companion object {
//토큰저장에 사용되는 키
private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

companion object가 중복 선언되어 컴파일 오류가 납니다. 키 정의를 하나의 companion으로 통합하세요.

현재 클래스 아래쪽(라인 49-53)에 또 다른 companion object가 존재합니다. Kotlin은 클래스 당 하나의 companion만 허용합니다. 또한 키가 두 곳으로 분산되면 유지보수성이 떨어집니다.

한 곳으로 합치고, 아래쪽의 중복 companion은 제거하세요.

적용 diff(상단 companion에 모든 키 통합, 하단 중복 companion 제거는 다음 코멘트 설명 참고):

-    companion object {
-        //토큰저장에 사용되는 키
-        private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
-    }
+    companion object {
+        // Preferences Keys
+        private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
+        private val TEMP_TOKEN_KEY = stringPreferencesKey("temp_token")
+        private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
+        private val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token")
+    }

선택 범위 밖(설명): 라인 49-53의 하단 companion object 블록은 통째로 삭제하세요.

📝 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
companion object {
//토큰저장에 사용되는 키
private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
}
companion object {
// Preferences Keys
private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
private val TEMP_TOKEN_KEY = stringPreferencesKey("temp_token")
private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
private val REFRESH_TOKEN_KEY= stringPreferencesKey("refresh_token")
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
24-27 and 49-53, there are two companion object declarations causing a compile
error; merge the key definitions into the top companion by moving any keys from
the bottom companion into the existing companion object at lines ~24-27 (e.g.,
consolidate stringPreferencesKey("app_token") and any other keys into that
single companion) and then delete the entire lower companion object block (lines
~49-53) so the class contains only one companion with all token-related keys.

Comment on lines +43 to +46
//저장된 토큰 삭제 (로그아웃 시?)
suspend fun deleteToken() {
context.dataStore.edit { prefs ->
prefs.remove(APP_TOKEN_KEY)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

중괄호 누락으로 컴파일 오류 발생 — deleteToken 블록을 닫아주세요.

edit {} 람다와 함수의 닫는 중괄호가 없습니다.

적용 diff:

     suspend fun deleteToken() {
         context.dataStore.edit { prefs ->
             prefs.remove(APP_TOKEN_KEY)
+        }
+    }

추가 제안: 본 메서드는 APP_TOKEN만 지우고, 아래의 clearTokens()는 전체 삭제를 수행합니다. 로그아웃 정책에 따라 둘 중 하나로 일원화하거나, 메서드명을 deleteAppToken() 등으로 명확히 구분하는 것을 권장합니다.

📝 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
//저장된 토큰 삭제 (로그아웃 시?)
suspend fun deleteToken() {
context.dataStore.edit { prefs ->
prefs.remove(APP_TOKEN_KEY)
//저장된 토큰 삭제 (로그아웃 시?)
suspend fun deleteToken() {
context.dataStore.edit { prefs ->
prefs.remove(APP_TOKEN_KEY)
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
43 to 46, the deleteToken() function is missing closing braces for the
dataStore.edit { ... } lambda and the function itself, causing a compile error;
add the two missing closing braces to properly close the edit lambda and the
function, and (optionally) rename the method to deleteAppToken() or consolidate
with clearTokens() depending on logout semantics to avoid ambiguity.


@HiltAndroidApp
class ThipApplication : Application() No newline at end of file
class ThipApplication : Application(){
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

AndroidManifest에 Application 등록 및 Kakao 리디렉션 구성 확인 권장

@HiltAndroidApp를 사용하는 Application은 매니페스트 application android:name에 반드시 등록되어야 합니다. 또한 카카오 로그인 리디렉션을 위해 com.kakao.sdk.AppKey 메타데이터와 AuthCodeHandlerActivity 등록이 필요합니다. 로컬 환경/브랜치에 따라 누락되지 않았는지 한번 점검 부탁드립니다.

다음 스크립트로 매니페스트/빌드 설정을 빠르게 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Application 클래스 등록 확인"
rg -n -C2 -P '<application[^>]*android:name\s*=\s*"(?:\.|com\.texthip\.thip\.)ThipApplication"' -g 'app/**/AndroidManifest.xml' || echo "⚠️ AndroidManifest에 ThipApplication 등록이 보이지 않습니다."

echo
echo "2) Kakao AppKey 메타데이터 확인"
rg -n -C2 -P '<meta-data[^>]*name="com\.kakao\.sdk\.AppKey"[^>]*/>' -g 'app/**/AndroidManifest.xml' || echo "⚠️ com.kakao.sdk.AppKey 메타데이터가 보이지 않습니다."

echo
echo "3) Kakao AuthCodeHandlerActivity 등록 확인"
rg -n -C2 -P 'com\.kakao\.sdk\.auth\.AuthCodeHandlerActivity' -g 'app/**/AndroidManifest.xml' || echo "⚠️ AuthCodeHandlerActivity 등록이 보이지 않습니다."

echo
echo "4) BuildConfig NATIVE_APP_KEY 정의 확인"
rg -n -C2 -P 'buildConfigField\([^)]*"String"\s*,\s*"NATIVE_APP_KEY"' -g 'app/**/build.gradle*' || echo "⚠️ build.gradle에 NATIVE_APP_KEY buildConfigField가 보이지 않습니다."

Length of output: 1692


AndroidManifest에 ThipApplication 및 Kakao AppKey 메타데이터 등록 필요

확인 결과, 아래 항목이 누락되어 있어 카카오 로그인 리디렉션이 정상 동작하지 않습니다. 적용 부탁드립니다.

• app/src/main/AndroidManifest.xml

  • <application> 태그에
    android:name=".ThipApplication"
    또는
    android:name="com.texthip.thip.ThipApplication"
    속성이 없습니다.
    • app/src/main/AndroidManifest.xml
  • <application> 내부에
    <meta-data
        android:name="com.kakao.sdk.AppKey"
        android:value="${NATIVE_APP_KEY}" />
    메타데이터 항목이 없습니다.
    • app/src/main/AndroidManifest.xml
  • AuthCodeHandlerActivity 등록은 올바르게 되어 있습니다.
    • app/build.gradle.kts
  • buildConfigField("String", "NATIVE_APP_KEY", …) 설정이 올바르게 되어 있습니다.

위 두 가지를 추가한 뒤 카카오 SDK 리디렉션이 정상 작동하는지 재확인 부탁드립니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ThipApplication.kt around line 10,
add/update app/src/main/AndroidManifest.xml: set the <application> android:name
attribute to ".ThipApplication" or the full "com.texthip.thip.ThipApplication"
value, and inside <application> add the Kakao SDK meta-data element with
android:name="com.kakao.sdk.AppKey" and android:value="${NATIVE_APP_KEY}";
ensure app/build.gradle.kts already defines buildConfigField("String",
"NATIVE_APP_KEY", …) and then rebuild and verify Kakao login redirection works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API] 카카오 소셜 로그인 API 연동[(THIP2025-201)]

2 participants