Skip to content

Feat/#43 자동 로그인 구현#44

Merged
starshape7 merged 6 commits intodevelopfrom
feat/#43-자동-로그인-구현
Feb 16, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#43-\uc790\ub3d9-\ub85c\uadf8\uc778-\uad6c\ud604"
Merged

Feat/#43 자동 로그인 구현#44
starshape7 merged 6 commits intodevelopfrom
feat/#43-자동-로그인-구현

Conversation

@starshape7
Copy link
Collaborator

@starshape7 starshape7 commented Feb 2, 2026

Related issue 🛠

  • closed

Work Description 📝

  • 자동로그인 구현 및 logout 마무리
  • api 호출 시 accessToken 만료되었으면 refreshToken으로 갱신 후 재호출

Screenshot 📸

Uncompleted Tasks 😅

  • 자동 로그인시 LandingScreen 보임

PR Point 📌

트러블 슈팅 💥

Summary by CodeRabbit

릴리스 노트

새로운 기능

  • 자동 토큰 갱신 기반 인증 도입 및 자동 재시도 처리
  • 앱 시작 스플래시로 자동 로그인 시도 추가
  • 명시적 로그아웃 기능과 마이페이지에서의 로그아웃 흐름 지원
  • 스플래시 애니메이션 UI 컴포넌트 추가

버그 수정

  • 인증 실패 시 세션(토큰/유저) 자동 정리

개선사항

  • 로그인 및 토큰 갱신 흐름 전반적인 사용자 경험 개선

@starshape7 starshape7 linked an issue Feb 2, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Walkthrough

토큰 기반 인증자와 토큰 재발급 서비스, 데이터스토어 연동을 추가하고 스플래시에서 자동 로그인(토큰 재발급) 흐름을 구현합니다.

Changes

