-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: 로그아웃 구현 #687
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
refactor: 로그아웃 구현 #687
Changes from 6 commits
87963e2
6ed6d90
2380584
ab6bc96
f649641
618f8c9
72f74cb
844ae23
3472ce6
c3105cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,20 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.into.websoso.ui.accountInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.LiveData | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.MutableLiveData | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.ViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.viewModelScope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.data.repository.AuthRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.core.auth.AuthPlatform | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.data.account.AccountRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.data.repository.PushMessageRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.data.repository.UserRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.into.websoso.ui.accountInfo.UiEffect.NavigateToLogin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.channels.Channel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.Flow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.StateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.asStateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.receiveAsFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.update | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -16,14 +23,14 @@ class AccountInfoViewModel | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val userRepository: UserRepository, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val authRepository: AuthRepository, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val pushMessageRepository: PushMessageRepository, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val accountRepository: AccountRepository, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ViewModel() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val _userEmail: MutableLiveData<String> = MutableLiveData() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val userEmail: LiveData<String> get() = _userEmail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val _userEmail: MutableStateFlow<String> = MutableStateFlow("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val userEmail: StateFlow<String> get() = _userEmail.asStateFlow() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val _isLogoutSuccess: MutableLiveData<Boolean> = MutableLiveData(false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val isLogoutSuccess: LiveData<Boolean> get() = _isLogoutSuccess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var _uiEffect = Channel<UiEffect>(Channel.BUFFERED) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| init { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateUserEmail() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,23 +41,27 @@ class AccountInfoViewModel | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runCatching { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userRepository.fetchUserInfoDetail() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }.onSuccess { userInfo -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _userEmail.value = userInfo.email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _userEmail.update { userInfo.email } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun logout() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun signOut(signOutToPlatform: suspend (platform: AuthPlatform) -> Unit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModelScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runCatching { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signOutToPlatform(AuthPlatform.KAKAO) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val userDeviceIdentifier = userRepository.fetchUserDeviceIdentifier() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authRepository.logout(userDeviceIdentifier) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accountRepository.deleteToken(userDeviceIdentifier) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }.onSuccess { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _isLogoutSuccess.value = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authRepository.updateIsAutoLogin(false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pushMessageRepository.clearFCMToken() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _uiEffect.send(NavigateToLogin) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }.onFailure { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _isLogoutSuccess.value = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _uiEffect.send(NavigateToLogin) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion FCM 토큰 제거가 실패 경로에서 누락될 수 있습니다 - }.onSuccess {
- pushMessageRepository.clearFCMToken()
- _uiEffect.send(NavigateToLogin)
- }.onFailure {
- _uiEffect.send(NavigateToLogin)
- }
+ }.onSuccess {
+ /* no-op */
+ }.onFailure {
+ /* 로그 등 추가 */
+ }.also {
+ // 무조건 실행
+ pushMessageRepository.clearFCMToken()
+ _uiEffect.send(NavigateToLogin)
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai 로그아웃이 실패했는데 왜 토큰을 지워? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @s9hn 말씀하신 내용이 맞습니다. 로그아웃이 실패한 경우에는 사용자가 여전히 로그인 상태로 남아있어야 하므로 FCM 토큰을 지우는 것이 적절하지 않습니다. 원래 구현대로 성공 경로에서만 FCM 토큰을 지우는 것이 맞습니다. 제안 내용을 철회합니다. ✏️ Learnings added
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sealed interface UiEffect { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data object NavigateToLogin : UiEffect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,9 +9,9 @@ import com.into.websoso.ui.splash.UiEffect.NavigateToLogin | |||||||||
| import com.into.websoso.ui.splash.UiEffect.NavigateToMain | ||||||||||
| import com.into.websoso.ui.splash.UiEffect.ShowDialog | ||||||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||||||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||||||||
| import kotlinx.coroutines.flow.SharedFlow | ||||||||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||||||||
| import kotlinx.coroutines.channels.Channel | ||||||||||
| import kotlinx.coroutines.flow.Flow | ||||||||||
| import kotlinx.coroutines.flow.receiveAsFlow | ||||||||||
| import kotlinx.coroutines.launch | ||||||||||
| import javax.inject.Inject | ||||||||||
|
|
||||||||||
|
|
@@ -23,12 +23,14 @@ class SplashViewModel | |||||||||
| private val userRepository: UserRepository, | ||||||||||
| private val accountRepository: AccountRepository, | ||||||||||
| ) : ViewModel() { | ||||||||||
| private var _uiEffect: MutableSharedFlow<UiEffect> = MutableSharedFlow(replay = 1) | ||||||||||
| val uiEffect: SharedFlow<UiEffect> get() = _uiEffect.asSharedFlow() | ||||||||||
| private var _uiEffect = Channel<UiEffect>(Channel.BUFFERED) | ||||||||||
| val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow() | ||||||||||
|
|
||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Channel → SharedFlow로 전환 및 - private var _uiEffect = Channel<UiEffect>(Channel.BUFFERED)
- val uiEffect: Flow<UiEffect> get() = _uiEffect.receiveAsFlow()
+ private val _uiEffect = MutableSharedFlow<UiEffect>(extraBufferCapacity = 1)
+ val uiEffect: SharedFlow<UiEffect> get() = _uiEffect.asSharedFlow()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| init { | ||||||||||
| checkMinimumVersion() | ||||||||||
| handleAutoLogin() | ||||||||||
| viewModelScope.launch { | ||||||||||
| val isUpdateRequired = checkMinimumVersion() | ||||||||||
| if (isUpdateRequired.not()) handleAutoLogin() | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| fun updateUserDeviceIdentifier(deviceIdentifier: String) { | ||||||||||
|
|
@@ -41,30 +43,25 @@ class SplashViewModel | |||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private fun checkMinimumVersion() { | ||||||||||
| viewModelScope.launch { | ||||||||||
| runCatching { | ||||||||||
| versionRepository.isUpdateRequired() | ||||||||||
| }.onSuccess { isRequired -> | ||||||||||
| if (isRequired) _uiEffect.emit(ShowDialog) | ||||||||||
| } | ||||||||||
| private suspend fun checkMinimumVersion(): Boolean = | ||||||||||
| runCatching { | ||||||||||
| versionRepository.isUpdateRequired() | ||||||||||
| }.getOrElse { false }.also { isRequired -> | ||||||||||
| if (isRequired) _uiEffect.send(ShowDialog) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private fun handleAutoLogin() { | ||||||||||
| viewModelScope.launch { | ||||||||||
| if (shouldRefresh()) { | ||||||||||
| _uiEffect.emit(NavigateToLogin) | ||||||||||
| return@launch | ||||||||||
| } | ||||||||||
|
|
||||||||||
| runCatching { accountRepository.renewToken() } | ||||||||||
| .onSuccess { | ||||||||||
| _uiEffect.emit(NavigateToMain) | ||||||||||
| }.onFailure { | ||||||||||
| _uiEffect.emit(NavigateToLogin) | ||||||||||
| } | ||||||||||
| private suspend fun handleAutoLogin() { | ||||||||||
| if (shouldRefresh()) { | ||||||||||
| _uiEffect.send(NavigateToLogin) | ||||||||||
| return | ||||||||||
| } | ||||||||||
|
|
||||||||||
| runCatching { accountRepository.renewToken() } | ||||||||||
| .onSuccess { | ||||||||||
| _uiEffect.send(NavigateToMain) | ||||||||||
| }.onFailure { | ||||||||||
| _uiEffect.send(NavigateToLogin) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private suspend fun shouldRefresh(): Boolean = | ||||||||||
|
|
||||||||||
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→SharedFlow+val로 일관성 확보SplashViewModel 과 동일한 패턴입니다. 이벤트 스트림이면
MutableSharedFlow사용이 더 직관적이며var제거로 재할당 위험을 없앨 수 있습니다.🤖 Prompt for AI Agents