Skip to content

Commit

Permalink
Refactor AuthRepository by creating authState
Browse files Browse the repository at this point in the history
Resolves #23
  • Loading branch information
jja08111 committed Jan 29, 2024
1 parent e87c662 commit 8d311d9
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 87 deletions.
24 changes: 8 additions & 16 deletions app/src/main/java/io/foundy/camstudy/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,33 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import dagger.hilt.android.lifecycle.HiltViewModel
import io.foundy.auth.domain.usecase.ExistsInitInfoUseCase
import io.foundy.auth.domain.usecase.GetCurrentUserIdUseCase
import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.usecase.GetAuthStateStreamUseCase
import io.foundy.auth.ui.destinations.LoginRouteDestination
import io.foundy.home.ui.destinations.HomeRouteDestination
import io.foundy.welcome.ui.destinations.WelcomeRouteDestination
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
private val getCurrentUserIdUseCase: GetCurrentUserIdUseCase,
private val existsInitInfoUseCase: ExistsInitInfoUseCase
private val getAuthStateStreamUseCase: GetAuthStateStreamUseCase,
) : ViewModel() {

private var _startDestination: DirectionDestinationSpec? = null
val startDestination: DirectionDestinationSpec? get() = _startDestination

init {
viewModelScope.launch {
val currentUserId = getCurrentUserIdUseCase()
if (currentUserId == null) {
_startDestination = LoginRouteDestination
} else {
val existsInitInfo = existsInitInfoUseCase()
if (existsInitInfo == null) {
// 로그인이 되어 있으나 초기 정보 입력 여부를 판단할 수 없는 경우 서버와의 연결에 실패한 것이다.
// 에러 메시지는 로그인 화면에서 보이기 때문에 따로 여기서 에러 메시지를 보이는 로직은 없다.
_startDestination = LoginRouteDestination
return@launch
}
_startDestination = if (existsInitInfo) {
_startDestination = when (val authState = getAuthStateStreamUseCase().firstOrNull()) {
is AuthState.SignedIn -> if (authState.existsInitInfo) {
HomeRouteDestination
} else {
WelcomeRouteDestination
}

else -> LoginRouteDestination
}
}
}
Expand Down
27 changes: 14 additions & 13 deletions app/src/test/java/io/foundy/camstudy/MainViewModelTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.foundy.camstudy

import dagger.hilt.android.testing.HiltAndroidTest
import io.foundy.auth.domain.usecase.ExistsInitInfoUseCase
import io.foundy.auth.domain.usecase.GetCurrentUserIdUseCase
import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.usecase.GetAuthStateStreamUseCase
import io.foundy.auth.ui.destinations.LoginRouteDestination
import io.foundy.camstudy.fake.FakeAuthRepository
import io.foundy.core.test.MainDispatcherRule
Expand All @@ -29,46 +29,47 @@ class MainViewModelTest {
private lateinit var viewModel: MainViewModel

@Test
fun `should startDestination is Login when currentUserId is null`() = runTest {
fun `should startDestination is Login when not signed in`() = runTest {
initViewModel()
authRepository.currentUserIdSharedFlow.emit(null)
authRepository.emitState(AuthState.NotSignedIn)

assertEquals(LoginRouteDestination, viewModel.startDestination)
}

@Test
fun `should startDestination is Home when currentUserId and init info exist`() = runTest {
authRepository.currentUserIdSharedFlow.emit("id")
authRepository.existsInitInfoTestValue = true
initViewModel()
authRepository.emitState(AuthState.SignedIn(currentUserId = "id", existsInitInfo = true))

assertEquals(HomeRouteDestination, viewModel.startDestination)
}

@Test
fun `should startDestination is Welcome when currentUserId exists but init info not`() =
runTest {
authRepository.currentUserIdSharedFlow.emit("id")
authRepository.existsInitInfoTestValue = false
initViewModel()
authRepository.emitState(
AuthState.SignedIn(
currentUserId = "id",
existsInitInfo = false
)
)

assertEquals(WelcomeRouteDestination, viewModel.startDestination)
}

@Test
fun `should startDestination is Login when currentUserId exists but init info is null`() =
fun `should startDestination is Login when there is error`() =
runTest {
authRepository.currentUserIdSharedFlow.emit("id")
authRepository.existsInitInfoTestValue = null
initViewModel()
authRepository.emitState(AuthState.Error)

assertEquals(LoginRouteDestination, viewModel.startDestination)
}

private fun initViewModel() {
viewModel = MainViewModel(
getCurrentUserIdUseCase = GetCurrentUserIdUseCase(authRepository = authRepository),
existsInitInfoUseCase = ExistsInitInfoUseCase(authRepository = authRepository)
getAuthStateStreamUseCase = GetAuthStateStreamUseCase(authRepository = authRepository)
)
}
}
13 changes: 5 additions & 8 deletions app/src/test/java/io/foundy/camstudy/fake/FakeAuthRepository.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package io.foundy.camstudy.fake

import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.repository.AuthRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow

class FakeAuthRepository : AuthRepository {

val currentUserIdSharedFlow = MutableSharedFlow<String?>(replay = 1)
override val stateStream: MutableSharedFlow<AuthState> = MutableSharedFlow(replay = 1)

var existsInitInfoTestValue: Boolean? = false

override val currentUserIdStream: Flow<String?> = currentUserIdSharedFlow

override val existsInitInfo: Boolean?
get() = existsInitInfoTestValue
suspend fun emitState(newState: AuthState) {
stateStream.emit(newState)
}

override suspend fun markAsUserInitialInfoExists(userId: String) {
TODO("Not yet implemented")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import io.foundy.auth.data.source.AuthLocalDataSource
import io.foundy.auth.data.source.AuthRemoteDataSource
import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.repository.AuthRepository
import io.foundy.core.common.di.ApplicationScope
import io.foundy.core.data.extension.getDataOrThrowMessage
Expand All @@ -20,8 +21,7 @@ class FirebaseAuthRepository @Inject constructor(
private val authLocalDataSource: AuthLocalDataSource
) : AuthRepository {

override val currentUserIdStream: MutableSharedFlow<String?> = MutableSharedFlow(replay = 1)
override var existsInitInfo: Boolean? = null
override val stateStream: MutableSharedFlow<AuthState> = MutableSharedFlow(replay = 1)

init {
Firebase.auth.addAuthStateListener { auth ->
Expand All @@ -36,25 +36,35 @@ class FirebaseAuthRepository @Inject constructor(
private fun fetchAndNotify(currentUserId: String?) {
externalScope.launch {
if (currentUserId == null) {
existsInitInfo = null
currentUserIdStream.emit(null)
stateStream.emit(AuthState.NotSignedIn)
return@launch
}
val existsInLocal = authLocalDataSource.existsUserInitialInfo(currentUserId)
if (existsInLocal) {
existsInitInfo = true
stateStream.emit(
AuthState.SignedIn(
currentUserId = currentUserId,
existsInitInfo = true
)
)
} else {
runCatching {
val response = authRemoteDataSource.getUserInitialInfoExistence(currentUserId)
response.getDataOrThrowMessage()
}.onSuccess { exists ->
existsInitInfo = exists
if (exists) {
authLocalDataSource.markAsUserInitialInfoExists(currentUserId)
}
stateStream.emit(
AuthState.SignedIn(
currentUserId = currentUserId,
existsInitInfo = exists
)
)
}.onFailure {
stateStream.emit(AuthState.Error)
}
}
currentUserIdStream.emit(currentUserId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.foundy.auth.domain.model

sealed class AuthState {
object NotSignedIn : AuthState()
data class SignedIn(val currentUserId: String, val existsInitInfo: Boolean) : AuthState()
object Error : AuthState() // TODO: Add error context message
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package io.foundy.auth.domain.repository

import io.foundy.auth.domain.model.AuthState
import kotlinx.coroutines.flow.Flow

interface AuthRepository {

val currentUserIdStream: Flow<String?>

/**
* `currentUserIdStream`이 `true`인데 이 값이 `null`인 경우 서버와의 연결을 실패한 경우이다.
*/
val existsInitInfo: Boolean?
val stateStream: Flow<AuthState>

suspend fun markAsUserInitialInfoExists(userId: String)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package io.foundy.auth.domain.usecase

import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.repository.AuthRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class GetCurrentUserIdStreamUseCase @Inject constructor(
class GetAuthStateStreamUseCase @Inject constructor(
private val authRepository: AuthRepository
) {
operator fun invoke(): Flow<String?> {
return authRepository.currentUserIdStream
operator fun invoke(): Flow<AuthState> {
return authRepository.stateStream
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.foundy.auth.domain.usecase

import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.repository.AuthRepository
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
Expand All @@ -8,6 +9,9 @@ class GetCurrentUserIdUseCase @Inject constructor(
private val authRepository: AuthRepository
) {
suspend operator fun invoke(): String? {
return authRepository.currentUserIdStream.firstOrNull()
return when (val authState = authRepository.stateStream.firstOrNull()) {
is AuthState.SignedIn -> authState.currentUserId
else -> null
}
}
}
31 changes: 14 additions & 17 deletions feature/auth/ui/src/main/java/io/foundy/auth/ui/LoginViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package io.foundy.auth.ui

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import io.foundy.auth.domain.usecase.ExistsInitInfoUseCase
import io.foundy.auth.domain.usecase.GetCurrentUserIdStreamUseCase
import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.usecase.GetAuthStateStreamUseCase
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.postSideEffect
Expand All @@ -13,31 +13,28 @@ import javax.inject.Inject

@HiltViewModel
class LoginViewModel @Inject constructor(
private val getCurrentUserIdStreamUseCase: GetCurrentUserIdStreamUseCase,
private val existsInitInfoUseCase: ExistsInitInfoUseCase
private val getAuthStateStreamUseCase: GetAuthStateStreamUseCase,
) : ViewModel(), ContainerHost<LoginUiState, LoginSideEffect> {

override val container = container<LoginUiState, LoginSideEffect>(LoginUiState())

init {
intent {
getCurrentUserIdStreamUseCase().collect { uid ->
if (uid == null) {
return@collect
}
val existsInitInfo = existsInitInfoUseCase()
if (existsInitInfo == null) {
postSideEffect(
getAuthStateStreamUseCase().collect { authState ->
when (authState) {
AuthState.Error -> postSideEffect(
LoginSideEffect.Message(
defaultMessageRes = R.string.failed_to_connect_server
)
)
return@collect
}
if (existsInitInfo) {
postSideEffect(LoginSideEffect.NavigateToHome)
} else {
postSideEffect(LoginSideEffect.NavigateToWelcome)

is AuthState.SignedIn -> if (authState.existsInitInfo) {
postSideEffect(LoginSideEffect.NavigateToHome)
} else {
postSideEffect(LoginSideEffect.NavigateToWelcome)
}

AuthState.NotSignedIn -> {}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package io.foundy.room.ui.fake

import io.foundy.auth.domain.model.AuthState
import io.foundy.auth.domain.repository.AuthRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class FakeAuthRepository(private val userId: String = "1234") : AuthRepository {

override val currentUserIdStream: Flow<String?>
get() = flow { emit(userId) }

override val existsInitInfo: Boolean = true
override val stateStream: Flow<AuthState>
get() = flow { emit(AuthState.SignedIn(currentUserId = userId, existsInitInfo = true)) }

override suspend fun markAsUserInitialInfoExists(userId: String) {
TODO("Not yet implemented")
Expand Down

0 comments on commit 8d311d9

Please sign in to comment.