Cohort / File(s) Summary
Token Authentication
core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt
OkHttp Authenticator 추가: 데이터스토어에서 토큰 조회, 동시 재발급 방지, 재발급 시도 및 실패 시 인증 데이터 삭제 로직 구현.
Network DI & Qualifiers
core/network/src/main/java/com/umcspot/spot/network/di/NetworkModule.kt, core/network/src/main/java/com/umcspot/spot/network/di/Qualifier.kt
TokenAuthenticator을 Spot API 클라이언트에 연결하고, 토큰 재발급 전용 OkHttpClient/Retrofit/TokenRefreshService를 제공하며 @SpotRefreshApi 한정자 추가.
Token Models & Service
core/network/src/main/java/com/umcspot/spot/network/model/TokenRefreshResponse.kt, core/network/src/main/java/com/umcspot/spot/network/service/TokenRefreshService.kt
토큰 재발급 응답 DTO (TokenRefreshResponse) 및 Retrofit 인터페이스(TokenRefreshService) 추가(@POST /api/auth/reissue).
Login Data Layer
data/login/src/main/java/com/umcspot/spot/login/datasource/*.kt, data/login/src/main/java/com/umcspot/spot/login/datasourceimpl/*.kt, data/login/src/main/java/com/umcspot/spot/login/service/LoginService.kt
소셜 로그인 메서드명 변경, 토큰 재발급 및 로그아웃 엔드포인트/메서드 추가 및 구현 위임.
Login Repository
data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt
LoginDataSource 기반으로 리팩터링, 토큰 재발급 및 로그아웃 흐름 구현(데이터스토어 업데이트/삭제).
Token Repository API
domain/token/src/main/java/com/umcspot/spot/token/repository/TokenRepository.kt
finishSocialLogin 반환 타입을 Result<Unit>으로 변경하고 refreshTokenData, spotLogout 메서드 추가.
MyPage Feature
feature/mypage/build.gradle.kts, feature/mypage/src/main/java/.../MyPageScreen.kt, .../MyPageViewModel.kt
TokenRepository 의존성 추가, MyPage에서 로그아웃 호출을 coroutine scope로 실행.
Signup Navigation & Landing
feature/signup/src/main/java/.../LandingScreen.kt, .../LandingState.kt, .../navigation/SignUpNavigation.kt
NavigateToHome → NavigateToSignUp 변경, 네비게이션 파라미터 추가(Splash route 포함).
Splash Screen & Auto-Login
feature/signup/src/main/java/.../splash/*.kt
스플래시 UI/상태/뷰모델 추가, 앱 시작 시 최소 대기 후 토큰 재발급 시도 및 성공/실패에 따라 NavigateToHome/NavigateToLanding 방출.
Design System
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/splash.kt
Lottie 기반 Splash 컴포저블 추가.
Misc
app/build.gradle.kts, 기타 파일
주석된 signingConfigs 제거 등 잡다한 정리 및 포맷 변경.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant TokenAuth as TokenAuthenticator
    participant DataStore as Token DataStore
    participant RefreshSvc as TokenRefreshService

    Client->>TokenAuth: authenticate(response)
    TokenAuth->>DataStore: read current tokens
    DataStore-->>TokenAuth: accessToken, refreshToken
    alt valid newer accessToken exists
        TokenAuth->>Client: retry request with latest accessToken
    else refresh needed
        TokenAuth->>TokenAuth: enter synchronized refreshLock
        TokenAuth->>RefreshSvc: refreshTokenData(refreshToken)
        RefreshSvc-->>TokenAuth: TokenRefreshResponse(success) / error
        alt refresh success
            TokenAuth->>DataStore: write new access/refresh tokens & userId
            DataStore-->>TokenAuth: ack
            TokenAuth->>Client: retry request with new accessToken
        else refresh failure
            TokenAuth->>DataStore: clear auth data
            TokenAuth->>Client: return null (stop retry)
        end
    end
Loading
sequenceDiagram
    participant Splash as SplashScreen
    participant VM as SplashViewModel
    participant Repo as TokenRepository
    participant DataStore as Token DataStore

    Splash->>VM: tryAutoLogin()
    VM->>VM: set hasStartedAutoLogin, record startTime
    VM->>Repo: refreshTokenData()
    Repo->>DataStore: read stored tokens
    DataStore-->>Repo: accessToken, refreshToken
    Repo->>RefreshSvc: (via network) request reissue
    RefreshSvc-->>Repo: success / failure
    alt success
        Repo-->>VM: Result.success
        VM->>VM: emit NavigateToHome, uiState.successAutoLogin = true
        Splash->>App: navigateToHome()
    else failure
        Repo-->>VM: Result.failure
        VM->>VM: emit NavigateToLanding, uiState.successAutoLogin = false
        Splash->>App: navigateToLanding()
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

🍒 [FEAT]

Suggested reviewers

  • fredleeJH

Poem

🐰 토큰이 춤추는 아침에 깨어,
재발급 바람 타고 길을 떠나네.
스플래시 빛 아래 자동 로그인 꿈꾸며,
데이터스토어에 씨앗 심고 도약하노라.
함께 안전한 여정, 홀이요! 🌱

🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Invalid branch name format
Description check ❓ Inconclusive PR 설명이 관련 이슈 번호가 빠져 있으며, PR Point와 트러블 슈팅 섹션이 비어 있고, 해결되지 않은 작업이 명시되어 있습니다. 관련 이슈 번호를 추가하고, PR Point와 트러블 슈팅 섹션을 작성하거나 불필요한 섹션을 제거해 주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 자동 로그인 구현이라는 주요 변경 사항을 명확하게 요약하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#43-자동-로그인-구현
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/#43-자동-로그인-구현
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt`:
- Around line 62-69: The current runBlocking block around
tokenRefreshService.refreshTokenData(refreshToken) swallows exceptions without
logging, hindering diagnostics; update the catch to log the caught exception
(including message and stack trace) before calling clearAuthData() and returning
null — reference the TokenAuthenticator class, the runBlocking block where
refreshResponse is assigned, tokenRefreshService.refreshTokenData(...), and
clearAuthData() so you add a logger call (e.g., logger.error or appropriate
logging utility used in this class) that includes the exception object.

In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`:
- Around line 96-101: The logout flow currently calls viewmodel.logout() but
ignores its Result and always invokes onLogoutClick(), so handle the Result from
viewmodel.logout() inside the scope.launch: call onLogoutClick() only when the
Result indicates success; on failure, do not call onLogoutClick() and instead
surface the error (e.g., show a toast/snackbar or log via viewmodel/UiEvent) so
the user is informed and server session cleanup is respected; update the block
surrounding onLogoutClick, scope.launch, and viewmodel.logout() to branch on
success/failure accordingly.

In
`@feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt`:
- Around line 58-60: The screen currently calls viewModel.tryAutoLogin() inside
LaunchedEffect but provides no UI feedback while viewModel.isLoading is true;
update the LandingScreen composable to observe the viewModel.isLoading state and
render a visible loading indicator or full-screen overlay (e.g., a centered
CircularProgressIndicator or semi-opaque overlay that blocks input) while
isLoading is true, disabling buttons beneath; keep the
LaunchedEffect/tryAutoLogin call as-is and ensure the loading UI is
conditionally shown/hidden based on the viewModel.isLoading property.
🧹 Nitpick comments (1)
data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt (1)

14-18: 파라미터 네이밍 개선 필요

loginDataStore 파라미터가 실제로는 LoginDataSource 타입입니다. loginDataSource로 이름을 변경하면 코드의 명확성이 향상됩니다.

🔧 제안하는 수정
 class LoginRepositoryImpl `@Inject` constructor(
-    private val loginDataStore: LoginDataSource,
+    private val loginDataSource: LoginDataSource,
     private val spotTokenDataStore: DataStore<SpotTokenData>,
     private val spotUserIdDataStore: DataStore<SpotUserIdData>
 ) : TokenRepository {

Comment on lines +62 to +69
val refreshResponse = runBlocking {
try {
tokenRefreshService.refreshTokenData(refreshToken)
} catch (e: Exception) {
clearAuthData()
null
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

예외가 삼켜지고 있어 디버깅이 어려움

토큰 갱신 실패 시 예외가 로깅 없이 삼켜지고 있습니다. 프로덕션 환경에서 토큰 갱신 문제를 진단하기 어려워질 수 있습니다.

🛠️ 로깅 추가 제안
             val refreshResponse = runBlocking {
                 try {
                     tokenRefreshService.refreshTokenData(refreshToken)
                 } catch (e: Exception) {
+                    android.util.Log.e("TokenAuthenticator", "Token refresh failed", e)
                     clearAuthData()
                     null
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val refreshResponse = runBlocking {
try {
tokenRefreshService.refreshTokenData(refreshToken)
} catch (e: Exception) {
clearAuthData()
null
}
}
val refreshResponse = runBlocking {
try {
tokenRefreshService.refreshTokenData(refreshToken)
} catch (e: Exception) {
android.util.Log.e("TokenAuthenticator", "Token refresh failed", e)
clearAuthData()
null
}
}
🧰 Tools
🪛 detekt (1.23.8)

[warning] 65-65: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🤖 Prompt for AI Agents
In `@core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt`
around lines 62 - 69, The current runBlocking block around
tokenRefreshService.refreshTokenData(refreshToken) swallows exceptions without
logging, hindering diagnostics; update the catch to log the caught exception
(including message and stack trace) before calling clearAuthData() and returning
null — reference the TokenAuthenticator class, the runBlocking block where
refreshResponse is assigned, tokenRefreshService.refreshTokenData(...), and
clearAuthData() so you add a logger call (e.g., logger.error or appropriate
logging utility used in this class) that includes the exception object.

Comment on lines +96 to +101
onLogoutClick = {
scope.launch {
viewmodel.logout()
onLogoutClick()
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

로그아웃 실패 시 에러 처리 누락

viewmodel.logout()Result를 처리하지 않고 있습니다. API 호출이 실패해도 onLogoutClick()이 호출되어 사용자는 로그아웃이 완료된 것으로 인식하지만, 실제로는 서버 세션이 정리되지 않을 수 있습니다.

🐛 에러 처리 추가 제안
         onLogoutClick = {
             scope.launch {
-                viewmodel.logout()
-                onLogoutClick()
+                viewmodel.logout()
+                    .onSuccess {
+                        onLogoutClick()
+                    }
+                    .onFailure {
+                        // TODO: 로그아웃 실패 시 사용자에게 알림 (예: Snackbar, Toast)
+                    }
             }
         }
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`
around lines 96 - 101, The logout flow currently calls viewmodel.logout() but
ignores its Result and always invokes onLogoutClick(), so handle the Result from
viewmodel.logout() inside the scope.launch: call onLogoutClick() only when the
Result indicates success; on failure, do not call onLogoutClick() and instead
surface the error (e.g., show a toast/snackbar or log via viewmodel/UiEvent) so
the user is informed and server session cleanup is respected; update the block
surrounding onLogoutClick, scope.launch, and viewmodel.logout() to branch on
success/failure accordingly.

Comment on lines 58 to 60
LaunchedEffect(Unit) {
viewModel.tryAutoLogin()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

자동 로그인 중 UI 피드백이 없어 혼동될 수 있습니다.
자동 로그인 동안 isLoading이 true인데 화면에는 변화가 없어 버튼이 무반응처럼 보일 수 있습니다. 로딩 인디케이터/오버레이 등 최소한의 피드백을 추가해 주세요.

🤖 Prompt for AI Agents
In
`@feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt`
around lines 58 - 60, The screen currently calls viewModel.tryAutoLogin() inside
LaunchedEffect but provides no UI feedback while viewModel.isLoading is true;
update the LandingScreen composable to observe the viewModel.isLoading state and
render a visible loading indicator or full-screen overlay (e.g., a centered
CircularProgressIndicator or semi-opaque overlay that blocks input) while
isLoading is true, disabling buttons beneath; keep the
LaunchedEffect/tryAutoLogin call as-is and ensure the loading UI is
conditionally shown/hidden based on the viewModel.isLoading property.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt (3)

69-69: ⚠️ Potential issue | 🟠 Major

액세스 토큰이 로그에 노출되고 있습니다.

Line 69와 Line 87에서 카카오/네이버 액세스 토큰을 Log.i로 출력하고 있습니다. 프로덕션 빌드에서도 노출될 수 있으므로 토큰 값을 로그에 포함하지 않거나, 최소한 Log.d로 변경하고 릴리스 빌드에서는 제거되도록 처리해 주세요.

Also applies to: 87-87


64-75: ⚠️ Potential issue | 🟡 Minor

카카오 콜백에서 error == null && token == null인 경우 처리가 누락되어 UI가 빈 화면으로 남을 수 있습니다.

errortoken이 모두 null인 경우 어느 분기도 실행되지 않아 successAutoLogintrue로 유지되고, 사용자에게 빈 화면이 보입니다.

🛡️ else 분기 추가 제안
             UserApiClient.instance.loginWithKakaoAccount(context) { token, error ->
                 if (error != null) {
                     handleLoginError("카카오 로그인 실패", error)
                 } else if (token != null) {
                     Log.i(TAG, "카카오 로그인 성공: ${token.accessToken}")
                     requestServerLogin(SocialLoginType.KAKAO, token.accessToken)
+                } else {
+                    handleLoginError("카카오 로그인: 알 수 없는 오류")
                 }
             }

62-76: ⚠️ Potential issue | 🟠 Major

loginWithKakao()Activity 컨텍스트 전달이 필요합니다.

loginWithKakao()는 생성자에서 주입된 @ApplicationContext context를 사용하지만, Kakao SDK의 loginWithKakaoAccount는 로그인/동의 화면을 표시하기 위해 Activity 컨텍스트가 필요합니다. startSocialLogin()에서 받은 activity 파라미터가 전달되지 않고 있으며, 비교하면 loginWithNaver(activity)는 올바르게 activity를 전달하고 있습니다.

또한 접근 토큰이 로그에 기록되고 있습니다 (69줄, 87줄). 민감한 토큰은 로그에 남기지 않아야 합니다.

🐛 수정 제안
-    private fun loginWithKakao() = viewModelScope.launch {
+    private fun loginWithKakao(activity: Activity) = viewModelScope.launch {
 
         try {
-            UserApiClient.instance.loginWithKakaoAccount(context) { token, error ->
+            UserApiClient.instance.loginWithKakaoAccount(activity) { token, error ->
                 if (error != null) {
                     handleLoginError("카카오 로그인 실패", error)
                 } else if (token != null) {
-                    Log.i(TAG, "카카오 로그인 성공: ${token.accessToken}")
+                    Log.i(TAG, "카카오 로그인 성공")
                     requestServerLogin(SocialLoginType.KAKAO, token.accessToken)

startSocialLogin 호출부도 수정:

         when (type) {
-            SocialLoginType.KAKAO -> loginWithKakao()
+            SocialLoginType.KAKAO -> loginWithKakao(activity)
             SocialLoginType.NAVER -> loginWithNaver(activity)
         }

네이버 로그인도 같은 방식으로 수정:

                 Log.i(TAG, "네이버 로그인 성공: $accessToken")
+                Log.i(TAG, "네이버 로그인 성공")
🧹 Nitpick comments (2)
feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingState.kt (1)

3-5: successAutoLogin 네이밍이 실제 용도와 불일치합니다.

ViewModel 코드를 보면 successAutoLogin은 로그인 시도 전true로 설정되고, 성공/실패 여부와 관계없이 완료 후 false로 재설정됩니다. 이는 의미적으로 isLoading 또는 isAutoLoginInProgress에 해당하며, "성공"을 나타내는 플래그가 아닙니다. 현재 이름은 코드 읽는 사람에게 혼란을 줄 수 있습니다.

♻️ 네이밍 개선 제안
 data class LandingState(
-    val successAutoLogin: Boolean = false,
+    val isLoginInProgress: Boolean = false,
 )
feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt (1)

36-48: tryAutoLogin() 실패 시 사용자에게 피드백이 없습니다 — 의도된 동작인지 확인 필요.

자동 로그인 실패 시 조용히 Landing 화면을 보여주는 것이 의도된 동작이라면 괜찮지만, 네트워크 오류 등의 경우 사용자에게 알림이 필요할 수 있습니다. 현재 실패 시 snackbar 등의 피드백 없이 단순히 상태만 리셋됩니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashScreen.kt`:
- Around line 56-74: You create snackBarHostState but never attach it to the UI,
so showSnackbar calls do nothing; pass snackBarHostState into the SplashScreen
composable (e.g., add a parameter like snackBarHostState: SnackbarHostState) and
update the SplashScreen implementation to include a SnackbarHost (or a Scaffold
with snackbarHost) using that state so the launched LaunchedEffect uses the
attached host; update the call site (SplashScreen()) to pass snackBarHostState
and keep the existing LaunchedEffect(viewModel.sideEffect) logic unchanged.

In `@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashState.kt`:
- Around line 3-10: The classes in this file are misnamed for the splash
package: rename data class LandingState to SplashState (keeping the
successAutoLogin property) and rename sealed interface LandingSideEffect to
SplashSideEffect (keeping members NavigateToHome, NavigateToLanding and
ShowSnackBar(message: String)), then update all references/imports across the
codebase to use SplashState and SplashSideEffect instead of
LandingState/LandingSideEffect so they no longer collide with the landing
package equivalents; ensure constructors, type usages, and any when/switch
branches or tests that pattern-match on LandingSideEffect members are updated to
the new Splash* names.

In
`@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashViewModel.kt`:
- Around line 33-34: The MutableSharedFlow _sideEffect is created with default
replay/extraBufferCapacity which can lose emissions if tryAutoLogin() emits
before SplashRoute's LaunchedEffect collector is registered; change the creation
of _sideEffect (MutableSharedFlow<LandingSideEffect>) to include a non-zero
buffer (e.g., replay=1 or extraBufferCapacity=1) so emitted LandingSideEffect
values are retained until the sideEffect collector in
SplashRoute/LaunchedEffect(viewModel.sideEffect) subscribes, or alternatively
refactor ordering so tryAutoLogin() runs after the collector is registered.
- Around line 36-49: tryAutoLogin currently ignores exception details and can
crash if loginRepository.refreshTokenData throws; wrap the call inside
viewModelScope.launch with try/catch (or use runCatching) around
loginRepository.refreshTokenData(), log the caught Throwable (using your app
logger or Log/TAG) including message and stacktrace, and in the catch/failure
path emit LandingSideEffect.NavigateToLanding and set _uiState.update {
it.copy(successAutoLogin = false) } so failures never crash the coroutine;
update references: tryAutoLogin, loginRepository.refreshTokenData,
_sideEffect.emit, and _uiState.update.
🧹 Nitpick comments (2)
feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashViewModel.kt (1)

3-22: 사용되지 않는 import 및 의존성 정리 필요

Activity, ContentValues.TAG, Context(직접 사용), Log, UserApiClient, NidOAuth, NidOAuthCallback, SocialLoginType 등 다수의 import가 사용되지 않고 있습니다. 또한 생성자에 주입된 context도 현재 사용되지 않습니다. 다른 ViewModel에서 복사한 흔적으로 보입니다.

♻️ 불필요한 import 및 의존성 제거
 package com.umcspot.spot.signup.splash

-import android.app.Activity
-import android.content.ContentValues.TAG
-import android.content.Context
-import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.kakao.sdk.user.UserApiClient
-import com.navercorp.nid.NidOAuth
-import com.navercorp.nid.oauth.util.NidOAuthCallback
-import com.umcspot.spot.model.SocialLoginType
 import com.umcspot.spot.token.repository.TokenRepository
 import dagger.hilt.android.lifecycle.HiltViewModel
-import dagger.hilt.android.qualifiers.ApplicationContext
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import javax.inject.Inject

 `@HiltViewModel`
 class SplashViewModel `@Inject` constructor(
     private val loginRepository: TokenRepository,
-    `@ApplicationContext` private val context: Context
 ) : ViewModel() {
feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashScreen.kt (1)

3-42: 다수의 사용되지 않는 import 정리 필요

Activity, Image, Arrangement, PaddingValues, Spacer, WindowInsets, fillMaxWidth, height, padding, size, systemBars, Scaffold, SnackbarHost, Text, ContentScale, painterResource, TextAlign, dp, 디자인 시스템 리소스 (R, KakaoStartButton, NaverStartButton, B500), SocialLoginType, screenHeightDp, screenWidthDp 등 대부분의 import가 현재 사용되지 않습니다. 미완성 상태인 것은 이해하지만, 작업 완료 시 정리해 주세요.

Comment on lines 56 to 74
val snackBarHostState = remember { SnackbarHostState() }

LaunchedEffect(Unit) {
viewModel.tryAutoLogin()
}

LaunchedEffect(viewModel.sideEffect) {
viewModel.sideEffect.collectLatest { effect ->
when (effect) {
is LandingSideEffect.NavigateToHome -> navigateToHome()
is LandingSideEffect.NavigateToLanding -> navigateToLanding()
is LandingSideEffect.ShowSnackBar -> {
snackBarHostState.showSnackbar(effect.message)
}
}
}
}

SplashScreen()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

snackBarHostState가 생성만 되고 UI 트리에 연결되지 않음

Line 56에서 SnackbarHostState를 생성하고 Line 68에서 showSnackbar를 호출하지만, SplashScreen() 컴포저블에 SnackbarHost가 포함되어 있지 않아 스낵바가 실제로 표시되지 않습니다.

🐛 SplashScreen에 SnackbarHost 연결 제안
 `@Composable`
-fun SplashScreen(
-) {
-    Column(
+fun SplashScreen(
+    snackBarHostState: SnackbarHostState = remember { SnackbarHostState() },
+) {
+    Scaffold(
+        snackbarHost = { SnackbarHost(snackBarHostState) },
+    ) { paddingValues ->
+      Column(
         modifier = Modifier
             .fillMaxSize()
             .background(SpotTheme.colors.white)
-    ) {
-
+             .padding(paddingValues)
+      ) {
+      }
     }
 }
🤖 Prompt for AI Agents
In `@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashScreen.kt`
around lines 56 - 74, You create snackBarHostState but never attach it to the
UI, so showSnackbar calls do nothing; pass snackBarHostState into the
SplashScreen composable (e.g., add a parameter like snackBarHostState:
SnackbarHostState) and update the SplashScreen implementation to include a
SnackbarHost (or a Scaffold with snackbarHost) using that state so the launched
LaunchedEffect uses the attached host; update the call site (SplashScreen()) to
pass snackBarHostState and keep the existing
LaunchedEffect(viewModel.sideEffect) logic unchanged.

Comment on lines 33 to 34
private val _sideEffect = MutableSharedFlow<LandingSideEffect>()
val sideEffect = _sideEffect.asSharedFlow()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

MutableSharedFlow의 기본 버퍼 설정으로 인한 사이드 이펙트 유실 가능성

MutableSharedFlow<LandingSideEffect>()replay=0, extraBufferCapacity=0으로 생성됩니다. SplashRoute에서 LaunchedEffect(viewModel.sideEffect)로 collector를 등록하기 전에 LaunchedEffect(Unit)에서 tryAutoLogin()이 호출되면, emit된 사이드 이펙트가 collector가 준비되기 전에 발생하여 유실될 수 있습니다.

이 경우 사용자는 splash 화면에 영원히 머물게 됩니다.

🐛 수정 제안: replay 또는 extraBufferCapacity 설정
-    private val _sideEffect = MutableSharedFlow<LandingSideEffect>()
+    private val _sideEffect = MutableSharedFlow<LandingSideEffect>(extraBufferCapacity = 1)

또는 SplashScreen.kt에서 side effect 수집과 auto-login 트리거 순서를 보장하도록 리팩터링하는 방법도 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private val _sideEffect = MutableSharedFlow<LandingSideEffect>()
val sideEffect = _sideEffect.asSharedFlow()
private val _sideEffect = MutableSharedFlow<LandingSideEffect>(extraBufferCapacity = 1)
val sideEffect = _sideEffect.asSharedFlow()
🤖 Prompt for AI Agents
In
`@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashViewModel.kt`
around lines 33 - 34, The MutableSharedFlow _sideEffect is created with default
replay/extraBufferCapacity which can lose emissions if tryAutoLogin() emits
before SplashRoute's LaunchedEffect collector is registered; change the creation
of _sideEffect (MutableSharedFlow<LandingSideEffect>) to include a non-zero
buffer (e.g., replay=1 or extraBufferCapacity=1) so emitted LandingSideEffect
values are retained until the sideEffect collector in
SplashRoute/LaunchedEffect(viewModel.sideEffect) subscribes, or alternatively
refactor ordering so tryAutoLogin() runs after the collector is registered.

Comment on lines 36 to 49
fun tryAutoLogin() {
_uiState.update { it.copy(successAutoLogin = true) }

viewModelScope.launch {
val result = loginRepository.refreshTokenData()
if (result.isSuccess) {
_sideEffect.emit(LandingSideEffect.NavigateToHome)
_uiState.update { it.copy(successAutoLogin = false) }
} else {
_sideEffect.emit(LandingSideEffect.NavigateToLanding)
_uiState.update { it.copy(successAutoLogin = false) }
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

tryAutoLogin() 실패 시 에러 로깅 및 예외 처리 부재

refreshTokenData()가 실패한 경우 결과에 포함된 예외 정보를 로깅하지 않아 디버깅이 어렵습니다. 또한 refreshTokenData() 자체에서 예외가 throw되면 코루틴이 crash할 수 있습니다.

🛡️ 에러 처리 개선 제안
     fun tryAutoLogin() {
         _uiState.update { it.copy(successAutoLogin = true) }

         viewModelScope.launch {
-            val result = loginRepository.refreshTokenData()
-            if (result.isSuccess) {
-                _sideEffect.emit(LandingSideEffect.NavigateToHome)
-                _uiState.update { it.copy(successAutoLogin = false) }
-            } else {
-                _sideEffect.emit(LandingSideEffect.NavigateToLanding)
-                _uiState.update { it.copy(successAutoLogin = false) }
-            }
+            runCatching { loginRepository.refreshTokenData() }
+                .onSuccess { result ->
+                    if (result.isSuccess) {
+                        _sideEffect.emit(LandingSideEffect.NavigateToHome)
+                    } else {
+                        _sideEffect.emit(LandingSideEffect.NavigateToLanding)
+                    }
+                }
+                .onFailure {
+                    Log.e("SplashViewModel", "Auto login failed", it)
+                    _sideEffect.emit(LandingSideEffect.NavigateToLanding)
+                }
+            _uiState.update { it.copy(successAutoLogin = false) }
         }
     }
🤖 Prompt for AI Agents
In
`@feature/signup/src/main/java/com/umcspot/spot/signup/splash/SplashViewModel.kt`
around lines 36 - 49, tryAutoLogin currently ignores exception details and can
crash if loginRepository.refreshTokenData throws; wrap the call inside
viewModelScope.launch with try/catch (or use runCatching) around
loginRepository.refreshTokenData(), log the caught Throwable (using your app
logger or Log/TAG) including message and stacktrace, and in the catch/failure
path emit LandingSideEffect.NavigateToLanding and set _uiState.update {
it.copy(successAutoLogin = false) } so failures never crash the coroutine;
update references: tryAutoLogin, loginRepository.refreshTokenData,
_sideEffect.emit, and _uiState.update.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt (1)

143-148: ⚠️ Potential issue | 🟠 Major

로그아웃 시 토큰/세션 데이터 삭제 로직이 누락되어 있습니다.

주석에 "로그아웃 처리(데이터 삭제) 트리거"라고 적혀 있지만 실제 토큰 삭제 로직이 없습니다. Landing으로 네비게이션만 수행하면 DataStore에 저장된 토큰이 남아 있어, 다음 앱 실행 시 Splash에서 자동 로그인이 성공하게 됩니다.

토큰 삭제 로직 구현을 도와드릴까요? 또는 이 작업을 추적할 이슈를 생성할까요?

@starshape7 starshape7 merged commit 86d8649 into develop Feb 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT/#43] 자동 로그인 구현

1 participant