[Feat] 카카오 인증 연결#122
Conversation
📝 WalkthroughWalkthrough카카오 소셜 로그인 통합을 위해 SDK 의존성·매니페스트·빌드 설정을 추가하고, 인증 API/DTO, 도메인 모델, 리포지토리 구현, 매퍼, 뷰모델, 로그인 UI/매니저 및 내비게이션 흐름을 추가·수정합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant 사용자 as User
participant UI as LoginScreen
participant Manager as KakaoLoginManager
participant KakaoSDK as Kakao SDK
participant VM as LoginViewModel
participant Repo as DefaultAuthRepository
participant API as AuthApi
participant Prefs as PreferencesManager
participant Nav as Navigator
User->>UI: 카카오 로그인 클릭
UI->>Manager: login(context, callback)
Manager->>KakaoSDK: loginWithKakaoTalk() / loginWithKakaoAccount()
KakaoSDK-->>Manager: OAuthToken / Error
Manager-->>UI: Result(OAuthToken)
UI->>VM: socialVerifyWithKakao(provider=KAKAO, accessToken)
VM->>Repo: socialVerify(requestModel)
Repo->>API: socialVerify(SocialVerifyRequestDto)
API-->>Repo: SocialVerifyResponseDto
Repo-->>VM: Result<SocialVerifyResponseModel>
alt isRegistered == false
VM-->>UI: UiState.Success(tempToken)
UI->>Nav: navigateToOnBoarding(tempToken)
else isRegistered == true
VM->>Prefs: save accessToken/refreshToken/userId
VM-->>UI: UiState.Success(null)
UI->>Nav: navigateToHome()
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
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/flint/presentation/onboarding/navigation/OnboardingNavigation.kt (1)
29-34: 라우트 파라미터tempToken이 추출되지 않고 있습니다.
Route.OnboardingProfile(tempToken)으로 네비게이션할 때tempToken을 전달하지만,composable<Route.OnboardingProfile>블록에서 이를 추출하여 사용하고 있지 않습니다.navBackStackEntry.arguments를 통해tempToken을 추출한 후OnboardingProfileRoute에 전달하거나 화면 로직에서 직접 사용해야 합니다.
🤖 Fix all issues with AI agents
In `@app/src/main/AndroidManifest.xml`:
- Line 16: Remove the global android:usesCleartextTraffic="true" setting from
the application manifest and instead either set
android:usesCleartextTraffic="false" or remove it entirely; if you need
cleartext for a dev backend, create a network-security-config XML
(res/xml/network_security_config.xml) and reference it via the application
attribute android:networkSecurityConfig, or apply the cleartext allowance only
to debug builds via manifest placeholders/manifestMerger so production does not
permit cleartext traffic; update AndroidManifest.xml to use the
networkSecurityConfig attribute and add the domain-specific config file to limit
cleartext to specific dev domains.
In `@app/src/main/java/com/flint/presentation/login/data/VerifyStatusData.kt`:
- Around line 3-6: VerifyStatusData currently mixes registration state and
tempToken allowing tempToken to be null when isRegistered is false; change the
model to make states explicit by replacing VerifyStatusData with a sealed class
(e.g., sealed class VerifyStatus { data class Registered(val accessToken:
String, val refreshToken: String, val userId: String): VerifyStatus(); data
class NotRegistered(val tempToken: String): VerifyStatus() }) or, if you prefer
keeping the data class, add validation where SocialVerifyResponseDto is mapped
(the converter/mapping function that builds VerifyStatusData) to throw/log and
reject/handle responses where isRegistered == false but tempToken == null so
onboarding never receives an empty token; update call sites that read
VerifyStatusData (login UI code using data.tempToken ?: "") to consume the new
sealed variants or rely on the validated non-null contract.
In `@app/src/main/java/com/flint/presentation/login/LoginScreen.kt`:
- Around line 49-62: LaunchedEffect keyed on socialVerifyStatus can retrigger
navigation on recomposition; after handling UiState.Success in LoginScreen's
LaunchedEffect (the branch that reads (socialVerifyStatus as
UiState.Success<VerifyStatusData>).data and calls navigateToHome() or
navigateToOnBoarding(...)), call a ViewModel method to clear the status (e.g.,
viewModel.resetVerifyStatus()) so the UiState is set back to UiState.Empty and
navigation won't fire again; implement resetVerifyStatus() in the LoginViewModel
to set _verifyStatus = UiState.Empty and invoke it immediately after each
navigateToHome() / navigateToOnBoarding(...) call.
In `@app/src/main/java/com/flint/presentation/login/LoginViewModel.kt`:
- Around line 43-45: The onFailure block currently only logs the exception with
Timber.e(it) and never updates the UI; modify the onFailure lambda in
LoginViewModel to emit an error state to _verifyStatus (e.g.,
_verifyStatus.emit(UiState.Error(...))) including the exception message or a
user-facing message, verify that UiState.Error exists (create it if missing),
and ensure LoginScreen consumes verifyStatus to show appropriate feedback
(toast/snackbar) for error states; keep the Timber.e call for logging but add
the state emission in the onFailure path.
In `@app/src/main/java/com/flint/presentation/login/manager/KakaoLoginManager.kt`:
- Around line 16-27: 로그인 취소 시 호출자가 무한 대기에 빠지지 않도록
UserApiClient.instance.loginWithKakaoTalk의 취소 분기에서 단순 return 대신 onResult를 호출해
취소를 알리는 실패 결과를 전달하고, when 블록 안에서도 token과 error가 둘 다 null인 경우를 방어적으로 처리해
onResult(Result.failure(...))를 호출하도록 수정하세요; 참고로 수정 위치는
UserApiClient.instance.loginWithKakaoTalk(...) 콜백의
ClientError/ClientErrorCause.Cancelled 처리와 내부 fallback인
UserApiClient.instance.loginWithKakaoAccount(...) 콜백(토큰2/에러2 처리)입니다.
In `@app/src/main/java/com/flint/presentation/splash/SplashScreen.kt`:
- Around line 16-20: 현재 SplashScreen의 LaunchedEffect(…) 블록은 FIXME 테스트 코드로
delay(2000) 후 항상 navigateToLogin()만 호출하여 viewModel과 navigateToHome 파라미터를 무시하고
있습니다; 이를 고치려면 LaunchedEffect 또는 viewModel의 상태(flow/state)를 구독하여 ViewModel의 인증
상태(예: viewModel.isAuthenticated / authState)를 확인한 뒤 인증되어 있으면 navigateToHome()을
호출하고 아니면 navigateToLogin()을 호출하도록 분기 처리하고, 필요한 경우 인증 확인이 끝날 때까지 짧은 스플래시 지연을 유지하되
하드코딩된 테스트 주석(FIXME)과 불필요한 고정 호출을 제거하세요 (참조 심볼: LaunchedEffect, viewModel,
navigateToLogin, navigateToHome).
🧹 Nitpick comments (8)
app/src/main/java/com/flint/presentation/splash/SplashScreen.kt (1)
25-27: 빈 스플래시 화면 구현.
SplashScreen()컴포저블이 비어있어 2초 딜레이 동안 사용자에게 빈 화면이 표시됩니다. 일반적으로 스플래시 화면에서는 앱 로고나 브랜딩 요소를 보여주는 것이 좋습니다.app/build.gradle.kts (1)
46-48: 누락된 속성에 대한 방어적 처리 권장.
kakao.native.app.key가local.properties에 없을 경우,properties["kakao.native.app.key"].toString()은 문자열"null"을 반환하여 런타임에 인증 실패를 유발할 수 있습니다. 빌드 시점에 명확한 오류 메시지로 실패하도록 하는 것이 좋습니다.♻️ 제안된 수정
- val kakaoNativeAppKey = properties["kakao.native.app.key"].toString() + val kakaoNativeAppKey = properties["kakao.native.app.key"]?.toString() + ?: error("kakao.native.app.key is missing in local.properties") buildConfigField("String", "KAKAO_NATIVE_APP_KEY", "\"$kakaoNativeAppKey\"") manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = kakaoNativeAppKeygradle/libs.versions.toml (1)
53-54: 카카오 SDK 버전 업데이트 권장.버전
2.21.0은 유효하지만 최신 버전이 아닙니다. 현재 최신 안정 버전은2.23.2입니다. 더 최신의 안정적인 버전으로 업데이트를 권장합니다. (가용한 최신 버전: 2.23.2, 2.23.1, 2.23.0, 2.22.0 등)app/src/main/java/com/flint/core/navigation/Route.kt (1)
12-15: 네비게이션 라우트에 민감한 토큰 전달 회피 필요
tempToken은 인증 관련 민감한 데이터이므로 네비게이션 라우트 매개변수로 전달하지 않는 것이 좋습니다. 완료 후 토큰을 사용할 때는ViewModel의SavedStateHandle이나 보안 저장소를 통해 전달하는 방식을 고려해 주세요.app/src/main/java/com/flint/core/common/di/RepositoryModule.kt (1)
16-16: 메서드 이름이 바인딩 대상과 일치하지 않습니다.메서드 이름이
bindFlintRepository이지만 실제로는AuthRepository를 바인딩하고 있습니다. 일관성을 위해 메서드 이름을 변경하는 것이 좋습니다.♻️ 제안된 수정
- abstract fun bindFlintRepository(defaultFlintRepository: DefaultAuthRepository): AuthRepository + abstract fun bindAuthRepository(defaultAuthRepository: DefaultAuthRepository): AuthRepositoryapp/src/main/java/com/flint/presentation/login/LoginViewModel.kt (1)
13-19: 사용되지 않는 import 존재
OAuthToken(line 13)과Locale(line 19)는 이 파일에서 사용되지 않습니다. 제거를 권장합니다.-import com.kakao.sdk.auth.model.OAuthToken import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import timber.log.Timber -import java.util.Locale import javax.inject.Injectapp/src/main/java/com/flint/presentation/login/LoginScreen.kt (2)
3-3: 사용되지 않는 import
android.util.Log가 import되어 있지만 실제로는Timber를 사용하고 있습니다. 제거해 주세요.
43-44: KakaoLoginManager 기본 인스턴스 생성
KakaoLoginManager()를 Composable 파라미터의 기본값으로 생성하면 테스트 시 모킹이 어려울 수 있습니다. Hilt를 통한 주입을 고려해 보세요.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
app/build.gradle.ktsapp/src/main/AndroidManifest.xmlapp/src/main/java/com/flint/FlintApplication.ktapp/src/main/java/com/flint/core/common/di/RepositoryModule.ktapp/src/main/java/com/flint/core/common/di/ServiceModule.ktapp/src/main/java/com/flint/core/common/util/Constants.ktapp/src/main/java/com/flint/core/navigation/Route.ktapp/src/main/java/com/flint/data/api/AuthApi.ktapp/src/main/java/com/flint/data/api/FlintApi.ktapp/src/main/java/com/flint/data/dto/request/auth/SignupRequestDto.ktapp/src/main/java/com/flint/data/dto/request/auth/SocialVerifyRequestDto.ktapp/src/main/java/com/flint/data/dto/response/auth/SignupResponseDto.ktapp/src/main/java/com/flint/data/dto/response/auth/SocialVerifyResponseDto.ktapp/src/main/java/com/flint/data/mapper/auth/SignupMapper.ktapp/src/main/java/com/flint/data/mapper/auth/SocialVerifyMapper.ktapp/src/main/java/com/flint/data/repositoryImpl/DefaultAuthRepository.ktapp/src/main/java/com/flint/data/repositoryImpl/DefaultFlintRepository.ktapp/src/main/java/com/flint/domain/model/auth/SignupModel.ktapp/src/main/java/com/flint/domain/model/auth/SocialVerifyModel.ktapp/src/main/java/com/flint/domain/repository/AuthRepository.ktapp/src/main/java/com/flint/domain/repository/FlintRepository.ktapp/src/main/java/com/flint/domain/type/ProviderType.ktapp/src/main/java/com/flint/presentation/login/LoginScreen.ktapp/src/main/java/com/flint/presentation/login/LoginViewModel.ktapp/src/main/java/com/flint/presentation/login/data/VerifyStatusData.ktapp/src/main/java/com/flint/presentation/login/manager/KakaoLoginManager.ktapp/src/main/java/com/flint/presentation/login/navigation/LoginNavigation.ktapp/src/main/java/com/flint/presentation/main/MainNavigator.ktapp/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.ktapp/src/main/java/com/flint/presentation/splash/SplashScreen.ktgradle/libs.versions.tomlsettings.gradle.kts
💤 Files with no reviewable changes (3)
- app/src/main/java/com/flint/data/api/FlintApi.kt
- app/src/main/java/com/flint/data/repositoryImpl/DefaultFlintRepository.kt
- app/src/main/java/com/flint/domain/repository/FlintRepository.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-16T08:47:13.979Z
Learnt from: ckals413
Repo: imflint/Flint-Android PR: 116
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt:97-105
Timestamp: 2026-01-16T08:47:13.979Z
Learning: In OnboardingDoneScreen.kt, the "시작하기" button is intentionally set to FlintButtonState.Disable as a placeholder state until ViewModel implementation is completed.
Applied to files:
app/src/main/java/com/flint/presentation/login/LoginViewModel.ktapp/src/main/java/com/flint/presentation/splash/SplashScreen.ktapp/src/main/java/com/flint/presentation/main/MainNavigator.ktapp/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.ktapp/src/main/java/com/flint/presentation/login/LoginScreen.kt
🧬 Code graph analysis (2)
app/src/main/java/com/flint/presentation/splash/SplashScreen.kt (2)
app/src/main/java/com/flint/presentation/main/MainNavigator.kt (1)
navigateToLogin(90-99)app/src/main/java/com/flint/presentation/login/navigation/LoginNavigation.kt (1)
navigateToLogin(11-13)
app/src/main/java/com/flint/presentation/login/LoginScreen.kt (2)
app/src/main/java/com/flint/presentation/main/MainNavigator.kt (2)
navigateToHome(105-114)navigateToOnBoarding(101-103)app/src/main/java/com/flint/presentation/home/navigation/HomeNavigation.kt (1)
navigateToHome(11-13)
🔇 Additional comments (28)
settings.gradle.kts (1)
19-19: LGTM!카카오 SDK 공식 Maven 저장소가 올바르게 추가되었습니다. 이는 카카오 SDK 의존성 해결에 필요합니다.
gradle/libs.versions.toml (1)
102-103: LGTM!카카오 사용자 SDK 라이브러리가 버전 카탈로그에 올바르게 정의되었습니다.
app/build.gradle.kts (1)
100-101: LGTM!카카오 사용자 SDK 의존성이 올바르게 추가되었습니다.
app/src/main/AndroidManifest.xml (1)
29-40: LGTM!카카오 SDK의
AuthCodeHandlerActivity설정이 올바르게 구성되었습니다. OAuth 콜백을 위한 intent-filter와 scheme 설정이 카카오 SDK 문서에 맞게 작성되었습니다.app/src/main/java/com/flint/FlintApplication.kt (1)
27-29: LGTM! Kakao SDK 초기화 구현이 올바릅니다.
Application.onCreate()에서 Kakao SDK를 초기화하는 것은 표준 패턴이며,BuildConfig를 통해 네이티브 앱 키를 관리하는 것은 권장되는 방식입니다.build.gradle에서
kakao.native.app.key를 local.properties에서 읽어 buildConfigField로 설정하고, manifestPlaceholder로도 주입하는 구성이 정확합니다. 민감한 정보를 local.properties에 저장하고 VCS에서 제외하는 것은 보안 모범 사례입니다.app/src/main/java/com/flint/data/dto/request/auth/SocialVerifyRequestDto.kt (1)
6-12: LGTM!DTO 구조가 적절하며
@Serializable및@SerialName어노테이션이 올바르게 적용되었습니다.app/src/main/java/com/flint/domain/type/ProviderType.kt (1)
3-5: LGTM!향후 다른 소셜 로그인 제공자(GOOGLE, APPLE 등)를 추가할 수 있도록 확장 가능한 enum 구조입니다.
app/src/main/java/com/flint/core/common/util/Constants.kt (1)
3-7: LGTM!DataStore 키를 상수로 중앙 관리하는 것은 좋은 패턴입니다. 인증 관련 토큰 및 사용자 정보 저장에 적합한 구조입니다.
app/src/main/java/com/flint/data/dto/response/auth/SignupResponseDto.kt (1)
6-14: LGTM!회원가입 응답 DTO가 적절하게 구성되었습니다. 모든 필드가 non-null로 선언되어 있으므로, 서버 API 응답이 항상 이 필드들을 반환하는지 확인해 주세요.
app/src/main/java/com/flint/data/dto/request/auth/SignupRequestDto.kt (1)
6-16: LGTM!DTO 구조가 명확하고 kotlinx.serialization 컨벤션을 잘 따르고 있습니다.
@SerialName어노테이션이 프로퍼티명과 동일하지만 명시적으로 선언하여 API 계약을 명확히 한 점이 좋습니다.app/src/main/java/com/flint/presentation/login/manager/KakaoLoginManager.kt (1)
29-37: KakaoAccount 폴백 로직 정상 구현됨KakaoTalk이 사용 불가능한 경우 KakaoAccount로 로그인을 시도하는 폴백 로직이 올바르게 구현되어 있습니다.
app/src/main/java/com/flint/domain/model/auth/SignupModel.kt (1)
3-14: LGTM!도메인 모델이 깔끔하게 정의되어 있습니다. DTO와 분리하여 레이어 간 경계를 명확히 유지하고 있습니다.
app/src/main/java/com/flint/data/mapper/auth/SignupMapper.kt (1)
8-23: LGTM!매퍼 함수들이 확장 함수 패턴을 사용하여 깔끔하게 구현되어 있습니다. 필드 매핑이 정확하고 도메인 ↔ DTO 변환 로직이 명확합니다.
app/src/main/java/com/flint/presentation/login/navigation/LoginNavigation.kt (1)
15-24: 온보딩 네비게이션 계약을 토큰 기반으로 맞춘 점 좋습니다.
LoginRoute에tempToken을 그대로 전달해 흐름이 일관됩니다.app/src/main/java/com/flint/data/api/AuthApi.kt (1)
11-16: AuthApi 엔드포인트 정의가 명확합니다.
signup/socialVerify분리가 직관적입니다.app/src/main/java/com/flint/domain/repository/AuthRepository.kt (1)
8-10: AuthRepository 계약이 명확합니다.
Result기반으로 에러 처리를 일관되게 구성하기 좋습니다.app/src/main/java/com/flint/data/mapper/auth/SocialVerifyMapper.kt (2)
8-12: provider 문자열 규격을 백엔드 계약과 재확인하세요.
provider.name이 API에서 요구하는 값(대/소문자, 별칭 등)과 다르면 인증이 실패할 수 있습니다. 필요 시 명시 매핑을 고려해 주세요.
15-22: 응답 매핑이 단순하고 명확합니다.
필드가 1:1로 연결되어 있어 추적이 쉽습니다.app/src/main/java/com/flint/presentation/main/MainNavigator.kt (2)
13-36: 시작 목적지를 Splash로 둔 변경이 로그인 플로우와 자연스럽습니다.
Route.Splash를startDestination으로 명시해 초기 진입이 명확해졌습니다.
101-102: 온보딩 이동 시 tempToken 전달이 일관적입니다.
토큰을 그대로 전달해 이후 온보딩 화면에서 활용하기 좋습니다.app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt (1)
12-14: 네비게이션 함수 변경이 적절합니다.
tempToken을 매개변수로 받아Route.OnboardingProfile에 전달하는 방식이 잘 구현되어 있습니다.app/src/main/java/com/flint/domain/model/auth/SocialVerifyModel.kt (1)
5-16: 도메인 모델이 적절하게 설계되었습니다.
SocialVerifyRequestModel과SocialVerifyResponseModel이 카카오 인증 플로우에 맞게 잘 정의되어 있습니다.accessToken,refreshToken,tempToken과 같은 민감한 데이터가 포함되어 있으므로, 로깅 시 마스킹 처리가 필요합니다.app/src/main/java/com/flint/data/dto/response/auth/SocialVerifyResponseDto.kt (1)
6-18: DTO가 적절하게 구현되었습니다.
@Serializable및@SerialName어노테이션을 사용하여 API 응답 매핑이 명시적이고 안정적입니다. nullable 필드에 기본값null을 설정하여 누락된 필드도 안전하게 처리됩니다.app/src/main/java/com/flint/core/common/di/ServiceModule.kt (1)
14-16: DI 모듈이 올바르게 업데이트되었습니다.
FlintApi에서AuthApi로의 전환이 적절하게 이루어졌으며, 메서드 이름(provideAuthApi)과 반환 타입이 일치합니다.app/src/main/java/com/flint/presentation/login/LoginViewModel.kt (1)
32-47: 전체적인 인증 로직 구현 확인인증 성공 시 토큰 저장 및 상태 emit 로직이 적절하게 구현되어 있습니다.
isRegistered여부에 따라 분기 처리하는 방식이 명확합니다.app/src/main/java/com/flint/data/repositoryImpl/DefaultAuthRepository.kt (1)
17-25: Repository 구현 LGTM
runCatching을 사용한 에러 처리와 DTO-Model 매핑이 적절하게 구현되어 있습니다.runCatching내부에서 발생하는 예외(예:.data가 null인 경우)도Result.failure로 전파되어 호출부에서 적절히 처리할 수 있습니다.app/src/main/java/com/flint/presentation/login/LoginScreen.kt (2)
56-58: tempToken이 null일 경우 빈 문자열 처리
data.tempToken ?: ""로 빈 문자열을 전달하면 온보딩 화면에서 유효하지 않은 토큰으로 인한 문제가 발생할 수 있습니다. tempToken이 필수값인지 확인이 필요합니다.온보딩 플로우에서 빈 tempToken을 어떻게 처리하는지 확인해 주세요. 만약 필수값이라면 null 체크 후 에러 처리가 필요합니다:
if (data.tempToken.isNullOrEmpty()) { // 에러 처리 또는 사용자 피드백 return@LaunchedEffect } navigateToOnBoarding(data.tempToken)
64-80: Kakao 로그인 플로우 통합 구현 확인Kakao 로그인 버튼 클릭 시
KakaoLoginManager.login→ 성공 시socialVerifyWithKakao호출 흐름이 적절하게 구현되어 있습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| data class VerifyStatusData( | ||
| val isRegistered: Boolean, | ||
| val tempToken: String? | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.kt" | head -20Repository: imflint/Flint-Android
Length of output: 1539
🏁 Script executed:
rg -n "VerifyStatusData" --type=ktRepository: imflint/Flint-Android
Length of output: 91
🏁 Script executed:
rg -n -B 5 -A 10 "isRegistered" --type=kt | head -100Repository: imflint/Flint-Android
Length of output: 91
🏁 Script executed:
rg "VerifyStatusData" -A 5 -B 5Repository: imflint/Flint-Android
Length of output: 6616
🏁 Script executed:
rg "isRegistered.*false" -A 10 -B 5Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
find . -path "*/login/*" -name "*.kt" | grep -E "(LoginScreen|LoginViewModel|Navigation)"Repository: imflint/Flint-Android
Length of output: 274
🏁 Script executed:
rg "navigateToOnBoarding" -A 10 -B 5Repository: imflint/Flint-Android
Length of output: 7093
🏁 Script executed:
rg "tempToken" -A 3 -B 3Repository: imflint/Flint-Android
Length of output: 9994
🏁 Script executed:
find . -path "*/onboarding/*" -name "*.kt" | grep -i "route\|screen\|navigation" | head -10Repository: imflint/Flint-Android
Length of output: 463
🏁 Script executed:
rg "OnboardingProfileRoute\|OnboardingProfileScreen" -A 15 -B 5Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
cat app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt | head -80Repository: imflint/Flint-Android
Length of output: 376
🏁 Script executed:
cat app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.ktRepository: imflint/Flint-Android
Length of output: 1549
🏁 Script executed:
rg "OnboardingProfile" -A 20 -B 5Repository: imflint/Flint-Android
Length of output: 8553
isRegistered가 false일 때 tempToken이 null인 상태 검토 필수.
로그인 화면에서 data.tempToken ?: ""를 사용해 null을 빈 문자열로 처리하고 있지만, 이는 문제를 감싸는 것이지 해결하는 것이 아닙니다. API 응답(SocialVerifyResponseDto)에서 isRegistered=false일 때 tempToken이 null로 반환될 수 있으며, 이 빈 문자열은 온보딩 진행 중 회원가입 API 호출 시 유효하지 않은 토큰이 됩니다.
다음 중 하나를 권장합니다:
- Sealed class 사용:
Registered(accessToken, refreshToken, userId)및NotRegistered(tempToken)로 상태를 명확히 분리 - 또는 null 체크 강화:
isRegistered=false일 때tempToken이 반드시 non-null임을 보장하는 검증 로직 추가
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/login/data/VerifyStatusData.kt`
around lines 3 - 6, VerifyStatusData currently mixes registration state and
tempToken allowing tempToken to be null when isRegistered is false; change the
model to make states explicit by replacing VerifyStatusData with a sealed class
(e.g., sealed class VerifyStatus { data class Registered(val accessToken:
String, val refreshToken: String, val userId: String): VerifyStatus(); data
class NotRegistered(val tempToken: String): VerifyStatus() }) or, if you prefer
keeping the data class, add validation where SocialVerifyResponseDto is mapped
(the converter/mapping function that builds VerifyStatusData) to throw/log and
reject/handle responses where isRegistered == false but tempToken == null so
onboarding never receives an empty token; update call sites that read
VerifyStatusData (login UI code using data.tempToken ?: "") to consume the new
sealed variants or rely on the validated non-null contract.
| LaunchedEffect(socialVerifyStatus) { | ||
| when(socialVerifyStatus) { | ||
| is UiState.Success -> { | ||
| val data = (socialVerifyStatus as UiState.Success<VerifyStatusData>).data | ||
|
|
||
| if (data.isRegistered) { | ||
| navigateToHome() | ||
| } else { | ||
| navigateToOnBoarding(data.tempToken ?: "") | ||
| } | ||
| } | ||
| else -> {} | ||
| } | ||
| } |
There was a problem hiding this comment.
중복 네비게이션 발생 가능성
LaunchedEffect의 key로 socialVerifyStatus를 사용하고 있어, Composable이 recomposition될 때 UiState.Success 상태가 유지된 채로 네비게이션이 다시 트리거될 수 있습니다.
🛠️ 상태 소비 후 리셋 처리 제안
ViewModel에서 네비게이션 후 상태를 리셋하는 방식을 고려해 주세요:
// LoginViewModel.kt에 추가
fun resetVerifyStatus() {
_verifyStatus.value = UiState.Empty
} LaunchedEffect(socialVerifyStatus) {
when(socialVerifyStatus) {
is UiState.Success -> {
val data = (socialVerifyStatus as UiState.Success<VerifyStatusData>).data
if (data.isRegistered) {
navigateToHome()
} else {
navigateToOnBoarding(data.tempToken ?: "")
}
+ viewModel.resetVerifyStatus()
}
else -> {}
}
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/login/LoginScreen.kt` around lines
49 - 62, LaunchedEffect keyed on socialVerifyStatus can retrigger navigation on
recomposition; after handling UiState.Success in LoginScreen's LaunchedEffect
(the branch that reads (socialVerifyStatus as
UiState.Success<VerifyStatusData>).data and calls navigateToHome() or
navigateToOnBoarding(...)), call a ViewModel method to clear the status (e.g.,
viewModel.resetVerifyStatus()) so the UiState is set back to UiState.Empty and
navigation won't fire again; implement resetVerifyStatus() in the LoginViewModel
to set _verifyStatus = UiState.Empty and invoke it immediately after each
navigateToHome() / navigateToOnBoarding(...) call.
There was a problem hiding this comment.
@kimjw2003, 죄송합니다. 제 분석이 잘못되었네요. LaunchedEffect는 키 값이 변경될 때만 재실행되고, 네비게이션 후에는 화면이 컴포지션에서 벗어나기 때문에 제가 우려한 중복 네비게이션 문제는 실제로 발생하지 않을 것 같습니다. 불필요한 지적으로 번거롭게 해드려 죄송합니다.
🧠 Learnings used
Learnt from: ckals413
Repo: imflint/Flint-Android PR: 116
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt:97-105
Timestamp: 2026-01-16T08:47:13.979Z
Learning: In OnboardingDoneScreen.kt, the "시작하기" button is intentionally set to FlintButtonState.Disable as a placeholder state until ViewModel implementation is completed.
| onFailure = { | ||
| Timber.e(it) | ||
| } |
There was a problem hiding this comment.
실패 시 UI 상태가 업데이트되지 않음
onFailure 블록에서 로그만 남기고 _verifyStatus에 에러 상태를 emit하지 않습니다. 이로 인해 사용자는 인증 실패 시 아무런 피드백을 받지 못하고 무한 대기 상태에 빠질 수 있습니다.
🛠️ 에러 상태 emit 추가 제안
onFailure = {
Timber.e(it)
+ _verifyStatus.emit(UiState.Error(it))
}UiState.Error 타입이 존재하는지 확인 후 적용해 주세요. 또한 LoginScreen에서 에러 상태를 처리하여 사용자에게 적절한 피드백(예: 토스트 메시지)을 제공하는 것을 권장합니다.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/login/LoginViewModel.kt` around
lines 43 - 45, The onFailure block currently only logs the exception with
Timber.e(it) and never updates the UI; modify the onFailure lambda in
LoginViewModel to emit an error state to _verifyStatus (e.g.,
_verifyStatus.emit(UiState.Error(...))) including the exception message or a
user-facing message, verify that UiState.Error exists (create it if missing),
and ensure LoginScreen consumes verifyStatus to show appropriate feedback
(toast/snackbar) for error states; keep the Timber.e call for logging but add
the state emission in the onFailure path.
There was a problem hiding this comment.
@kimjw2003, 천만에요! 에러 상태 처리를 추가하시면 사용자 경험이 더 좋아질 거예요. 궁금한 점 있으시면 언제든지 말씀해 주세요. 😊
🧠 Learnings used
Learnt from: ckals413
Repo: imflint/Flint-Android PR: 116
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt:97-105
Timestamp: 2026-01-16T08:47:13.979Z
Learning: In OnboardingDoneScreen.kt, the "시작하기" button is intentionally set to FlintButtonState.Disable as a placeholder state until ViewModel implementation is completed.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the UiState implementation to see what states are available
fd -e kt UiState.kt --exec cat {}
Length of output: 268
🏁 Script executed:
#!/bin/bash
# Check LoginScreen to see how it currently handles verifyStatus states
rg -A 10 -B 2 "verifyStatus" app/src/main/java/com/flint/presentation/login/LoginScreen.kt
Length of output: 495
태스크: 로그인 실패 시 UI 상태 업데이트 구현
- LoginViewModel의 onFailure 블록에서 UiState.Failure 상태 emit 추가
- LoginScreen에서 Failure 상태 처리 및 Toast 메시지 표시
🧠 Learnings used
Learnt from: ckals413
Repo: imflint/Flint-Android PR: 116
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt:97-105
Timestamp: 2026-01-16T08:47:13.979Z
Learning: In OnboardingDoneScreen.kt, the "시작하기" button is intentionally set to FlintButtonState.Disable as a placeholder state until ViewModel implementation is completed.
💡 For code changes across multiple files, use @coderabbitai plan in an issue comment
| UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> | ||
| if (error is ClientError && error.reason == ClientErrorCause.Cancelled) return@loginWithKakaoTalk | ||
| when { | ||
| token != null -> onResult(Result.success(token)) | ||
| error != null -> UserApiClient.instance.loginWithKakaoAccount(context) { token2, error2 -> | ||
| if (token2 != null) { | ||
| onResult(Result.success(token2)) | ||
| } else { | ||
| onResult(Result.failure(error2 ?: Exception("Unknown error"))) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
취소 시 콜백 누락 - 호출자가 무한 대기 상태에 빠질 수 있음
Line 17에서 사용자가 로그인을 취소했을 때 onResult 콜백 없이 return하면 호출자가 결과를 받지 못해 무한 대기 상태에 빠질 수 있습니다.
또한, when 블록에서 token과 error가 모두 null인 경우를 처리하지 않습니다.
🐛 취소 처리 및 방어적 코딩 수정 제안
UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
- if (error is ClientError && error.reason == ClientErrorCause.Cancelled) return@loginWithKakaoTalk
+ if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
+ onResult(Result.failure(error))
+ return@loginWithKakaoTalk
+ }
when {
token != null -> onResult(Result.success(token))
error != null -> UserApiClient.instance.loginWithKakaoAccount(context) { token2, error2 ->
if (token2 != null) {
onResult(Result.success(token2))
} else {
onResult(Result.failure(error2 ?: Exception("Unknown error")))
}
}
+ else -> onResult(Result.failure(Exception("Unknown error: token and error are both null")))
}
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/login/manager/KakaoLoginManager.kt`
around lines 16 - 27, 로그인 취소 시 호출자가 무한 대기에 빠지지 않도록
UserApiClient.instance.loginWithKakaoTalk의 취소 분기에서 단순 return 대신 onResult를 호출해
취소를 알리는 실패 결과를 전달하고, when 블록 안에서도 token과 error가 둘 다 null인 경우를 방어적으로 처리해
onResult(Result.failure(...))를 호출하도록 수정하세요; 참고로 수정 위치는
UserApiClient.instance.loginWithKakaoTalk(...) 콜백의
ClientError/ClientErrorCause.Cancelled 처리와 내부 fallback인
UserApiClient.instance.loginWithKakaoAccount(...) 콜백(토큰2/에러2 처리)입니다.
| LaunchedEffect(Unit) { | ||
| viewModel.sampleSaveData() | ||
| // FIXME navigation test @ckals413 | ||
| delay(2000) | ||
| navigateToLogin() | ||
| } |
There was a problem hiding this comment.
FIXME 주석이 있는 테스트/플레이스홀더 코드가 머지 대상에 포함되어 있습니다.
FIXME 주석이 이 코드가 테스트 목적임을 나타내고 있습니다. 현재 구현은 인증 상태와 관계없이 항상 로그인 화면으로 이동하며, viewModel과 navigateToHome 파라미터가 전혀 사용되지 않고 있습니다.
프로덕션 코드에서는 ViewModel을 통해 인증 상태(토큰 유무 등)를 확인한 후 navigateToLogin() 또는 navigateToHome()으로 분기해야 합니다.
🛠️ 제안하는 구현 방향
LaunchedEffect(Unit) {
- // FIXME navigation test `@ckals413`
- delay(2000)
- navigateToLogin()
+ delay(2000)
+ val isLoggedIn = viewModel.checkAuthState()
+ if (isLoggedIn) {
+ navigateToHome()
+ } else {
+ navigateToLogin()
+ }
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/splash/SplashScreen.kt` around lines
16 - 20, 현재 SplashScreen의 LaunchedEffect(…) 블록은 FIXME 테스트 코드로 delay(2000) 후 항상
navigateToLogin()만 호출하여 viewModel과 navigateToHome 파라미터를 무시하고 있습니다; 이를 고치려면
LaunchedEffect 또는 viewModel의 상태(flow/state)를 구독하여 ViewModel의 인증 상태(예:
viewModel.isAuthenticated / authState)를 확인한 뒤 인증되어 있으면 navigateToHome()을 호출하고
아니면 navigateToLogin()을 호출하도록 분기 처리하고, 필요한 경우 인증 확인이 끝날 때까지 짧은 스플래시 지연을 유지하되
하드코딩된 테스트 주석(FIXME)과 불필요한 고정 호출을 제거하세요 (참조 심볼: LaunchedEffect, viewModel,
navigateToLogin, navigateToHome).
There was a problem hiding this comment.
@kimjw2003, 알겠습니다. 의도적으로 작성하신 플레이스홀더 코드에 대해서는 리뷰 코멘트를 남기지 않도록 하겠습니다.
✏️ Learnings added
Learnt from: kimjw2003
Repo: imflint/Flint-Android PR: 122
File: app/src/main/java/com/flint/presentation/splash/SplashScreen.kt:16-20
Timestamp: 2026-01-16T15:56:53.763Z
Learning: In the imflint/Flint-Android repository, do not flag or comment on FIXME comments, placeholder code, or test code with TODO/FIXME markers. The team prefers to handle these intentional placeholders on their own timeline without review comments.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: ckals413
Repo: imflint/Flint-Android PR: 116
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt:97-105
Timestamp: 2026-01-16T08:47:13.979Z
Learning: In OnboardingDoneScreen.kt, the "시작하기" button is intentionally set to FlintButtonState.Disable as a placeholder state until ViewModel implementation is completed.
…gin-api # Conflicts: # app/src/main/java/com/flint/presentation/splash/SplashScreen.kt
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/presentation/login/LoginScreen.kt`:
- Around line 73-75: The onFailure { error -> Timber.e(error) } block in
LoginScreen.kt only logs Kakao SDK login errors and does not surface feedback to
the user; update that failure handler to map SDK errors into the UI failure flow
(e.g., call into the ViewModel to emit a UiState.Failure or a dedicated
viewModel.onKakaoLoginFailure(error) method) so the UI can display a
Toast/Snackbar/error state; ensure you pass a user-friendly message
(error.message ?: "Login failed or cancelled") and reuse the existing
UiState.Failure handling used by socialVerifyWithKakao for consistent UX.
🧹 Nitpick comments (1)
app/src/main/java/com/flint/presentation/login/LoginViewModel.kt (1)
13-19: 사용되지 않는 import 존재
OAuthToken과Localeimport가 코드에서 사용되지 않습니다.♻️ 미사용 import 제거 제안
-import com.kakao.sdk.auth.model.OAuthToken import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import timber.log.Timber -import java.util.Locale import javax.inject.Inject
| }.onFailure { error -> | ||
| Timber.e(error) | ||
| } |
There was a problem hiding this comment.
카카오 SDK 로그인 실패 시 사용자 피드백 누락
카카오 SDK 자체의 로그인 실패(예: 사용자 취소, 네트워크 오류 등)는 로그만 남기고 사용자에게 피드백을 제공하지 않습니다. ViewModel의 socialVerifyWithKakao 실패(서버 인증 실패)는 UiState.Failure로 처리되지만, SDK 레벨 실패는 별도로 처리가 필요합니다.
🛠️ SDK 실패 시 피드백 추가 제안
}.onFailure { error ->
Timber.e(error)
+ // 사용자에게 로그인 실패 피드백 제공 (예: Toast 또는 Snackbar)
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/login/LoginScreen.kt` around lines
73 - 75, The onFailure { error -> Timber.e(error) } block in LoginScreen.kt only
logs Kakao SDK login errors and does not surface feedback to the user; update
that failure handler to map SDK errors into the UI failure flow (e.g., call into
the ViewModel to emit a UiState.Failure or a dedicated
viewModel.onKakaoLoginFailure(error) method) so the UI can display a
Toast/Snackbar/error state; ensure you pass a user-friendly message
(error.message ?: "Login failed or cancelled") and reuse the existing
UiState.Failure handling used by socialVerifyWithKakao for consistent UX.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/core/navigation/Route.kt`:
- Around line 12-15: OnboardingProfile data class currently carries tempToken
but screens/routes (OnboardingProfileRoute, OnboardingProfileScreen) do not
extract or use it and the `@Serializable` token can leak; change flow so Nav host
reads the token via
navBackStackEntry.toRoute<Route.OnboardingProfile>().tempToken and passes it
into OnboardingViewModel (or have the ViewModel read it on init), remove
tempToken from any serializable navigation state to avoid serialization leakage,
store token in OnboardingViewModel state, and ensure the ViewModel supplies the
token when building SignupRequestModel for the final signup call.
| @Serializable | ||
| data object OnboardingProfile : Route | ||
| data class OnboardingProfile( | ||
| val tempToken: String | ||
| ) : Route |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for tempToken usage and any logging that might expose it
rg -n 'tempToken' -A 3 -B 3 --type=kotlin --type=javaRepository: imflint/Flint-Android
Length of output: 10309
🏁 Script executed:
#!/bin/bash
# Check for logging statements that might expose sensitive tokens
rg -n -i '(log\.|timber\.|println|print)' --type=kotlin --type=java | rg -i 'token' | head -20Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Look for OnboardingProfile usage in navigation
rg -n 'OnboardingProfile' --type=kotlin --type=java -A 2 -B 2Repository: imflint/Flint-Android
Length of output: 4631
🏁 Script executed:
#!/bin/bash
# Check for token expiration or validity checks
rg -n -i '(expir|ttl|timeout|valid.*token)' --type=kotlin --type=java | head -20Repository: imflint/Flint-Android
Length of output: 2469
🏁 Script executed:
#!/bin/bash
# Find the Route.kt file and review its full content
find . -name "Route.kt" -type f | head -5Repository: imflint/Flint-Android
Length of output: 119
🏁 Script executed:
#!/bin/bash
# Check OnboardingProfileScreen.kt to see how tempToken is used
cat app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt | head -80Repository: imflint/Flint-Android
Length of output: 3044
🏁 Script executed:
#!/bin/bash
# Check the full OnboardingNavigation to see composable parameters
cat app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.ktRepository: imflint/Flint-Android
Length of output: 2101
🏁 Script executed:
#!/bin/bash
# Search for any data class/parameter that receives tempToken
rg -n 'class.*tempToken|fun.*tempToken' --type=kotlin -A 3Repository: imflint/Flint-Android
Length of output: 918
🏁 Script executed:
#!/bin/bash
# Check if tempToken is stored locally or cached
rg -n '(SharedPreferences|DataStore|saveString|save.*token)' --type=kotlin | grep -i 'temp\|token' | head -15Repository: imflint/Flint-Android
Length of output: 647
🏁 Script executed:
#!/bin/bash
# Check how OnboardingProfileRoute extracts tempToken from the route
rg -n 'navBackStackEntry|savedStateHandle' app/src/main/java/com/flint/presentation/onboarding/ --type=kotlin -A 3Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Check if tempToken is used in signup/onboarding completion
rg -n 'SignupRequestModel|SignupRequestDto' --type=kotlin -A 5 | grep -C 3 'tempToken'Repository: imflint/Flint-Android
Length of output: 1893
🏁 Script executed:
#!/bin/bash
# Look for ViewModel in onboarding that might use tempToken
find app/src/main/java/com/flint/presentation/onboarding -name "*ViewModel*" -type fRepository: imflint/Flint-Android
Length of output: 139
🏁 Script executed:
#!/bin/bash
# Check OnboardingDoneRoute to see if that's where signup happens
cat app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt | head -100Repository: imflint/Flint-Android
Length of output: 3294
🏁 Script executed:
#!/bin/bash
# Check OnboardingViewModel for tempToken usage
cat app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt | head -150Repository: imflint/Flint-Android
Length of output: 343
🏁 Script executed:
#!/bin/bash
# Check if tempToken is extracted using navBackStackEntry
rg -n 'toRoute\|getArgumentOrNull\|getArgument' app/src/main/java/com/flint/presentation/onboarding/ --type=kotlinRepository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Look for any usage of OnboardingProfile or where tempToken might be passed
rg -n 'OnboardingProfile\|toRoute' app/src/main/java/com/flint/presentation/onboarding/ --type=kotlin -A 2Repository: imflint/Flint-Android
Length of output: 47
네비게이션 인자로 전달된 tempToken을 화면에서 추출하지 않음
data object에서 data class로 변경하여 tempToken을 전달하는 구조는 적절하나, 다음 문제점이 있습니다:
-
토큰이 추출되지 않음:
OnboardingProfileRoute와OnboardingProfileScreen이tempToken파라미터를 받지 않습니다. 네비게이션 경로에 포함된 토큰을navBackStackEntry.toRoute<Route.OnboardingProfile>().tempToken으로 추출하여 ViewModel이나 상태로 관리해야 합니다. -
보안 노출:
@Serializable애노테이션으로 인해 토큰이 네비게이션 상태에 직렬화되므로 디버그 로그, 크래시 리포트, 백스택 히스토리에 노출될 수 있습니다.
tempToken을 OnboardingViewModel에서 관리하고, 최종 회원가입 요청 시 SignupRequestModel에 전달되도록 수정하세요.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/core/navigation/Route.kt` around lines 12 - 15,
OnboardingProfile data class currently carries tempToken but screens/routes
(OnboardingProfileRoute, OnboardingProfileScreen) do not extract or use it and
the `@Serializable` token can leak; change flow so Nav host reads the token via
navBackStackEntry.toRoute<Route.OnboardingProfile>().tempToken and passes it
into OnboardingViewModel (or have the ViewModel read it on init), remove
tempToken from any serializable navigation state to avoid serialization leakage,
store token in OnboardingViewModel state, and ensure the ViewModel supplies the
token when building SignupRequestModel for the final signup call.
| kakaoLoginManager.login(context) { result -> | ||
| result.onSuccess { token -> | ||
| viewModel.socialVerifyWithKakao( | ||
| requestModel = SocialVerifyRequestModel( | ||
| provider = ProviderType.KAKAO, | ||
| accessToken = token.accessToken | ||
| ), | ||
| ) | ||
| }.onFailure { error -> | ||
| Timber.e(error) | ||
| } |
There was a problem hiding this comment.
p2
이런 처리도 뷰모델에서 해보면 좋을까요?? context가 필요해서 조금 애매할지도
코드레빗보다 김종우 |
📮 관련 이슈
📌 작업 내용
Summary by CodeRabbit
릴리스 노트
새 기능
Chores
✏️ Tip: You can customize this high-level summary in your review settings.