-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: 소셜로그인(+토큰 관리) 재구현 #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
refactor: network 코어 모듈 분리
refactor: 소셜로그인 클라이언트 모듈 구현
| @@ -0,0 +1,58 @@ | |||
| package com.into.websoso.core.auth_kakao | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:package-name reported by reviewdog 🐶
Package name must not contain underscore
| @@ -0,0 +1,20 @@ | |||
| package com.into.websoso.core.auth_kakao.di | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:package-name reported by reviewdog 🐶
Package name must not contain underscore
| @@ -0,0 +1,7 @@ | |||
| package com.into.websoso.core.datastore.di | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:filename reported by reviewdog 🐶
File 'DataStoreQualifier.kt' contains a single class and possibly also extension functions for that class and should be named same after that class 'AccountDataStore.kt'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 17
🔭 Outside diff range comments (1)
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (1)
38-46: 🛠️ Refactor suggestion사용자 정보 호출 실패 시 처리 로직이 없습니다
runCatching의onFailure블록이 비어 있어 네트워크 오류 등으로 이메일을 받아오지 못하면 UI 에서는 빈 문자열이 그대로 노출됩니다. 최소한 로그 기록·재시도·오류 UI 노출 중 하나는 필요해 보입니다.-runCatching { - userRepository.fetchUserInfoDetail() -}.onSuccess { userInfo -> - _userEmail.update { userInfo.email } -} +runCatching { + userRepository.fetchUserInfoDetail() +}.onSuccess { userInfo -> + _userEmail.update { userInfo.email } +}.onFailure { throwable -> + // TODO: 사용자에게 오류 안내 혹은 재시도 로직 추가 + Timber.w(throwable) // 예: Timber 로깅 +}
♻️ Duplicate comments (3)
core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt (1)
1-7: 파일명과 어노테이션 클래스명 불일치
현재 파일명(DataStoreQualifier.kt)과 어노테이션 클래스명(AccountDataStore)이 일치하지 않습니다. 파일명을AccountDataStore.kt로 변경하여 가독성과 유지보수성을 높이는 것을 권장드립니다.core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt (1)
1-1:⚠️ Potential issue패키지명 명명 규칙 위반
패키지명에 언더스코어(
_)를 포함하면 안 됩니다. 현재com.into.websoso.core.auth_kakao.di와 같이 사용하고 있으나,com.into.websoso.core.authkakao.di와 같이 변경하는 것이 좋겠습니다.core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/KakaoAuthClient.kt (1)
1-1: 패키지명에 언더스코어 사용 – Ktlint 동일 지적 반복
core.auth_kakao패키지명은 언더스코어를 포함하고 있어 ktlint 규칙에 위배됩니다. 이미 이전 커밋 리뷰에서 같은 지적이 있었습니다. 하위 모듈 분리를 유지하더라도core.auth.kakao등으로 변경해 주세요.
🧹 Nitpick comments (31)
core/auth-kakao/src/main/AndroidManifest.xml (1)
2-4: 루트<manifest>에package속성 추가 권장
현재 패키지 이름이 명시되어 있지 않아 머지 시 기본 패키지가 사용될 수 있습니다. 명시적으로package="com.teamwss.core.auth.kakao"(실제 모듈 패키지명)와 같이 추가하면 매니페스트 병합 충돌 가능성을 줄일 수 있습니다.core/network/proguard-rules.pro (2)
1-21: 네트워크 라이브러리용 기본 ProGuard 규칙 추가를 고려하세요
Retrofit, OkHttp, Moshi/Gson, Dagger-Hilt 등 리플렉션이나 코드 생성 라이브러리를 사용할 경우 필수-keep규칙이 없습니다. 예시를 참고하여 네트워크 모듈 보호를 강화하시기 바랍니다.+ -keep class com.squareup.okhttp.** { *; } + -keep interface com.squareup.okhttp.** { *; } + -keep class retrofit2.** { *; } + -keepattributes Signature + -keepattributes *Annotation* + -keep class com.squareup.moshi.** { *; } + -keep class dagger.hilt.** { *; } + -keep class javax.inject.** { *; }
5-7: ProGuard 문서 링크를 HTTPS로 업데이트하세요
보안 및 최신 문서 접근성을 위해 HTTP 대신 HTTPS URL 사용을 권장드립니다.-# http://developer.android.com/guide/developing/tools/proguard.html +# https://developer.android.com/guide/developing/build/system/proguardcore/network/.gitignore (1)
1-1: 디렉토리 무시 패턴 검토
/build패턴으로 모듈 빌드 디렉토리를 무시하는 것은 적절합니다. 다만 디렉토리임을 명확히 하기 위해/build/(슬래시 포함) 패턴을 사용하는 것을 권장드립니다.-/build +/build/data/account/.gitignore (1)
1-1: 디렉토리 무시 패턴 검토
/build패턴이data/account모듈의 빌드 결과물을 무시하도록 추가된 점은 적절합니다. 일관성을 위해/build/형태로 수정하면 가독성이 개선됩니다.-/build +/build/data/account/src/main/AndroidManifest.xml (1)
2-2: 라이브러리 매니페스트에 패키지 속성 추가 권장
Android 라이브러리 모듈의AndroidManifest.xml에는 명시적인package속성 선언이 필요합니다. Gradle 병합 시 자동 추가되지만, 가독성과 안정성을 위해 다음과 같이 명시를 권장합니다.-<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.into.websoso.data.account">core/network/src/main/AndroidManifest.xml (1)
2-2: 라이브러리 매니페스트에 패키지 속성 추가 권장
core/network모듈의AndroidManifest.xml에도package속성 선언이 필요합니다. Gradle 빌드 설정에 따라 자동 병합되더라도 명시를 통해 향후 혼동을 방지할 수 있습니다.-<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.into.websoso.core.network">feature/signin/src/main/AndroidManifest.xml (1)
2-2: 라이브러리 매니페스트에 패키지 속성 추가 권장
feature/signin모듈의AndroidManifest.xml에도package속성을 명시해 주세요. 명시적 선언은 유지보수성과 매니페스트 병합 정확도를 높입니다.-<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.into.websoso.feature.signin">data/account/src/main/java/com/into/websoso/data/account/model/AccountEntity.kt (1)
3-6: 속성 이름과 문서화 개선 검토이 데이터 클래스는 명확하고 간단하게 설계되었습니다. 다만 몇 가지 개선사항을 제안합니다:
isRegister속성은 의미상isRegistered로 변경하는 것이 Kotlin 네이밍 컨벤션에 더 적합할 것 같습니다.- 클래스와 속성에 KDoc 문서를 추가하여 이 객체의 목적과 사용법을 명확히 해주세요.
data class AccountEntity( + /** + * 사용자 인증 토큰 정보 + */ val token: TokenEntity, + /** + * 사용자 등록 여부 + */ - val isRegister: Boolean, + val isRegistered: Boolean, )core/common/src/main/java/com/into/websoso/core/common/extensions/ThrottleHelper.kt (1)
14-42: 코드 스타일 개선 제안생성자 선언 후 클래스 본문의 들여쓰기가 불필요합니다. 클래스 본문을 생성자와 같은 레벨로 정렬하는 것이 가독성에 더 좋을 것 같습니다.
다음과 같이 들여쓰기를 조정하는 것을 권장합니다:
@Singleton class ThrottleHelper @Inject - constructor() { - private val mutex = Mutex() - private var lastExecutionTime: TimeMark? = null - - suspend operator fun <T> invoke( - durationMillis: Long = DEFAULT_THROTTLE_DURATION, - block: suspend () -> T, - ): T? { + constructor() { + private val mutex = Mutex() + private var lastExecutionTime: TimeMark? = null + + suspend operator fun <T> invoke( + durationMillis: Long = DEFAULT_THROTTLE_DURATION, + block: suspend () -> T, + ): T? {feature/signin/src/main/java/com/into/websoso/feature/signin/component/SignInButtons.kt (2)
17-28: 확장성을 고려한 설계 제안현재는 카카오 로그인 버튼만 구현되어 있지만, 향후 다른 소셜 로그인(예: 구글, 애플, 네이버 등)이 추가될 경우를 대비하여 버튼 간 간격을 설정하는 것이 좋겠습니다.
@Composable internal fun SignInButtons( onClick: (platform: AuthPlatform) -> Unit, modifier: Modifier = Modifier, ) { Row( horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier.fillMaxWidth(), ) { KakaoSignInButton(onClick = onClick) } }
30-42: 접근성 개선 제안현재 contentDescription이 null로 설정되어 있어 시각 장애인 사용자가 스크린 리더를 통해 이 버튼을 식별하기 어렵습니다. 접근성을 개선하기 위해 contentDescription을 추가하는 것이 좋겠습니다.
@Composable private fun KakaoSignInButton( onClick: (platform: AuthPlatform) -> Unit, modifier: Modifier = Modifier, ) { Image( imageVector = ImageVector.vectorResource(id = ic_login_kakao), - contentDescription = null, + contentDescription = "카카오 로그인", modifier = modifier.clickable { onClick(AuthPlatform.KAKAO) }, ) }data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)
3-13: 인터페이스가 토큰 관리를 위해 잘 설계되어 있습니다.토큰 관리를 위한 기본 작업들이 명확하게 정의되어 있고, 모든 메서드가 suspend 함수로 구현되어 코루틴과 함께 사용하기 적합합니다.
다음과 같은 추가 기능을 고려해보면 좋을 것 같습니다:
- 토큰의 존재 여부나 유효성을 확인하는 메서드(예:
hasValidToken())- 토큰 만료 시간을 관리하는 기능
다음과 같은 메서드 추가를 고려해보세요:
/** 유효한 액세스 토큰이 있는지 확인합니다 */ suspend fun hasValidAccessToken(): Boolean /** 토큰 만료 시간을 저장합니다 */ suspend fun saveTokenExpiration(expirationTimeMillis: Long) /** 토큰 만료 시간을 조회합니다 */ suspend fun getTokenExpiration(): Longfeature/signin/src/main/java/com/into/websoso/feature/signin/component/OnboardingHorizontalPager.kt (2)
18-23: 온보딩 이미지 배열의 네이밍 컨벤션을 개선해보세요.상수로 사용되는 배열의 이름은 일반적으로
SCREAMING_SNAKE_CASE를 사용합니다.ONBOARDING_IMAGES와 같은 형태가 Kotlin 스타일 가이드에 더 적합합니다.-internal val Onboarding_Images = arrayOf( +internal val ONBOARDING_IMAGES = arrayOf( img_login_1, img_login_2, img_login_3, img_login_4, )
25-40: Compose OnboardingHorizontalPager 구현이 잘 되었습니다.Jetpack Compose를 활용한 온보딩 페이저 구현이 깔끔하게 되었습니다. 다만, 접근성을 위해 이미지에 content description을 추가하면 좋을 것 같습니다.
Image( painter = painterResource(id = Onboarding_Images[pageIndex]), - contentDescription = null, + contentDescription = "온보딩 이미지 ${pageIndex + 1}", modifier = Modifier.fillMaxWidth(), )app/src/main/java/com/into/websoso/core/common/util/sessionManager/WebsosoAuthSessionManager.kt (1)
16-22: 세션 관리 구현이 깔끔하게 작성되었습니다.Flow 구현 방식이 적절합니다.
replay = 1과extraBufferCapacity = 1을 설정하여 마지막 세션 만료 이벤트를 새로운 구독자에게 전달하고, 발행 시 중단을 방지합니다.다만, 한 가지 고려할 점은 세션 만료 후 재로그인이 완료된 경우 Flow를 리셋하는 메커니즘이 없다는 것입니다. 세션 만료 후 재인증 시나리오에서 필요할 수 있습니다.
internal class WebsosoAuthSessionManager @Inject constructor() : AuthSessionManager { private val _sessionExpired = MutableSharedFlow<Unit>(replay = 1, extraBufferCapacity = 1) override val sessionExpired: SharedFlow<Unit> get() = _sessionExpired.asSharedFlow() override suspend fun onSessionExpired() { _sessionExpired.emit(Unit) } + + override suspend fun resetSessionState() { + // 재인증 후 세션 상태를 리셋하는 로직 + // 필요한 경우 구현 + } }core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/KakaoLoginResponseDto.kt (1)
17-25: DTO에서 도메인 엔티티로의 매핑 방식에 대한 고려사항
toData()메서드 구현은 간결하고 명확합니다. 다만, 네트워크 계층(DTO)에서 도메인 계층(Entity)으로의 직접적인 의존성이 발생하고 있습니다. 이는 클린 아키텍처의 의존성 방향 원칙에 완전히 부합하지 않을 수 있습니다.더 엄격한 계층 분리를 원한다면, 매핑 로직을 별도의 Mapper 클래스로 분리하거나 도메인 계층에서 이 변환을 담당하도록 설계할 수 있습니다.
data/account/src/main/java/com/into/websoso/data/account/datasource/AccountRemoteDataSource.kt (1)
14-22: 오류 처리 전략 고려 필요인터페이스에 오류 처리에 대한 명시적인 방법이 정의되어 있지 않습니다. 현재는 예외를 통한 암시적 오류 처리에 의존하고 있습니다.
네트워크 오류, 인증 실패 등의 다양한 오류 상황에 대한 처리 전략을 보다 명확히 정의하는 것이 좋을 수 있습니다. 예를 들어,
Result타입을 반환하거나 특정 예외 타입을 문서화하는 방법을 고려해볼 수 있습니다.interface AccountRemoteDataSource { suspend fun postLogin( platform: AuthPlatform, authToken: AuthToken, - ): AccountEntity + ): Result<AccountEntity> suspend fun postLogout( refreshToken: String, deviceIdentifier: String, - ) + ): Result<Unit> suspend fun postWithdraw(reason: String) - ): TokenEntity + ): Result<Unit> suspend fun postReissue(refreshToken: String): Result<TokenEntity> }feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (1)
31-31: 컴포넌트 네이밍 개선 필요
Onboarding_Images는 Kotlin 네이밍 컨벤션을 따르지 않습니다. Kotlin에서는 언더스코어(_) 대신 카멜케이스(camelCase)를 사용하는 것이 권장됩니다.-import com.into.websoso.feature.signin.component.Onboarding_Images +import com.into.websoso.feature.signin.component.OnboardingImages다른 곳에도 동일하게 적용해주세요:
-val pagerState = rememberPagerState { Onboarding_Images.size } +val pagerState = rememberPagerState { OnboardingImages.size }core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
23-33: 토큰 조회 메서드 최적화 검토
accessToken()과refreshToken()메서드에서first()를 호출하여 Flow의 첫 번째 값을 가져오고 있습니다. 이는 코루틴이 일시 중단되고 Flow에서 값이 방출될 때까지 대기합니다. 토큰이 없는 경우 빈 문자열을 반환하는 로직은 적절하지만, 조회 성능을 개선할 여지가 있습니다.DataStore에서 값이 방출되기까지 대기 시간이 길어질 수 있으므로, 캐싱 메커니즘을 고려해 볼 수 있습니다:
internal class DefaultAccountDataSource @Inject constructor( @AccountDataStore private val accountDataStore: DataStore<Preferences>, ) : AccountLocalDataSource { + private var cachedAccessToken: String? = null + private var cachedRefreshToken: String? = null + override suspend fun accessToken(): String = + cachedAccessToken ?: accountDataStore.data .map { preferences -> preferences[ACCESS_TOKEN].orEmpty() }.first() + .also { cachedAccessToken = it } override suspend fun refreshToken(): String = + cachedRefreshToken ?: accountDataStore.data .map { preferences -> preferences[REFRESH_TOKEN].orEmpty() }.first() + .also { cachedRefreshToken = it }토큰이 변경될 때 캐시도 업데이트해야 합니다:
override suspend fun saveAccessToken(accessToken: String) { + cachedAccessToken = accessToken accountDataStore.edit { preferences -> preferences[ACCESS_TOKEN] = accessToken } } override suspend fun saveRefreshToken(refreshToken: String) { + cachedRefreshToken = refreshToken accountDataStore.edit { preferences -> preferences[REFRESH_TOKEN] = refreshToken } } override suspend fun clearTokens() { + cachedAccessToken = null + cachedRefreshToken = null accountDataStore.edit { preferences -> preferences.remove(ACCESS_TOKEN) preferences.remove(REFRESH_TOKEN) } }app/src/main/java/com/into/websoso/ui/splash/SplashActivity.kt (1)
35-36: UI 스레드에서 하드웨어 ID 조회 – I/O 분리 권장
Settings.Secure.getString()은 비교적 가벼운 호출이지만, 시스템 서비스 접근·I/O 가 포함됩니다.
StrictMode가 활성화된 환경(디버그 빌드 등)에서는 메인 스레드 I/O 경고가 발생할 수 있으니, 별도 코루틴(IO 디스패처)로 분리해두면 안전합니다.- val deviceId = getString(contentResolver, ANDROID_ID).orEmpty() - splashViewModel.updateUserDeviceIdentifier(deviceIdentifier = deviceId) + lifecycleScope.launch(Dispatchers.IO) { + val deviceId = getString(contentResolver, ANDROID_ID).orEmpty() + withContext(Dispatchers.Main) { + splashViewModel.updateUserDeviceIdentifier(deviceIdentifier = deviceId) + } + }app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt (1)
30-30: 명명 중복으로 가독성 저하
authClient(Map) 프로퍼티와SignInScreen의 파라미터 이름이 동일하여, 람다 내부에서 어떤 심볼을 참조하는지 한눈에 구분하기 어렵습니다. 변수명을 바꾸거나this@LoginActivity.authClient와 같이 명시적으로 구분하면 혼동을 줄일 수 있습니다.data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
10-10: TODO 항목 반영 여부 확인 필요
TODO: 인스턴스 싱글톤 참고하기, tokens 네이밍수정, result 객체 적용주석이 그대로 남아 있습니다.
실제 배포 전까지는
- 싱글톤 사용 여부(예: object/DI scope)
accessToken·refreshToken네이밍(복수형 혼선)- Boolean 대신 Result 객체 또는 sealed class 반환
등을 확정‧정리하는 편이 좋습니다.
51-58:renewToken동시성 보호 및 예외 변환 고려다중 코루틴에서 동시에 호출될 경우 원격
postReissue가 중복 요청될 수 있습니다.
AuthorizationAuthenticator는 Mutex 로 보호하지만, 레포를 직접 쓰는 곳에서도 재진입 가능성을 막으려면 여기서도Mutex(또는atomic) 사용을 고려해 보세요. 또한 예외 전파 대신 Result 로 감싸면 ViewModel 단에서 실패 UI 처리하기 수월합니다.core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (1)
37-44:runBlocking블로킹 위험OkHttp 의 스레드 풀은 크지 않기 때문에
runBlocking(dispatcher)가 길어지면 네트워크 스레드를 장시간 점유할 수 있습니다.
가능하다면dispatcher.asExecutor()로 비동기 Future 를 만들어 OkHttp 5 의Authenticator.execute()형태를 활용하거나, 토큰 재요청 시간을 최소화하는 별도 스레드풀을 지정해 주세요.core/network/src/main/java/com/into/websoso/core/network/datasource/account/DefaultAccountDataSource.kt (1)
18-62: 계정 관련 네트워크 작업을 담당하는 데이터 소스 구현이 잘 되어 있습니다.구현된 함수들(로그인, 로그아웃, 회원탈퇴, 토큰 재발급)이 명확하게 분리되어 있고 각각의 역할에 맞게 구현되어 있습니다. 하지만
postLogin메소드는 현재 카카오 플랫폼만 처리할 수 있습니다. 향후 다른 소셜 로그인 플랫폼을 추가할 계획이 있다면when구문에else분기를 추가하거나 예외 처리를 고려해 보세요.override suspend fun postLogin( platform: AuthPlatform, authToken: AuthToken, ): AccountEntity = when (platform) { AuthPlatform.KAKAO -> accountApi .postLoginWithKakao( accessToken = authToken.accessToken, ).toData() + else -> throw IllegalArgumentException("지원하지 않는 로그인 플랫폼입니다: $platform") }core/network/src/main/java/com/into/websoso/core/network/datasource/account/AccountApi.kt (1)
18-38: API 인터페이스가 명확하게 정의되어 있습니다.Retrofit API 인터페이스가 각 엔드포인트별로 필요한 메소드와 파라미터를 명확하게 정의하고 있습니다. 모든 메소드가 suspend 함수로 선언되어 있어 코루틴과 함께 사용하기 적합합니다.
다만, 현재 모든 메소드가 카카오 관련 기능만 처리하고 있는데, 인터페이스 이름은 일반적인
AccountApi로 되어 있습니다. 향후 다른 소셜 로그인 플랫폼을 추가할 계획이 없다면 더 구체적인 이름(예:KakaoAccountApi)을 고려해볼 수 있습니다.core/network/src/main/java/com/into/websoso/core/network/di/NetworkModule.kt (1)
28-32: DEBUG 빌드에서만 로깅 인터셉터를 추가하도록 조건 분기하는 편이 안전합니다현재는
httpLoggingInterceptor인스턴스를 항상 체인에 등록하고, DEBUG 여부에 따라 로그 레벨만 조정하고 있습니다. 릴리즈 빌드에서도 불필요한 호출‧필터링 오버헤드가 남으므로, 인터셉터 자체를 DEBUG 빌드에서만 추가하는 편이 더 효율적입니다.- .addInterceptor(httpLoggingInterceptor) + .apply { + if (BuildConfig.DEBUG) addInterceptor(httpLoggingInterceptor) + }feature/signin/src/main/java/com/into/websoso/feature/signin/component/OnboardingDotsIndicator.kt (2)
79-91:remember범위를 줄여 recomposition 비용을 완화할 수 있습니다
dotStates를remember(currentPage, offsetFraction)으로 캐싱해도offsetFraction이 frame 단위로 변하면서 거의 매 프레임 새 리스트를 생성합니다.List(...)자체는 작은 비용이지만, 불필요한remember호출이 오히려 혼란을 줄 수 있어, 아래처럼 일반val로 두어도 무방합니다.- val dotStates = remember(currentPage, offsetFraction) { + val dotStates = List(pagerState.pageCount) { … }
108-115: Compose 키를 지정해 애니메이션 상태 혼동을 방지하세요
Row의dotStates.forEach에서 각DotStyle에는 key가 없으므로, 페이지가 추가/삭제되면 애니메이션 상태가 틀어질 수 있습니다.dotStates.forEachIndexed { index, dotState -> key(index) { DotStyle( … ) } }이를 통해 Compose가 각 아이템을 올바르게 추적하도록 도울 수 있습니다.
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (1)
31-33:Channel대신MutableSharedFlow사용을 고려해 보세요
UI 단일 이벤트 전달에는Channel보다MutableSharedFlow(replay = 0, extraBufferCapacity = …)가 관리·수명주기 측면에서 더 간편합니다.Channel은close()처리 누락 시 메모리 릭 위험이 있고, 정지 상태에서send시 예외가 발생할 수 있습니다.-private val _uiEffect = Channel<UiEffect>(Channel.BUFFERED) -val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow() +private val _uiEffect = MutableSharedFlow<UiEffect>(replay = 0, extraBufferCapacity = 1) +val uiEffect: Flow<UiEffect> get() = _uiEffect
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (102)
app/build.gradle.kts(1 hunks)app/src/main/java/com/into/websoso/WebsosoApp.kt(1 hunks)app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt(1 hunks)app/src/main/java/com/into/websoso/core/common/util/sessionManager/WebsosoAuthSessionManager.kt(1 hunks)app/src/main/java/com/into/websoso/data/authenticator/WebsosoAuthenticator.kt(0 hunks)app/src/main/java/com/into/websoso/data/di/ApiModule.kt(1 hunks)app/src/main/java/com/into/websoso/data/di/NetworkModule.kt(0 hunks)app/src/main/java/com/into/websoso/data/di/OAuthModule.kt(0 hunks)app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt(0 hunks)app/src/main/java/com/into/websoso/data/interceptor/AuthInterceptor.kt(0 hunks)app/src/main/java/com/into/websoso/data/mapper/KakaoTokenMapper.kt(0 hunks)app/src/main/java/com/into/websoso/data/mapper/LoginMapper.kt(0 hunks)app/src/main/java/com/into/websoso/data/model/LoginEntity.kt(0 hunks)app/src/main/java/com/into/websoso/data/model/OAuthToken.kt(0 hunks)app/src/main/java/com/into/websoso/data/qualifier/Auth.kt(0 hunks)app/src/main/java/com/into/websoso/data/qualifier/Interceptor.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/api/AuthApi.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/api/KakaoAuthService.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/api/OAuthService.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/response/KakaoLoginResponseDto.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/response/KakaoTokenReissueResponseDto.kt(0 hunks)app/src/main/java/com/into/websoso/data/repository/AuthRepository.kt(1 hunks)app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt(3 hunks)app/src/main/java/com/into/websoso/ui/accountInfo/LogoutDialogFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt(1 hunks)app/src/main/java/com/into/websoso/ui/login/LoginViewModel.kt(0 hunks)app/src/main/java/com/into/websoso/ui/login/adapter/ImageViewPagerAdapter.kt(0 hunks)app/src/main/java/com/into/websoso/ui/login/adapter/ImageViewPagerViewHolder.kt(0 hunks)app/src/main/java/com/into/websoso/ui/login/model/LoginUiState.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/home/HomeFragment.kt(0 hunks)app/src/main/java/com/into/websoso/ui/onboarding/OnboardingActivity.kt(0 hunks)app/src/main/java/com/into/websoso/ui/onboarding/OnboardingViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/splash/SplashActivity.kt(1 hunks)app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/splash/dialog/MinimumVersionDialogFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondActivity.kt(4 hunks)app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt(3 hunks)app/src/main/res/drawable/bg_login_white_radius_14dp_stroke_primary100_1dp.xml(0 hunks)app/src/main/res/layout/activity_login.xml(0 hunks)app/src/main/res/layout/item_login_image.xml(0 hunks)build-logic/src/main/kotlin/websoso.android.feature.gradle.kts(1 hunks)core/auth-kakao/.gitignore(1 hunks)core/auth-kakao/build.gradle.kts(1 hunks)core/auth-kakao/proguard-rules.pro(1 hunks)core/auth-kakao/src/main/AndroidManifest.xml(1 hunks)core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/KakaoAuthClient.kt(1 hunks)core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt(1 hunks)core/auth/.gitignore(1 hunks)core/auth/build.gradle.kts(1 hunks)core/auth/src/main/java/com/into/websoso/core/auth/AuthClient.kt(1 hunks)core/auth/src/main/java/com/into/websoso/core/auth/AuthPlatform.kt(1 hunks)core/auth/src/main/java/com/into/websoso/core/auth/AuthSessionManager.kt(1 hunks)core/auth/src/main/java/com/into/websoso/core/auth/AuthToken.kt(1 hunks)core/common/.gitignore(1 hunks)core/common/build.gradle.kts(1 hunks)core/common/proguard-rules.pro(1 hunks)core/common/src/main/AndroidManifest.xml(1 hunks)core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt(1 hunks)core/common/src/main/java/com/into/websoso/core/common/extensions/FlowExtensions.kt(1 hunks)core/common/src/main/java/com/into/websoso/core/common/extensions/ThrottleHelper.kt(1 hunks)core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt(1 hunks)core/datastore/.gitignore(1 hunks)core/datastore/build.gradle.kts(1 hunks)core/datastore/proguard-rules.pro(1 hunks)core/datastore/src/main/AndroidManifest.xml(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt(1 hunks)core/network/.gitignore(1 hunks)core/network/build.gradle.kts(1 hunks)core/network/proguard-rules.pro(1 hunks)core/network/src/main/AndroidManifest.xml(1 hunks)core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/AccountApi.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/DefaultAccountDataSource.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/KakaoLogoutRequestDto.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/KakaoWithdrawRequestDto.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/TokenReissueRequestDto.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/KakaoLoginResponseDto.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/TokenReissueResponseDto.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/di/NetworkModule.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt(1 hunks)data/account/.gitignore(1 hunks)data/account/build.gradle.kts(1 hunks)data/account/proguard-rules.pro(1 hunks)data/account/src/main/AndroidManifest.xml(1 hunks)data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/datasource/AccountRemoteDataSource.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/model/AccountEntity.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/model/TokenEntity.kt(1 hunks)feature/signin/.gitignore(1 hunks)feature/signin/build.gradle.kts(1 hunks)feature/signin/proguard-rules.pro(1 hunks)feature/signin/src/main/AndroidManifest.xml(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/component/OnboardingDotsIndicator.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/component/OnboardingHorizontalPager.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/component/SignInButtons.kt(1 hunks)gradle/libs.versions.toml(4 hunks)settings.gradle.kts(1 hunks)
💤 Files with no reviewable changes (25)
- app/src/main/res/drawable/bg_login_white_radius_14dp_stroke_primary100_1dp.xml
- app/src/main/java/com/into/websoso/data/model/LoginEntity.kt
- app/src/main/java/com/into/websoso/ui/main/home/HomeFragment.kt
- app/src/main/java/com/into/websoso/data/mapper/LoginMapper.kt
- app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt
- app/src/main/java/com/into/websoso/data/qualifier/Auth.kt
- app/src/main/java/com/into/websoso/data/remote/response/KakaoLoginResponseDto.kt
- app/src/main/res/layout/item_login_image.xml
- app/src/main/java/com/into/websoso/data/mapper/KakaoTokenMapper.kt
- app/src/main/java/com/into/websoso/data/di/OAuthModule.kt
- app/src/main/res/layout/activity_login.xml
- app/src/main/java/com/into/websoso/data/interceptor/AuthInterceptor.kt
- app/src/main/java/com/into/websoso/data/qualifier/Interceptor.kt
- app/src/main/java/com/into/websoso/data/remote/api/OAuthService.kt
- app/src/main/java/com/into/websoso/ui/login/adapter/ImageViewPagerAdapter.kt
- app/src/main/java/com/into/websoso/ui/login/adapter/ImageViewPagerViewHolder.kt
- app/src/main/java/com/into/websoso/ui/login/model/LoginUiState.kt
- app/src/main/java/com/into/websoso/ui/onboarding/OnboardingActivity.kt
- app/src/main/java/com/into/websoso/data/authenticator/WebsosoAuthenticator.kt
- app/src/main/java/com/into/websoso/data/model/OAuthToken.kt
- app/src/main/java/com/into/websoso/ui/login/LoginViewModel.kt
- app/src/main/java/com/into/websoso/data/remote/response/KakaoTokenReissueResponseDto.kt
- app/src/main/java/com/into/websoso/data/di/NetworkModule.kt
- app/src/main/java/com/into/websoso/data/remote/api/KakaoAuthService.kt
- app/src/main/java/com/into/websoso/data/remote/api/AuthApi.kt
🧰 Additional context used
🧬 Code Graph Analysis (10)
feature/signin/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
core/common/build.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)
core/auth-kakao/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
build-logic/src/main/kotlin/websoso.android.feature.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
core/datastore/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
data/account/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
app/src/main/java/com/into/websoso/ui/splash/SplashActivity.kt (2)
app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)
updateUserDeviceIdentifier(36-44)app/src/main/java/com/into/websoso/ui/accountInfo/LogoutDialogFragment.kt (1)
collectUiEffect(33-39)
core/network/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt (1)
feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (2)
SignInScreen(34-71)SignInScreen(73-92)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)
refreshToken(6-6)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
refreshToken(29-33)
🔇 Additional comments (98)
core/auth/.gitignore (1)
1-1: 빌드 아티팩트 무시 설정 확인
/build디렉토리를 무시하도록 지정한 것은 적절하며, 다른 core 모듈(core/common,core/auth-kakao,core/network등)과 일관성을 잘 유지하고 있습니다.core/auth-kakao/.gitignore (1)
1-1:build디렉토리 무시 설정 적절
core/auth-kakao 모듈의 빌드 아티팩트인build폴더를.gitignore에 추가하여 버전 관리에서 배제하는 것은 타 모듈과 일관성을 유지하면서 올바른 설정입니다.core/auth-kakao/src/main/AndroidManifest.xml (1)
1-2: XML 선언 및 네임스페이스 올바르게 설정됨
라이브러리 모듈의 최소 매니페스트를 위한 기본 골격이 잘 구성되었습니다.build-logic/src/main/kotlin/websoso.android.feature.gradle.kts (1)
11-11::core:common모듈 의존성 추가
feature 모듈에서 공통 유틸리티를 제공하는:core:common프로젝트 의존성이 올바르게 추가되었습니다.feature/signin/.gitignore (1)
1-1: 빌드 아티팩트 무시 설정
/build디렉토리를 무시하여 모듈 빌드 결과물이 버전 관리되지 않도록 설정했습니다.core/common/.gitignore (1)
1-1: 빌드 아티팩트 무시 설정
/build디렉토리를 무시하여 모듈 빌드 결과물이 버전 관리되지 않도록 설정했습니다.core/datastore/.gitignore (1)
1-1: 빌드 아티팩트 무시 설정
/build디렉토리를 무시하여 모듈 빌드 결과물이 버전 관리되지 않도록 설정했습니다.core/common/build.gradle.kts (2)
1-2:setNamespace확장함수 가져오기 확인
import com.into.websoso.setNamespace를 통해 네임스페이스 설정 확장함수를 올바르게 불러오고 있습니다.
8-10: 네임스페이스 설정 적절
setNamespace("core.common")로com.into.websoso.core.common네임스페이스가 올바르게 설정됩니다.data/account/build.gradle.kts (3)
1-2:setNamespace확장함수 가져오기 확인
네임스페이스 설정을 위한import com.into.websoso.setNamespace선언이 올바르게 적용되었습니다.
7-9: 네임스페이스 설정 적절
setNamespace("data.account")로com.into.websoso.data.account네임스페이스가 올바르게 설정됩니다.
11-13: 필수 의존성 누락 확인 필요
AccountLocalDataSource,AccountRemoteDataSource구현을 위해projects.core.datastore및projects.core.network모듈 의존성이 필요할 수 있습니다. dependencies 블록에 해당 모듈들이 포함되었는지 검토해주세요.core/common/src/main/AndroidManifest.xml (1)
1-4: 빈 AndroidManifest 확인
core.common모듈에 불필요한 컴포넌트나 권한 없이 최소한의 manifest가 잘 추가되었습니다.core/datastore/src/main/AndroidManifest.xml (1)
1-4: 빈 AndroidManifest 확인
core.datastore모듈에 기본 manifest가 문제없이 추가되었습니다.core/auth/build.gradle.kts (1)
1-5: 플러그인 구성 적절
Kotlin JVM, Coroutines, Dagger(DI) 플러그인이 모듈 특성에 맞게 잘 적용되었습니다.feature/signin/build.gradle.kts (1)
1-15: 사인인 모듈 구성이 적절합니다빌드 스크립트가 잘 구성되어 있으며, 프로젝트의 모듈화 구조를 잘 따르고 있습니다. 필요한 의존성(auth와 account)이 명확하게 정의되어 있습니다.
app/src/main/java/com/into/websoso/ui/splash/dialog/MinimumVersionDialogFragment.kt (1)
37-38: 상수 추가가 적절합니다다이얼로그 프래그먼트를 위한 태그 상수를 추가한 것은 좋은 변경사항입니다. 이를 통해 프래그먼트 트랜잭션 관리와 다이얼로그 표시가 더 일관되게 처리될 수 있습니다.
core/auth-kakao/build.gradle.kts (2)
8-8: 네임스페이스 표기법 불일치 확인 필요모듈 경로는 하이픈(
auth-kakao)을 사용하는 반면, 네임스페이스는 언더스코어(auth_kakao)를 사용하고 있습니다. 일관성을 위해 동일한 구분자를 사용하는 것이 좋습니다.이 불일치가 의도적인지 확인해주세요. 모듈 경로와 네임스페이스의 구분자를 통일하면 코드 가독성과 유지보수성이 향상될 수 있습니다.
11-15: 의존성 구성이 적절합니다카카오 인증 모듈에 필요한 의존성이 잘 정의되어 있습니다.
core.auth모듈 의존성을 통해 인터페이스를 구현하고,libs.kakao의존성을 통해 SDK 기능을 활용할 수 있습니다.data/account/src/main/java/com/into/websoso/data/account/model/TokenEntity.kt (1)
1-6: TokenEntity 데이터 클래스 정의 검토 완료
accessToken과refreshToken을 불변(val)으로 선언하여 도메인 모델을 명확히 표현했습니다. 파일명과 패키지 구조도 일치합니다.core/datastore/build.gradle.kts (4)
1-1: import 구문 확인
setNamespace확장함수 import가 정상 적용되었습니다.
3-5: 플러그인 설정 검토
websoso.android.library플러그인이 올바르게 적용되어 있습니다.
7-9: 네임스페이스 설정 검토
setNamespace("core.datastore")를 통해com.into.websoso.core.datastore네임스페이스가 정확히 설정되었습니다.
11-17: 의존성 선언 검토
data.account모듈과datastore.preferences라이브러리가 적절히 추가되었습니다. 불필요한 의존성 없이 깔끔합니다.settings.gradle.kts (3)
25-30: 새로운 core 모듈 등록 확인
:core:common,:core:auth,:core:auth-kakao,:core:network,:core:datastore모듈이 정상적으로 추가되었습니다.
33-33: feature:signin 모듈 등록 확인
feature:signin모듈이 포함되어 경로 구성과 일치하는지 확인해주세요.
35-35: data:account 모듈 등록 확인
data:account모듈이 추가되었습니다. 디렉토리 구조(data/account)와 잘 매칭되는지 검증해주세요.core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/KakaoLogoutRequestDto.kt (2)
1-1: 패키지 경로 확인
네트워크 계층 요청 DTO 패키지가core.network.datasource.account.model.request로 정리되어 일관성이 높아졌습니다.
7-7: DTO 클래스 정의 검토
internal가시성 제한과@Serializable,@SerialName적용이 일관되며 적절합니다.core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/KakaoWithdrawRequestDto.kt (1)
1-10: DTO 클래스의 가시성 및 이름 변경이 잘 이루어졌습니다.기존의
WithdrawRequestDto에서KakaoWithdrawRequestDto로 이름이 변경되고internal가시성 한정자가 추가되었습니다. 이는 네트워크 계층의 캡슐화를 강화하는 좋은 접근 방식입니다. 또한refreshToken속성이 제거되어 API 계약에 맞게 심플해졌습니다.이러한 변경은 인증 및 계정 관련 코드를 모듈화하는 전체적인 리팩토링 방향과 잘 맞습니다.
core/auth/src/main/java/com/into/websoso/core/auth/AuthToken.kt (2)
3-5: 토큰 클래스 설계가 단순하고 명확합니다.
AuthToken데이터 클래스가accessToken만 포함하는 간결한 설계로 되어 있어 좋습니다. 이런 단순한 래퍼 클래스는 토큰 관리를 일관되게 하고 추후 확장성도 제공합니다.
7-10: String 확장 함수로 변환 로직이 깔끔합니다.문자열을
AuthToken으로 변환하는 확장 함수가 잘 구현되어 있습니다. 이 확장 함수는 Kakao SDK와 같은 외부 토큰을 통합된 앱 내부 모델로 쉽게 변환할 수 있게 해줍니다.core/auth/src/main/java/com/into/websoso/core/auth/AuthSessionManager.kt (1)
5-9: 세션 만료 관리 인터페이스가 잘 설계되었습니다.
AuthSessionManager인터페이스는 세션 만료 이벤트를 관리하기 위한 좋은 추상화입니다.SharedFlow를 사용해 세션 만료 신호를 발행하고, 일시 중단 함수로 만료 로직을 처리하는 방식이 현대적인 Kotlin 코루틴 패턴과 잘 맞습니다.이 인터페이스는 네트워크 인증, 앱 라이프사이클, 네비게이션 등 여러 계층에서 세션 만료를 일관되게 처리할 수 있는 중심점이 될 것입니다.
core/datastore/proguard-rules.pro (1)
1-21: Proguard 규칙 파일이 템플릿 상태입니다.이 파일은 기본 Proguard 규칙 템플릿만 포함하고 있고 실제 활성화된 규칙은 없습니다. 현재는 문제가 없지만, 추후
core/datastore모듈에 대한 구체적인 Proguard 규칙이 필요할 수 있습니다.특히 DataStore 관련 직렬화 클래스나 코루틴 관련 클래스에 대한 보존 규칙을 고려해보는 것이 좋을 것 같습니다.
모듈 출시 전에 필요한 Proguard 규칙이 있는지 확인해주세요. 특히 Kotlin 직렬화나 DataStore 관련 클래스에 대한 규칙이 필요할 수 있습니다.
core/common/proguard-rules.pro (1)
1-21: 프로가드 템플릿 파일 추가 확인
모듈별로 향후 필요한 ProGuard 규칙을 정의하기 위한 템플릿이 잘 추가되었습니다.core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/request/TokenReissueRequestDto.kt (1)
6-10: internal 가시성으로 캡슐화 적절
TokenReissueRequestDto를internal로 선언하여 모듈 경계를 넘지 않도록 한 것은 좋은 설계입니다. 시리얼라이즈 어노테이션도 올바르게 적용되어 있습니다.data/account/proguard-rules.pro (1)
1-21: 프로가드 템플릿 파일 추가 확인
data/account모듈용 ProGuard 템플릿이 잘 추가되었습니다.feature/signin/proguard-rules.pro (1)
1-21: 프로가드 템플릿 파일 추가 확인
feature/signin모듈용 ProGuard 템플릿이 잘 추가되었습니다.app/build.gradle.kts (2)
79-84: 모듈화 아키텍처 적용 확인소셜 로그인 리팩토링에 필요한 새로운 모듈 의존성이 적절히 추가되었습니다. 인증, 네트워크, 데이터 저장 등의 책임을 각각의 모듈로 분리한 것이 확인됩니다.
85-86: 기능 모듈 및 데이터 레이어 의존성 확인로그인 기능(
feature.signin)과 계정 데이터(data.account) 모듈이 적절히 추가되었습니다. 이는 소셜 로그인 기능의 UI와 데이터 처리 로직을 분리하는 좋은 접근 방식입니다.core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt (1)
13-20: 멀티바인딩 설정 확인
ActivityComponent스코프에 설치된 모듈이AuthPlatform.KAKAO키를 사용하여KakaoAuthClient를AuthClient인터페이스에 바인딩하는 구현이 적절합니다. 이는 여러 소셜 로그인 제공자를 동적으로 처리할 수 있게 해주는 좋은 접근 방식입니다.app/src/main/java/com/into/websoso/data/di/ApiModule.kt (2)
29-61: 모든 API 한정자 일관성 있게 제거모든 API 제공 메서드에서 Retrofit 한정자가 일관성 있게 제거되었습니다. 이는 단일 Retrofit 인스턴스를 사용하도록 네트워크 계층이 재구성되었음을 나타냅니다.
25-25:Details
✅ Verification successful
Retrofit 한정자(qualifier) 제거 확인
@Secured한정자가 제거되었습니다. 이는 모듈화된 새로운 인증 방식으로 이동함에 따른 적절한 변경입니다. 모든 인증 관련 처리가 전용 모듈로 이동되었는지 확인하세요.다음 스크립트를 실행하여 인증 로직이 새 모듈로 올바르게 이동되었는지 확인해 보세요:
🏁 Script executed:
#!/bin/bash # AuthorizationInterceptor와 AuthorizationAuthenticator가 네트워크 모듈에 존재하는지 확인 echo "인증 관련 새 클래스 확인:" find core/network/src -type f -name "*.kt" | xargs grep -l "Authorization" # AccountRepository가 올바르게 구현되었는지 확인 echo "계정 저장소 구현 확인:" find data/account/src -type f -name "*.kt" | xargs grep -l "AccountRepository"Length of output: 818
인증 로직 전용 모듈로 이동 완료 확인
@Secured한정자는 제거되었으며, 아래 인증 관련 클래스들이 각각 올바른 모듈에 존재함을 확인했습니다.
– core/network 모듈
• AuthorizationInterceptor.kt
• AuthorizationAuthenticator.kt
– data/account 모듈
• AccountRepository.kt변경된 인증 처리 로직이 전용 모듈로 완전히 이전되었으므로 코드 변경을 승인합니다.
core/common/src/main/java/com/into/websoso/core/common/extensions/ThrottleHelper.kt (2)
11-42: 스로틀링 헬퍼 클래스 구현다중 클릭이나 빠른 연속 작업을 방지하기 위한 ThrottleHelper 클래스가 잘 구현되었습니다. 코루틴 Mutex를 사용한 스레드 안전성 확보와 모노토닉 타임소스 사용은 좋은 접근 방식입니다.
18-37: 스로틀 구현 방식 확인스로틀링 로직이 적절히 구현되었습니다. Mutex를 사용한 동시성 제어, 이전 실행 시간 확인, 조건 충족 시 새 블록 실행을 처리하는 흐름이 잘 설계되었습니다.
app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondActivity.kt (4)
9-9: NavigatorProvider 인터페이스를 사용하는 좋은 변경사항입니다.액티비티 간 직접 참조 대신 NavigatorProvider를 사용함으로써 화면 이동 로직을 추상화하고 있습니다. 이는 테스트 용이성과 결합도를 낮추는 데 도움이 됩니다.
28-30: Dependency Injection을 통한 네비게이션 주입이 잘 구현되었습니다.Hilt의 @Inject 어노테이션을 사용하여 NavigatorProvider를 주입받음으로써 액티비티가 직접 다른 화면에 대한 의존성을 가지지 않도록 했습니다. 이는 SOLID 원칙 중 의존성 역전 원칙(DIP)을 잘 따르고 있습니다.
85-85: 스로틀링 호출 방식이 개선되었습니다.메서드 참조를 사용하여 코드가 더 간결해졌습니다. 이전 코드에 비해 가독성이 향상되었습니다.
107-107: 네비게이션 로직이 중앙 집중화되었습니다.직접 LoginActivity를 시작하는 대신 NavigatorProvider를 통해 로그인 화면으로 이동함으로써 화면 이동 로직이 중앙 집중화되었습니다. 이는 앱 전체의 네비게이션 로직을 일관되게 관리할 수 있게 해줍니다.
feature/signin/src/main/java/com/into/websoso/feature/signin/component/SignInButtons.kt (1)
1-51: Jetpack Compose를 활용한 재사용 가능한 로그인 버튼 컴포넌트가 잘 구현되었습니다.새로운 Compose UI 컴포넌트인 SignInButtons는 다음과 같은 장점을 가지고 있습니다:
- 명확한 책임 분리: 각 소셜 로그인 버튼이 별도의 private 컴포넌트로 분리되어 있어 확장성이 좋습니다.
- 재사용성: Modifier 파라미터를 통해 상위 컴포넌트에서 스타일을 쉽게 변경할 수 있습니다.
- 이벤트 처리: onClick 콜백을 통해 클릭 이벤트를 상위 컴포넌트로 위임합니다.
- 미리보기 제공: Preview 컴포넌트가 포함되어 개발 시 UI를 쉽게 확인할 수 있습니다.
다만, 향후 다른 소셜 로그인 플랫폼이 추가될 경우를 대비하여 버튼 간 간격 설정이 필요할 수 있습니다.
app/src/main/java/com/into/websoso/WebsosoApp.kt (4)
5-10: 세션 만료 감지를 위한 적절한 의존성 추가ProcessLifecycleOwner와 세션 관련 클래스를 import하여 앱 수준에서 세션 상태를 관리할 수 있는 기반을 마련했습니다. 이는 앱 전체에서 세션 만료 시 일관된 처리를 가능하게 합니다.
16-21: 세션 관리와 네비게이션을 위한 의존성 주입이 잘 구현되었습니다.Hilt를 통해 AuthSessionManager와 NavigatorProvider를 주입받아 앱 수준에서 세션과 네비게이션을 관리할 수 있게 되었습니다. 이는 관심사 분리와 모듈화를 촉진합니다.
26-26: 세션 상태 구독 메서드 호출이 적절한 위치에 추가되었습니다.onCreate 메서드 내에서 subscribeSessionState()를 호출하여 앱 시작 시 세션 상태 감지를 시작하는 것이 좋습니다. 이는 앱이 실행되는 동안 세션 만료를 지속적으로 모니터링할 수 있게 합니다.
30-34: 세션 만료 처리 로직이 잘 구현되었습니다.ProcessLifecycleOwner를 사용하여 앱의 수명 주기에 맞게 세션 만료 이벤트를 수집하고, 세션이 만료되면 로그인 화면으로 이동하는 로직이 잘 구현되었습니다. 이는 사용자 경험을 향상시키고 보안을 강화합니다.
core/network/build.gradle.kts (3)
9-27: 네트워크 모듈의 네임스페이스와 빌드 설정이 잘 구성되었습니다.Android 라이브러리 모듈의 네임스페이스가 명확하게 설정되었고, debug와 release 빌드 타입에 따라 다른 BASE_URL을 사용하도록 설정되어 있습니다. 이는 개발 및 프로덕션 환경에서 적절한 API 엔드포인트를 사용할 수 있게 해줍니다.
BuildConfig 생성을 활성화하여 코드에서 빌드 타입에 따른 설정값에 접근할 수 있게 한 점도 좋습니다.
29-43: 네트워크 모듈의 의존성이 명확하게 구성되었습니다.모듈 간 의존성과 외부 라이브러리 의존성이 명확하게 구분되어 있고, 주석을 통해 각 의존성 그룹의 목적이 설명되어 있습니다.
Retrofit, OkHttp, Kotlin Serialization 등 네트워크 통신과 직렬화에 필요한 라이브러리들이 잘 구성되어 있습니다. 특히 각 의존성 그룹에 대한 주석을 통해 코드의 가독성과 유지보수성이 향상되었습니다.
1-7: 빌드 스크립트 설정이 깔끔하게 구성되었습니다.import 문이 간결하게 정리되어 있고, 필요한 플러그인들이 명확하게 적용되어 있습니다. 특히 커스텀 함수인 setNamespace와 buildConfigs를 활용하여 코드 중복을 줄이고 일관된 설정을 유지하고 있습니다.
core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/TokenReissueResponseDto.kt (1)
7-19: 데이터 클래스 구조와 매핑 로직이 명확하게 구현되어 있습니다.코드가 깔끔하고 잘 정의되어 있습니다.
@Serializable어노테이션을 통한 JSON 직렬화와@SerialName으로 필드명을 지정한 부분이 적절합니다. 또한 내부(internal) 가시성 제한자를 활용하여 모듈 내부에서만 접근 가능하도록 캡슐화한 점이 좋습니다.
toData()메서드를 통해 DTO에서 도메인 모델로 변환하는 방식도 클린 아키텍처 원칙을 잘 따르고 있습니다.core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt (1)
11-27: 디스패처 모듈이 Dagger Hilt 모범 사례를 잘 따릅니다.코루틴 디스패처를 제공하는 모듈이 깔끔하게 구현되어 있습니다. 어노테이션 기반의 한정자(qualifier)를 사용하여 타입 안전한 의존성 주입을 가능하게 한 점이 좋습니다.
현재는 IO 디스패처만 제공하고 있는데, 필요에 따라 Main, Default 등 다른 디스패처 타입도 추가하는 것을 고려해볼 수 있습니다.
app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt (3)
8-8: AuthRepository에서 AccountRepository로 변경 적절합니다.인증 관련 로직을 AuthRepository에서 AccountRepository로 이동하는 리팩토링이 전체 아키텍처의 관심사 분리를 잘 반영했습니다.
19-20: DI 의존성 변경이 일관되게 적용되었습니다.AccountRepository를 주입받도록 변경하여 새로운 아키텍처 설계를 잘 따르고 있습니다.
78-82: 회원 탈퇴 로직이 개선되었습니다.회원 탈퇴 로직이 더 간결해졌고, AccountRepository를 통해 처리되는 방식으로 변경되었습니다. 이전 코드의
authRepository.updateIsAutoLogin(false)호출이 제거되었는데, 이는 자동 로그인 상태 관리가 계정 관리 로직에서 분리되었음을 의미합니다.gradle/libs.versions.toml (4)
13-15: Hilt와 Compose 통합을 위한 의존성 추가가 적절합니다.Hilt Navigation Compose 및 javax-inject 의존성을 추가한 것은 모듈화된 아키텍처에서 Compose UI와 의존성 주입을 잘 통합하기 위한 좋은 접근 방식입니다.
137-137: javax-inject 라이브러리 정의가 잘 추가되었습니다.모듈화된 아키텍처에서 의존성 주입을 위한 표준 어노테이션 사용을 위해 적절히 추가되었습니다.
158-158: Compose Hilt Navigation 라이브러리 정의가 추가되었습니다.Jetpack Compose에서 Hilt 기반 종속성 주입을 활용한 내비게이션을 구현할 수 있게 됩니다. 소셜 로그인 리팩토링에 적합한 의존성입니다.
172-173: Compose 번들에 Hilt Navigation이 포함되었습니다.모든 모듈에서 일관되게 Compose 의존성을 사용할 수 있도록 번들에 포함시킨 것은 좋은 접근 방식입니다.
app/src/main/java/com/into/websoso/ui/onboarding/OnboardingViewModel.kt (1)
100-100: 인증 토큰 관리가 ViewModel에서 제거되었습니다.
authRepository.fetchNicknameValidity()호출에서 토큰 파라미터가 제거되었습니다. 이는 토큰 관리 로직이 별도 모듈로 분리되어 ViewModel이 UI 상태 관리에만 집중할 수 있도록 개선되었음을 보여줍니다. 이 변경은 관심사 분리 원칙을 잘 따르고 있습니다.feature/signin/src/main/java/com/into/websoso/feature/signin/component/OnboardingHorizontalPager.kt (1)
42-50: Preview 함수가 잘 구현되었습니다.디자인 시간 시각화를 위한 Preview 함수가 적절히 구현되어 있습니다. Compose UI 개발 워크플로우를 효율적으로 지원합니다.
app/src/main/java/com/into/websoso/core/common/util/sessionManager/WebsosoAuthSessionManager.kt (1)
25-31: DI 모듈 구현이 적절합니다.
WebsosoAuthSessionManagerModule의 구현과 바인딩이 잘 되어 있습니다. 싱글톤으로 제공되어 애플리케이션 전체에서 일관된 세션 관리가 가능합니다.core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/KakaoLoginResponseDto.kt (1)
8-16: 직렬화 클래스의 필드 구성이 적절합니다.
SerialName어노테이션을 사용하여 JSON 필드 매핑이 명확하게 되어 있으며, 필요한 모든 필드(인증 토큰, 리프레시 토큰, 가입 여부)가 포함되어 있습니다.data/account/src/main/java/com/into/websoso/data/account/datasource/AccountRemoteDataSource.kt (1)
8-13: 로그인 인터페이스 설계가 플랫폼에 독립적입니다.
postLogin메서드가AuthPlatform과AuthToken을 매개변수로 받아 다양한 소셜 로그인 플랫폼을 지원할 수 있도록 설계된 점이 좋습니다. 플랫폼별 구현체를 추가하기 쉬운 구조입니다.core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt (1)
14-19: DataStore 설정이 적절합니다.DataStore의 이름이 명확하게 지정되어 있고, 확장 프로퍼티를 통한 접근 방식이 코틀린 idiom에 맞게 구현되었습니다.
feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (3)
52-54: TODO 항목 구현 필요실패 시 사용자에게 피드백을 제공하는 것은 중요합니다. 해당 TODO 항목을 구현하지 않으면 사용자는 로그인 실패 시 어떤 일이 발생했는지 알 수 없습니다.
커스텀 스낵바 또는 Toast 메시지를 구현하여 사용자에게 적절한 피드백을 제공해주세요.
34-71: 코드 구조가 잘 설계되어 있습니다상위 레벨
SignInScreen컴포저블에서 ViewModel과 비즈니스 로직 처리를 담당하고, 실제 UI 렌더링은 private 컴포저블로 분리한 것이 좋은 구조입니다. 이렇게 하면 UI와 로직이 깔끔하게 분리되어 테스트와 유지보수가 용이해집니다.
73-92: UI 구성이 잘 되어 있습니다Column을 사용하여 수직 레이아웃을 구성하고, Spacer와 weight을 적절히 활용하여 요소들 간의 간격을 조정한 점이 좋습니다. 화면 구성이 깔끔하고 가독성이 좋습니다.
app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt (2)
17-36: 네비게이션 로직 중앙화 구현이 잘 되어 있습니다
WebsosoNavigator클래스가NavigatorProvider인터페이스를 구현하여 앱 전체의 네비게이션 로직을 중앙화한 것이 좋습니다. 액티비티 전환 로직을 한 곳에서 관리함으로써 코드의 재사용성과 일관성이 높아졌습니다.특히, lambda를 통해 startActivity를 전달받는 방식은 테스트 용이성을 높이고 컴포넌트 간 결합도를 낮추는 좋은 패턴입니다.
38-44: 의존성 주입 설정이 적절합니다Dagger Hilt를 사용하여
WebsosoNavigator를NavigatorProvider의 싱글톤 구현체로 바인딩한 것이 좋습니다. 이렇게 하면 앱 전체에서 동일한 인스턴스를 사용할 수 있으며, 테스트 시에도 쉽게 모킹할 수 있습니다.core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (2)
47-52: 토큰 삭제 방식이 적절합니다
clearTokens()메서드에서 한 번의edit호출로 두 토큰을 모두 제거하는 방식은 효율적입니다. DataStore 트랜잭션을 최소화하여 성능을 최적화했습니다.
60-66: 의존성 주입 설정이 적절합니다Dagger Hilt를 사용하여
DefaultAccountDataSource를AccountLocalDataSource의 싱글톤 구현체로 바인딩한 것이 좋습니다. 이렇게 하면 앱 전체에서 동일한 인스턴스를 사용할 수 있으며, 테스트 시에도 쉽게 모킹할 수 있습니다.core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt (2)
8-14: 네비게이션 인터페이스 설계가 훌륭합니다
NavigatorProvider인터페이스가 간결하고 목적에 맞게 잘 설계되었습니다. 각 네비게이션 메서드가 lambda를 통해startActivity를 전달받는 방식은 테스트 용이성을 높이고 컴포넌트 간 결합도를 낮추는 좋은 패턴입니다.
16-20: Hilt EntryPoint 사용이 적절합니다
NavigatorEntryPoint인터페이스를 통해 Hilt EntryPoint를 정의한 것은 적절합니다. 이를 통해 일반적인 Hilt 의존성 주입이 불가능한 컨텍스트(예: 컴포즈 미리보기, 브로드캐스트 리시버 등)에서도 네비게이터에 접근할 수 있습니다.core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/KakaoAuthClient.kt (1)
20-24: @ActivityContext 주입 범위 점검 필요
AuthClient구현이ActivityContext를 보관하면 ViewModel·Singleton 범위에서 재사용될 때 Activity Leak 위험이 있습니다.
현재는 로그인 화면 한정으로 주입되지만, 다른 컴포넌트에서 주입받을 가능성을 고려해Context대신Activity를 메서드 파라미터로 전달하거나, 범위를@ActivityRetainedScoped등으로 제한하는 방안을 검토해 주세요.core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (2)
56-60: 재시도 횟수 계산에서 현재 응답 제외 여부 확인
retryAttemptCount()는 현재Response를 포함해 카운트하기 때문에
최대 시도 값이 2일 때 실제 서버 호출은 1회(원본)+1회(재시도)입니다.
원하는 정책이 “두 번 재시도(총 3회 시도)” 라면priorResponse만 세도록 수정해야 합니다. 로직 의도 확인을 권장합니다.
61-68: 🛠️ Refactor suggestion
isRefreshNeeded조건 재검토토큰을 갱신한 뒤 두 번째 401 이 들어오면
oldAccessToken == updatedAccessToken가 다시true가 되어 재갱신을 시도합니다.
Throttle 로 막고 있기는 하지만, 의미상 “갱신 전-후 토큰이 동일하면 갱신 필요” → “다르면 필요 없음” 으로 바꾸는 편이 자연스럽습니다.-return oldAccessToken == updatedAccessToken +return oldAccessToken != updatedAccessToken이렇게 하면 첫 401 때만 재발급을 시도하고, 이후에는 세션 만료 처리로 바로 넘어갑니다.
Likely an incorrect or invalid review comment.
core/network/src/main/java/com/into/websoso/core/network/datasource/account/DefaultAccountDataSource.kt (1)
64-70: 의존성 주입 설정이 잘 구현되어 있습니다.Hilt를 사용한 싱글톤 컴포넌트로
AccountRemoteDataSource의 구현체를 바인딩하는 모듈이 깔끔하게 구현되어 있습니다.core/network/src/main/java/com/into/websoso/core/network/datasource/account/AccountApi.kt (1)
40-46: API 모듈 설정이 잘 구현되어 있습니다.Hilt를 사용한 싱글톤 컴포넌트로
AccountApi를 제공하는 모듈이 깔끔하게 구현되어 있습니다.app/src/main/java/com/into/websoso/ui/accountInfo/LogoutDialogFragment.kt (4)
7-7: Hilt를 사용한 의존성 주입과 네비게이션 추상화가 잘 적용되었습니다.
@AndroidEntryPoint어노테이션과@Inject를 통해 Hilt의 의존성 주입을 잘 활용하고 있습니다. 특히NavigatorProvider를 주입받아 화면 전환 로직을 분리한 것은 좋은 설계입니다.Also applies to: 10-13, 15-22
23-31: 라이프사이클 메소드의 간결한 구현이 좋습니다.
onViewCreated메소드가 두 개의 메소드 호출로 간결하게 정리되었습니다. 이전 구현의 여러 설정 메소드 호출을 두 개의 함수로 통합하여 가독성이 향상되었습니다.
33-39: Flow를 사용한 UI 이벤트 수집이 잘 구현되었습니다.LiveData에서 Flow로의 마이그레이션이 잘 이루어졌으며,
collectWithLifecycle확장 함수를 사용하여 라이프사이클을 고려한 Flow 수집이 잘 구현되어 있습니다. 또한when표현식을 사용하여 UI 이벤트 처리가 명확합니다.
41-49: 클릭 리스너 설정이 잘 구현되었습니다.클릭 리스너 설정이 하나의 메소드로 통합되어 있고, 특히 로그아웃 버튼 클릭 시
singleEventHandler.throttleFirst를 사용하여 중복 클릭을 방지하는 것은 좋은 구현입니다.feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt (4)
15-27: ViewModel 초기화와 UI 이벤트 설정이 잘 구현되어 있습니다.Hilt를 사용한 ViewModel 의존성 주입이 잘 구현되어 있고, Channel과 Flow를 사용한 UI 이벤트 처리 방식이 적절합니다.
init블록에서 자동 스크롤을 시작하는 것도 좋은 접근입니다.
28-42: 소셜 로그인 구현이 깔끔하게 설계되어 있습니다.고차 함수를 매개변수로 받아 플랫폼별 로그인 로직을 추상화한 것은 좋은 설계입니다.
runCatching을 통한 예외 처리도 적절합니다.
43-68: 성공 및 실패 처리 로직이 명확하게 분리되어 있습니다.로그인 성공과 실패 시의 처리가 별도의 메소드로 분리되어 있어 가독성이 좋습니다. 특히 성공 시 사용자의 등록 상태에 따라 다른 화면으로 이동하는 로직이 명확합니다.
84-92: UI 이벤트를 위한 sealed interface가 잘 정의되어 있습니다.UI 이벤트를 표현하기 위한 sealed interface와 data object를 사용한 구현이 깔끔합니다. 각 이벤트의 목적이 명확하게 표현되어 있습니다.
core/network/src/main/java/com/into/websoso/core/network/di/NetworkModule.kt (1)
63-71: 공통 Dispatcher 싱글턴 사용 시 스로틀 값 튜닝을 검토하세요
Dispatcher().apply { maxRequestsPerHost = 20 }를 모든OkHttpClient에 공유하면, 향후 다른 모듈에서 별도 클라이언트를 생성할 때도 동일한 20 제한을 강제하게 됩니다. 모듈 단위로 연결 수요가 다를 수 있으니,
- DI 레벨에서 필요한 경우 다른 Dispatcher 인스턴스를 주입하거나,
- 프로젝트 전체 요구사항을 명시적으로 문서화해 두는 것을 권장합니다.
app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)
46-51: 버전 확인 결과가 예외로 실패했을 때 업데이트 대화상자를 열 가능성이 있습니다
checkMinimumVersion()에서 예외 발생 시getOrElse { false }로false를 반환한 뒤,also블록에서isRequired를 기준으로ShowDialog를 보냅니다. 하지만false인 경우에도ShowDialog를 보내지 않으므로 “실패 → 업데이트 불필요” 처리입니다.문제는 서버 연결 오류 등으로 업데이트 여부를 판단할 수 없어도 앱이 계속 진행된다는 점입니다. 네트워크 오류를 명시적으로 사용자에게 알리거나 재시도 로직을 넣을지 검토해 주세요.
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (1)
48-58:⚠️ Potential issueFCM 토큰 정리 로직이 실패 경로에서 누락되었습니다
deleteToken실패 시에도 화면은 로그인으로 이동하지만 디바이스의 FCM 토큰은 그대로 남아 서버에 유지될 가능성이 있습니다. 성공/실패 여부와 관계없이 토큰 정리를 보장하고,NavigateToLogin이벤트 중복 전송도 제거하는 형태가 안전합니다.-fun signOut() { - viewModelScope.launch { - runCatching { - val userDeviceIdentifier = userRepository.fetchUserDeviceIdentifier() - accountRepository.deleteToken(userDeviceIdentifier) - }.onSuccess { - pushMessageRepository.clearFCMToken() - _uiEffect.send(NavigateToLogin) - }.onFailure { - _uiEffect.send(NavigateToLogin) - } - } -} +fun signOut() { + viewModelScope.launch { + try { + val userDeviceIdentifier = userRepository.fetchUserDeviceIdentifier() + accountRepository.deleteToken(userDeviceIdentifier) + } catch (e: Exception) { + Timber.w(e) // 실패 로그 + } finally { + pushMessageRepository.clearFCMToken() + _uiEffect.emit(NavigateToLogin) // SharedFlow 사용 시 emit + } + } +}⛔ Skipped due to learnings
Learnt from: s9hn PR: Team-WSS/WSS-Android#687 File: app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt:0-0 Timestamp: 2025-05-15T17:11:19.809Z Learning: 로그아웃 성공 케이스에서만 FCM 토큰을 제거해야 합니다. 로그아웃 실패 시에는 사용자가 여전히 로그인 상태이므로 FCM 토큰을 유지해야 합니다.app/src/main/java/com/into/websoso/data/repository/AuthRepository.kt (1)
9-38: 간결화된 책임 분리가 인상적입니다
토큰 관리·세션 로직을 별도 모듈로 이동하여AuthRepository가 순수 원격 호출 역할만 수행하도록 만든 점이 깔끔합니다. 현재 코드에서 특별한 문제점은 보이지 않습니다.
| # Add project specific ProGuard rules here. | ||
| # You can control the set of applied configuration files using the | ||
| # proguardFiles setting in build.gradle. | ||
| # | ||
| # For more details, see | ||
| # http://developer.android.com/guide/developing/tools/proguard.html | ||
|
|
||
| # If your project uses WebView with JS, uncomment the following | ||
| # and specify the fully qualified class name to the JavaScript interface | ||
| # class: | ||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| # public *; | ||
| #} | ||
|
|
||
| # Uncomment this to preserve the line number information for | ||
| # debugging stack traces. | ||
| #-keepattributes SourceFile,LineNumberTable | ||
|
|
||
| # If you keep the line number information, uncomment this to | ||
| # hide the original source file name. | ||
| #-renamesourcefileattribute SourceFile No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
ProGuard 규칙이 실제로 적용되고 있는지 확인하세요
현재 파일에 활성화된 규칙이 없기 때문에, 이 설정이 릴리즈 빌드에 반영되는지 core/network 모듈의 build.gradle에서 proguardFiles에 포함되었는지 검증할 필요가 있습니다.
다음 스크립트로 확인할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# core/network 모듈에서 ProGuard 설정 파일 참조 여부 검사
grep -R "proguard-rules.pro" -n core/network/build.gradleLength of output: 116
core/network 모듈의 Gradle 설정 파일 위치 및 ProGuard 참조 여부 재확인
아래 스크립트로 core/network 디렉터리 내에 build.gradle 또는 build.gradle.kts 파일이 존재하는지, 그리고 그 안에 proguard-rules.pro가 참조되고 있는지를 함께 확인해 주세요:
🏁 Script executed:
#!/bin/bash
# 1. core/network 모듈의 Gradle 파일 확인
find core/network -maxdepth 2 -type f \( -name "build.gradle" -o -name "build.gradle.kts" \)
# 2. 해당 파일들에서 proguard-rules.pro 참조 여부 검색
grep -R "proguard-rules.pro" -n core/networkLength of output: 167
core/network/build.gradle.kts에 ProGuard 규칙 미적용
현재 core/network/build.gradle.kts에서 proguard-rules.pro를 참조하고 있지 않아, 릴리즈 빌드에 ProGuard 설정이 반영되지 않습니다. 아래를 확인하고 수정해 주세요:
- 파일 위치:
core/network/build.gradle.kts android { buildTypes { release { … } } }블록에proguardFiles설정 추가
예시 수정안 (Kotlin DSL):
android {
buildTypes {
release {
// 기존 설정…
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}🤖 Prompt for AI Agents
In core/network/build.gradle.kts around the android buildTypes release block,
the proguard-rules.pro file is not referenced, so ProGuard rules are not applied
in the release build. To fix this, add a proguardFiles entry inside the release
block that includes getDefaultProguardFile("proguard-android-optimize.txt") and
"proguard-rules.pro" to ensure the ProGuard rules from
core/network/proguard-rules.pro are applied during release builds.
| # Add project specific ProGuard rules here. | ||
| # You can control the set of applied configuration files using the | ||
| # proguardFiles setting in build.gradle. | ||
| # | ||
| # For more details, see | ||
| # http://developer.android.com/guide/developing/tools/proguard.html | ||
|
|
||
| # If your project uses WebView with JS, uncomment the following | ||
| # and specify the fully qualified class name to the JavaScript interface | ||
| # class: | ||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| # public *; | ||
| #} | ||
|
|
||
| # Uncomment this to preserve the line number information for | ||
| # debugging stack traces. | ||
| #-keepattributes SourceFile,LineNumberTable | ||
|
|
||
| # If you keep the line number information, uncomment this to | ||
| # hide the original source file name. | ||
| #-renamesourcefileattribute SourceFile No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
ProGuard 규칙 파일 템플릿 추가
아직 활성화된 규칙이 없으며, 주석으로만 구성된 기본 템플릿입니다. 해당 모듈의 build.gradle.kts 또는 build.gradle 파일에서 이 ProGuard 파일이 proguardFiles로 참조되고 있는지 확인하세요.
아래 스크립트를 실행하여 참조 여부를 검증할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# core/auth-kakao 모듈의 build 스크립트에서 proguard-rules.pro 참조 확인
rg -n "proguardFiles.*proguard-rules.pro" core/auth-kakao/build.gradle.ktsLength of output: 76
아직 proguard-rules.pro 파일이 실제로 참조되고 있는지 확인되지 않습니다. 아래 스크립트를 실행해 build.gradle.kts, build.gradle뿐만 아니라 라이브러리용 설정(consumerProguardFiles)까지 모두 검사해주세요:
🏁 Script executed:
#!/bin/bash
# core/auth-kakao 모듈 전체에서 ProGuard 룰 파일 참조 확인
# Gradle 스크립트에서 proguardFiles 사용 여부
rg -n "proguardFiles.*proguard-rules.pro" core/auth-kakao
# 라이브러리용 consumerProguardFiles 사용 여부
rg -n "consumerProguardFiles.*proguard-rules.pro" core/auth-kakao
# 일반적인 파일명 참조 여부
rg -n "proguard-rules.pro" core/auth-kakaoLength of output: 170
proguard-rules.pro 파일이 Gradle 설정에 참조되지 않음
현재 core/auth-kakao/proguard-rules.pro가 build.gradle(.kts)의 proguardFiles 또는 consumerProguardFiles에 포함되어 있지 않습니다. 아래 중 하나를 선택해 모듈 빌드 스크립트에 파일 참조를 추가해주세요.
• core/auth-kakao/build.gradle.kts 예시
android {
// …
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}• core/auth-kakao/build.gradle 예시
android {
// …
consumerProguardFiles "proguard-rules.pro"
}🤖 Prompt for AI Agents
In core/auth-kakao/proguard-rules.pro around lines 1 to 21, the ProGuard rules
file is not currently referenced in the module's build.gradle or
build.gradle.kts file. To fix this, add a reference to "proguard-rules.pro" in
the core/auth-kakao/build.gradle.kts or build.gradle file under the android
block using either proguardFiles or consumerProguardFiles as appropriate. For
example, in build.gradle.kts, add
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"), or in build.gradle, add consumerProguardFiles
"proguard-rules.pro" to ensure the ProGuard rules are applied during the build.
| plugins { | ||
| id("websoso.android.library") | ||
| id("websoso.android.compose") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
DI 모듈(Hilt) 플러그인 누락
core.common 모듈에서 Hilt를 사용하는 Dagger 모듈(DispatchersModule, Hilt 엔트리포인트 등)이 정의되어 있으므로, build.gradle.kts에 websoso.dagger 또는 dagger.hilt.android.plugin 플러그인 적용이 필요합니다.
🤖 Prompt for AI Agents
In core/common/build.gradle.kts around lines 3 to 6, the build script is missing
the Hilt plugin required for dependency injection modules like DispatchersModule
and Hilt entry points. Add the appropriate Hilt plugin, such as 'websoso.dagger'
or 'dagger.hilt.android.plugin', to the plugins block to enable Hilt support in
this module.
| plugins { | ||
| id("websoso.android.library") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
DI 플러그인(Hilt) 추가 권장
AccountRepository 등 의존성 주입을 사용하는 클래스가 포함될 가능성이 있으므로, websoso.dagger 또는 dagger.hilt.android.plugin 플러그인 적용을 권장합니다.
🤖 Prompt for AI Agents
In data/account/build.gradle.kts around lines 3 to 5, the current plugins block
only applies the websoso.android.library plugin. Since the module may include
classes like AccountRepository that use dependency injection, add the
appropriate DI plugin such as websoso.dagger or dagger.hilt.android.plugin to
the plugins block to enable Hilt support.
| interface AuthClient { | ||
| suspend fun signIn(): AuthToken | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
인터페이스에 문서화 추가 필요
인터페이스가 간결하고 명확하게 정의되어 있습니다. 다만, 이 인터페이스와 메서드의 목적, 예상 동작 및 오류 처리 방법에 대한 문서화가 필요합니다.
+/**
+ * 소셜 로그인과 같은 인증 서비스를 위한 클라이언트 인터페이스
+ * 각 인증 플랫폼(카카오, 네이버 등)은 이 인터페이스를 구현해야 함
+ */
interface AuthClient {
+ /**
+ * 사용자 로그인을 수행하고 인증 토큰을 반환합니다.
+ *
+ * @return 로그인 성공 시 인증 토큰
+ * @throws Exception 로그인 실패 시 (네트워크 오류, 인증 거부 등)
+ */
suspend fun signIn(): AuthToken
}🤖 Prompt for AI Agents
In core/auth/src/main/java/com/into/websoso/core/auth/AuthClient.kt at lines 3
to 5, the AuthClient interface and its signIn method lack documentation. Add
KDoc comments above the interface and the signIn function to describe their
purpose, expected behavior, and how errors should be handled. This will improve
code clarity and maintainability.
| suspend fun deleteToken(deviceIdentifier: String) { | ||
| accountRemoteDataSource | ||
| .postLogout( | ||
| refreshToken = refreshToken(), | ||
| deviceIdentifier = deviceIdentifier, | ||
| ).also { accountLocalDataSource.clearTokens() } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
토큰 삭제 로직의 finally 보장
원격 postLogout 실패 시 clearTokens() 가 호출되지 않아 클라이언트-서버 상태가 불일치할 수 있습니다. finally 블록에서 무조건 local 토큰을 지우도록 수정하는 편이 안전합니다.
-suspend fun deleteToken(deviceIdentifier: String) {
- accountRemoteDataSource
- .postLogout(
- refreshToken = refreshToken(),
- deviceIdentifier = deviceIdentifier,
- ).also { accountLocalDataSource.clearTokens() }
-}
+suspend fun deleteToken(deviceIdentifier: String) {
+ try {
+ accountRemoteDataSource.postLogout(
+ refreshToken = refreshToken(),
+ deviceIdentifier = deviceIdentifier,
+ )
+ } finally {
+ accountLocalDataSource.clearTokens()
+ }
+}🤖 Prompt for AI Agents
In data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt
around lines 37 to 43, the local tokens are only cleared if postLogout succeeds,
which risks client-server state inconsistency if postLogout fails. Refactor the
deleteToken function to call clearTokens() inside a finally block to guarantee
local token clearance regardless of postLogout success or failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai runcatching안에 같이 넣으면 보장되는거아냐?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@s9hn runCatching을 사용할 때 구조에 따라 결과가 달라집니다.
현재 deleteAccount와 유사한 방식으로 작성할 경우:
suspend fun deleteToken(deviceIdentifier: String): Result<Unit> =
runCatching {
accountRemoteDataSource.postLogout(...)
accountLocalDataSource.clearTokens()
}이 방식은 postLogout()이 예외를 발생시키면 그 시점에서 실행이 중단되고 clearTokens()는 호출되지 않습니다. 예외가 발생해도 로컬 토큰은 여전히 남아있게 됩니다.
토큰 삭제를 보장하려면 다음 방식을 사용해야 합니다:
suspend fun deleteToken(deviceIdentifier: String): Result<Unit> {
val result = runCatching {
accountRemoteDataSource.postLogout(...)
}
// 성공 여부와 관계없이 항상 실행됨
accountLocalDataSource.clearTokens()
return result
}또는 제안한 try-finally도 좋은 방법입니다. 그리고 다른 메소드들처럼 Result를 반환하여 일관성을 유지하는 것도 고려해볼 만합니다.
| @Composable | ||
| fun <T> Flow<T>.collectAsEventWithLifecycle( | ||
| lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle, | ||
| minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | ||
| context: CoroutineContext = EmptyCoroutineContext, | ||
| onEvent: suspend (T) -> Unit, | ||
| ) { | ||
| LaunchedEffect(this, lifecycle, minActiveState, context) { | ||
| lifecycle.repeatOnLifecycle(minActiveState) { | ||
| if (context == EmptyCoroutineContext) { | ||
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | ||
| } else { | ||
| withContext(context) { | ||
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
문서화된 예외 조건이 실제 코드에 반영되지 않음
KDoc 에서는 Lifecycle.State.INITIALIZED 전달 시 IllegalArgumentException 을 던진다고 명시하지만, 실제 구현에는 검사 로직이 없습니다. 작성자 의도대로 동작하게 하려면 아래와 같이 요구사항을 명시적으로 확인해 주세요.
LaunchedEffect(this, lifecycle, minActiveState, context) {
+ require(minActiveState != Lifecycle.State.INITIALIZED) {
+ "minActiveState cannot be Lifecycle.State.INITIALIZED"
+ }
lifecycle.repeatOnLifecycle(minActiveState) {📝 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.
| @Composable | |
| fun <T> Flow<T>.collectAsEventWithLifecycle( | |
| lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle, | |
| minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | |
| context: CoroutineContext = EmptyCoroutineContext, | |
| onEvent: suspend (T) -> Unit, | |
| ) { | |
| LaunchedEffect(this, lifecycle, minActiveState, context) { | |
| lifecycle.repeatOnLifecycle(minActiveState) { | |
| if (context == EmptyCoroutineContext) { | |
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | |
| } else { | |
| withContext(context) { | |
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| @Composable | |
| fun <T> Flow<T>.collectAsEventWithLifecycle( | |
| lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle, | |
| minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | |
| context: CoroutineContext = EmptyCoroutineContext, | |
| onEvent: suspend (T) -> Unit, | |
| ) { | |
| LaunchedEffect(this, lifecycle, minActiveState, context) { | |
| require(minActiveState != Lifecycle.State.INITIALIZED) { | |
| "minActiveState cannot be Lifecycle.State.INITIALIZED" | |
| } | |
| lifecycle.repeatOnLifecycle(minActiveState) { | |
| if (context == EmptyCoroutineContext) { | |
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | |
| } else { | |
| withContext(context) { | |
| this@collectAsEventWithLifecycle.collect { onEvent(it) } | |
| } | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
core/common/src/main/java/com/into/websoso/core/common/extensions/FlowExtensions.kt
between lines 41 and 59, the function collectAsEventWithLifecycle lacks the
check for Lifecycle.State.INITIALIZED that the KDoc states should throw an
IllegalArgumentException. Add an explicit validation at the start of the
function to check if minActiveState is Lifecycle.State.INITIALIZED and throw
IllegalArgumentException if so, ensuring the code behavior matches the
documented contract.
feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt
Outdated
Show resolved
Hide resolved
| runCatching { accountRepository.renewToken() } | ||
| .onSuccess { | ||
| _uiEffect.send(NavigateToMain) | ||
| }.onFailure { | ||
| it.printStackTrace() | ||
| _uiEffect.send(NavigateToLogin) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
토큰 재발급 실패 시 세션 정리가 필요합니다
renewToken()이 실패하면 단순히 NavigateToLogin만 발생시키고 있는데, 로컬에 저장된 만료 액세스/리프레시 토큰은 그대로 남아 있을 가능성이 있습니다. 이후 로그인 화면으로 이동했다가 다시 앱을 시작하면 동일한 실패를 반복할 수 있으니,
accountRepository.clearTokens()같은 정리 메서드를 호출해 두는 편이 안정적입니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt around lines
59 to 64, when renewToken() fails, the code only triggers NavigateToLogin
without clearing stored tokens. To fix this, call
accountRepository.clearTokens() inside the onFailure block before sending
NavigateToLogin to ensure local expired tokens are removed and prevent repeated
failures on app restart.
| private val _uiEffect = Channel<UiEffect>(Channel.BUFFERED) | ||
| val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow() | ||
|
|
||
| init { | ||
| checkAndUpdateVersion() | ||
| } | ||
|
|
||
| private fun checkAndUpdateVersion() { | ||
| viewModelScope.launch { | ||
| runCatching { | ||
| versionRepository.isUpdateRequired() | ||
| }.onSuccess { isRequired -> | ||
| _isUpdateRequired.value = isRequired | ||
| } | ||
| val isUpdateRequired = checkMinimumVersion() | ||
| if (isUpdateRequired.not()) handleAutoLogin() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Channel → MutableSharedFlow 사용을 고려하면 안정성과 테스트 편의성이 향상됩니다
Channel.BUFFERED는 ViewModel 파괴 시 명시적으로 닫지 않으면 누수 가능성이 있으며, UI 레벨에서 collect 직전에 동시 수신자가 생기면 초기 이벤트가 유실될 수 있습니다. MutableSharedFlow(replay = 0 또는 1)로 교체하면
• 라이프사이클 내구성 및 back-pressure 처리가 단순해지고
• 테스트 코드에서 testIn 등으로 손쉽게 검증할 수 있습니다.
-private val _uiEffect = Channel<UiEffect>(Channel.BUFFERED)
-val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow()
+private val _uiEffect = MutableSharedFlow<UiEffect>(extraBufferCapacity = 1)
+val uiEffect: Flow<UiEffect> get() = _uiEffect그리고 send() 대신 tryEmit()을 사용해 suspend 오버헤드를 제거할 수 있습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private val _uiEffect = Channel<UiEffect>(Channel.BUFFERED) | |
| val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow() | |
| init { | |
| checkAndUpdateVersion() | |
| } | |
| private fun checkAndUpdateVersion() { | |
| viewModelScope.launch { | |
| runCatching { | |
| versionRepository.isUpdateRequired() | |
| }.onSuccess { isRequired -> | |
| _isUpdateRequired.value = isRequired | |
| } | |
| val isUpdateRequired = checkMinimumVersion() | |
| if (isUpdateRequired.not()) handleAutoLogin() | |
| } | |
| // app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt | |
| import kotlinx.coroutines.flow.MutableSharedFlow | |
| import kotlinx.coroutines.flow.Flow | |
| // … | |
| private val _uiEffect = MutableSharedFlow<UiEffect>(extraBufferCapacity = 1) | |
| val uiEffect: Flow<UiEffect> get() = _uiEffect | |
| init { | |
| viewModelScope.launch { | |
| val isUpdateRequired = checkMinimumVersion() | |
| if (isUpdateRequired.not()) handleAutoLogin() | |
| } | |
| } | |
| // Later, when emitting: | |
| // replace _uiEffect.send(effect) with _uiEffect.tryEmit(effect) |
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt around lines
26 to 33, replace the use of Channel<UiEffect> with MutableSharedFlow<UiEffect>
to improve lifecycle durability and testability. Initialize a private
MutableSharedFlow with replay set to 0 or 1, expose it as a Flow, and replace
all send() calls with tryEmit() to avoid suspend overhead. This change prevents
potential memory leaks and event loss issues related to Channel usage in
ViewModel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (1)
37-46:⚠️ Potential issue
deleteTokens함수의 토큰 삭제 보장 개선 필요
deleteTokens메서드가runCatching을 사용하여 원격 호출 실패 시 Result.failure를 반환하는 것은 좋습니다. 그러나postLogout호출이 실패할 경우 로컬 토큰이 삭제되지 않을 수 있습니다. 이전 리뷰 코멘트에서 권장한 대로finally블록을 사용하여 원격 호출 성공 여부와 관계없이 로컬 토큰을 항상 삭제하도록 수정하는 것이 좋습니다.suspend fun deleteTokens(deviceIdentifier: String): Result<Unit> = runCatching { - accountRemoteDataSource - .postLogout( - refreshToken = refreshToken(), - deviceIdentifier = deviceIdentifier, - ) - - accountLocalDataSource.clearTokens() + try { + accountRemoteDataSource + .postLogout( + refreshToken = refreshToken(), + deviceIdentifier = deviceIdentifier, + ) + } finally { + accountLocalDataSource.clearTokens() + } }app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt (1)
24-26: 이전 리뷰 코멘트가 적용되어 Compose 전용 UI로 개선되었습니다이전 리뷰에서 지적된 ViewBinding과 Compose 혼용 문제를 해결하고 Compose 전용 UI로 전환한 것은 좋은 개선입니다. 다만, 아직 생성자에서
activity_login레이아웃을 참조하고 있어 완전히 해결되지 않았습니다. XML 레이아웃 참조를 제거하는 것이 더 좋을 것입니다.TODO 주석에서 언급한 CompositionLocal 주입 방식은 좋은 개선 방향입니다. 의존성 주입을 더 Compose 친화적으로 만들 수 있습니다.
-class LoginActivity : AppCompatActivity(activity_login) { +class LoginActivity : AppCompatActivity() {
🧹 Nitpick comments (1)
app/src/main/res/layout/activity_login.xml (1)
2-8: XML 레이아웃이 Compose로 대체되어 간소화되었습니다로그인 화면 레이아웃이 대폭 간소화되었습니다. 이는 LoginActivity에서 Jetpack Compose로 UI를 구현하는 방식으로 변경되었기 때문입니다. 이 빈 ConstraintLayout은 Compose UI의 컨테이너 역할만 하고 있습니다.
다만, Compose만 사용한다면 이 XML 파일 자체가 불필요할 수 있습니다. AppCompatActivity 생성자에 레이아웃 ID를 전달하지 않고 Compose만 사용하는 방식으로 리팩토링하면 더 깔끔할 것입니다.
-class LoginActivity : AppCompatActivity(activity_login) { +class LoginActivity : AppCompatActivity() {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt(3 hunks)app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt(1 hunks)app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt(3 hunks)app/src/main/res/layout/activity_login.xml(1 hunks)core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/KakaoAuthClient.kt(1 hunks)core/auth/src/main/java/com/into/websoso/core/auth/AuthPlatform.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- core/auth/src/main/java/com/into/websoso/core/auth/AuthPlatform.kt
- core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt
- app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt
- core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/KakaoAuthClient.kt
- app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt
- app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt
- feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (2)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)
refreshToken(6-6)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
refreshToken(29-33)
app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt (1)
feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (2)
SignInScreen(34-71)SignInScreen(73-92)
🪛 detekt (1.23.7)
app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt
[warning] 42-42: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
🔇 Additional comments (3)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
21-35:saveTokens메서드가 Result 타입을 반환하도록 개선되었습니다
runCatching을 사용하여 원격 호출 및 토큰 저장 작업을 감싸고 Result 타입을 반환함으로써 예외 처리가 잘 개선되었습니다. 이제 호출하는 측에서 성공/실패를 명확하게 처리할 수 있습니다.
54-62: 리프레시 토큰 갱신 처리가 잘 구현되었습니다
renewTokens메서드에서runCatching을 사용하여 예외 처리를 하고 Result 타입을 반환하는 것은 좋은 방식입니다. 원격 호출과 로컬 데이터 저장 로직이 잘 조합되어 있습니다.app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt (1)
37-47: 엣지 투 엣지 UI와 Compose 통합이 잘 구현되었습니다
enableEdgeToEdge()를 호출하고 Compose UI를 설정하는 방식으로 모던 Android UI 패턴을 잘 적용했습니다. 의존성 주입된authClient와websosoNavigator를 SignInScreen에 전달하는 방식도 좋습니다.향후 CompositionLocal을 통한 의존성 주입을 구현하면 컴포저블 간의 의존성 전달이 더 간결해질 수 있습니다.
🧰 Tools
🪛 detekt (1.23.7)
[warning] 42-42: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
| suspend fun deleteAccount(reason: String): Result<Unit> = | ||
| runCatching { | ||
| accountRemoteDataSource.postWithdraw(reason = reason) | ||
| accountLocalDataSource.clearTokens() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleteAccount 함수에서도 토큰 삭제 보장 필요
deleteAccount 함수도 deleteTokens와 동일한 문제가 있습니다. 원격 postWithdraw 호출이 실패하면 로컬 토큰이 삭제되지 않습니다. 따라서 클라이언트-서버 상태가 불일치할 수 있습니다. finally 블록을 사용하여 무조건 로컬 토큰을 지우도록 수정하는 것이 좋습니다.
suspend fun deleteAccount(reason: String): Result<Unit> =
runCatching {
- accountRemoteDataSource.postWithdraw(reason = reason)
- accountLocalDataSource.clearTokens()
+ try {
+ accountRemoteDataSource.postWithdraw(reason = reason)
+ } finally {
+ accountLocalDataSource.clearTokens()
+ }
}📝 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.
| suspend fun deleteAccount(reason: String): Result<Unit> = | |
| runCatching { | |
| accountRemoteDataSource.postWithdraw(reason = reason) | |
| accountLocalDataSource.clearTokens() | |
| } | |
| suspend fun deleteAccount(reason: String): Result<Unit> = | |
| runCatching { | |
| try { | |
| accountRemoteDataSource.postWithdraw(reason = reason) | |
| } finally { | |
| accountLocalDataSource.clearTokens() | |
| } | |
| } |
🤖 Prompt for AI Agents
In data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt
around lines 48 to 52, the deleteAccount function currently deletes local tokens
only if the remote postWithdraw call succeeds, risking client-server state
inconsistency if postWithdraw fails. Modify the function to use a finally block
that ensures accountLocalDataSource.clearTokens() is called regardless of
whether postWithdraw succeeds or throws an exception, guaranteeing local token
deletion in all cases.
| authClient = { platform -> | ||
| authClient[platform] ?: throw IllegalStateException() | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
예외 메시지 없이 IllegalStateException을 던지는 코드 개선 필요
authClient[platform]이 null일 경우 메시지 없이 IllegalStateException을 던지고 있습니다. 디버깅과 문제 해결을 위해 구체적인 오류 메시지를 추가하는 것이 좋습니다.
- authClient[platform] ?: throw IllegalStateException()
+ authClient[platform] ?: throw IllegalStateException("AuthClient for platform $platform not found")📝 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.
| authClient = { platform -> | |
| authClient[platform] ?: throw IllegalStateException() | |
| }, | |
| authClient = { platform -> | |
| - authClient[platform] ?: throw IllegalStateException() | |
| + authClient[platform] ?: throw IllegalStateException("AuthClient for platform $platform not found") | |
| }, |
🧰 Tools
🪛 detekt (1.23.7)
[warning] 42-42: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/login/LoginActivity.kt around lines 41
to 43, the code throws an IllegalStateException without a message when
authClient[platform] is null. Improve this by adding a clear and specific error
message to the IllegalStateException to aid debugging and clarify the cause of
the exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
40-49: 토큰 삭제 로직의 finally 보장 필요원격
postLogout실패 시clearTokens()가 호출되지 않아 클라이언트-서버 상태가 불일치할 수 있습니다.runCatching내부에서try-finally블록을 사용하여 무조건 local 토큰을 지우도록 수정하는 편이 안전합니다.suspend fun deleteTokens(deviceIdentifier: String): Result<Unit> = runCatching { - accountRemoteDataSource - .postLogout( - refreshToken = refreshToken(), - deviceIdentifier = deviceIdentifier, - ) - - accountLocalDataSource.clearTokens() + try { + accountRemoteDataSource + .postLogout( + refreshToken = refreshToken(), + deviceIdentifier = deviceIdentifier, + ) + } finally { + accountLocalDataSource.clearTokens() + } }
51-55:deleteAccount함수에서도 토큰 삭제 보장 필요
deleteAccount함수도deleteTokens와 동일한 문제가 있습니다. 원격postWithdraw호출이 실패하면 로컬 토큰이 삭제되지 않습니다. 따라서 클라이언트-서버 상태가 불일치할 수 있습니다.finally블록을 사용하여 무조건 로컬 토큰을 지우도록 수정하는 것이 좋습니다.suspend fun deleteAccount(reason: String): Result<Unit> = runCatching { - accountRemoteDataSource.postWithdraw(reason = reason) - accountLocalDataSource.clearTokens() + try { + accountRemoteDataSource.postWithdraw(reason = reason) + } finally { + accountLocalDataSource.clearTokens() + } }
🧹 Nitpick comments (1)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (1)
17-19: 이 TODO 주석을 해결하거나 추적 계획 수립 필요현재 이 속성에 대한 TODO 주석이 있습니다. 리팩토링의 일부로 이 속성이
UserRepository로 이동될 예정인지, 그리고 그 일정이 어떻게 되는지 명확히 하는 것이 좋습니다.이 속성이 다른 곳으로 이동할 예정이라면, 지금 이동시키거나 관련 이슈를 생성하여 추적하는 것이 어떨까요?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt
- core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)
refreshToken(6-6)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
refreshToken(29-33)
🔇 Additional comments (2)
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2)
25-38: 토큰 저장 로직 개선이 이루어졌습니다이전 리뷰에서 지적된 예외 처리 문제가
runCatching을 사용하여 적절히 해결되었습니다. 함수 이름도saveTokens로 명확하게 변경되었습니다.
57-63: 토큰 갱신 로직 검토토큰 갱신 로직이 적절하게 구현되었습니다. 다만, 다른 메서드들과의 일관성을 위해 리모트 호출과 로컬 저장 사이에 예외가 발생할 경우를 고려할 수 있습니다. 특히 토큰 갱신이 실패했을 때 기존 토큰을 어떻게 처리할지 명확히 하는 것이 좋을 것 같습니다.
클라이언트-서버 간 토큰 상태가 불일치하는 경우를 방지하기 위한 추가 로직이 필요할 수 있습니다. 이 부분에 대해 팀 내에서 논의가 필요합니다.
m6z1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기나긴 토큰 여정
정말 고생 많으셨습니다 👍👍 👍 👍
junseo511
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정말로 포트폴리오 한 줄을 챙길 정도의,,,
고생 많으셨습니다 👏
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
길고 길었던 소셜로그인 재구현 이슈를 이렇게 마무리하게 되었습니다..
기존 대비 성능 비교를.. 제대로 하려고 했으나, 현 'develop' 브랜치의 소셜로그인 기능은 전혀 정상 작동하지 못했습니다.
성능 비교는 Network Inspector, Profiler, Logcat을 사용했습니다.
✅ 자동 로그인 시나리오 QA
✅ 인증/인가 시나리오 QA
develop)엑세스 토큰 만료 시나리오 (홈 → 피드 → 서재 → 마이페이지 → 홈)
문제
Authenticator가 약 2초간 무한 반복 호출됨 (약 20회)리프레시 토큰 만료 시 (홈 → 마이페이지 → 로그인 → 홈)
문제
🚀 개선점 (
feat/657)엑세스 토큰 만료 시나리오 성능 비교
리프레시 토큰 만료 시나리오 성능 비교
이렇게 마무리하도록 하겠습니다! 긴 여정 리뷰해주셔서 감사합니다 :)