Skip to content

Commit

Permalink
일회성 이벤트를 처리하는 방법을 SharedFlow 에서 Channel 로 변경 (#157)
Browse files Browse the repository at this point in the history
- SharedFlow 의 경우 Shared 라는 키워드 처럼, 여러 Observer 가 동시에 event 를 구독할 수 있음, Channel 은 단일한 Observer
- 일회성 이벤트의 경우 해당 이벤트를 구독하는 Observer 는 그 화면 하나가 전부(하나의 구독자를 위한 이벤트)
- 또한 sharedFlow 는 hotFlow 이기 때문에 observer 가 존재하지 않는 경우에도 계속 이벤트를 방출함, 따라서 앱이 백그라운드에 진입했을 때 에도, 계속해서 이벤트가 방출됨
- 앱이 백그라운드에 진입할 경우, Observer 는 이벤트를 수집하지 않기에, 백그라운드에 진입된 동안 sharedFlow 가 방출한 이벤트들은 유실됨
- 따라서 이벤트를 방출하는 동안 configuration change 가 발생할 경우, 극히 적은 확률이지만 이벤트가 유실될 가능성이 존재
- Channel 의 경우 백그라운드에 진입하여 이벤트를 수집할 구독자가 존재하지 않을 경우, 이벤트를 캐싱하고, 다시 구독자가 수집을 시작할 경우, 캐싱한 이벤트를 방출함
- Channel 역시 이벤트가 방출되는 동안 configration change 가 발생할 경우, 이벤트가 유실될 가능성이 존재하지만 ObserveAsEvent 함수가 이를 방지할 수 있음
  • Loading branch information
easyhooon committed Mar 14, 2024
1 parent 88ae4e7 commit fb5c962
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import com.nexters.bandalart.android.feature.complete.navigation.BANDALART_TITLE
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
Expand Down Expand Up @@ -58,8 +57,8 @@ class CompleteViewModel @Inject constructor(
private val _uiState = MutableStateFlow(CompleteUiState())
val uiState: StateFlow<CompleteUiState> = this._uiState.asStateFlow()

private val _eventFlow = MutableSharedFlow<CompleteUiEvent>()
val eventFlow: SharedFlow<CompleteUiEvent> = _eventFlow.asSharedFlow()
private val _eventChannel = Channel<CompleteUiEvent>()
val eventFlow = _eventChannel.receiveAsFlow()

init {
initComplete()
Expand Down Expand Up @@ -98,7 +97,7 @@ class CompleteViewModel @Inject constructor(

result.isFailure -> {
val exception = result.exceptionOrNull()!!
_eventFlow.emit(CompleteUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(CompleteUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
Timber.e(exception)
}
}
Expand All @@ -112,7 +111,7 @@ class CompleteViewModel @Inject constructor(

fun navigateToHome() {
viewModelScope.launch {
_eventFlow.emit(CompleteUiEvent.NavigateToHome)
_eventChannel.send(CompleteUiEvent.NavigateToHome)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ import com.nexters.bandalart.android.feature.home.model.UpdateBandalartMainCellM
import com.nexters.bandalart.android.feature.home.model.UpdateBandalartSubCellModel
import com.nexters.bandalart.android.feature.home.model.UpdateBandalartTaskCellModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

/**
* BottomSheetUiState
Expand Down Expand Up @@ -65,8 +64,8 @@ class BottomSheetViewModel @Inject constructor(
private val _uiState = MutableStateFlow(BottomSheetUiState())
val uiState: StateFlow<BottomSheetUiState> = _uiState.asStateFlow()

private val _eventFlow = MutableSharedFlow<BottomSheetUiEvent>()
val eventFlow: SharedFlow<BottomSheetUiEvent> = _eventFlow.asSharedFlow()
private val _eventChannel = Channel<BottomSheetUiEvent>()
val eventFlow = _eventChannel.receiveAsFlow()

fun copyCellData(cellData: BandalartCellUiModel) {
_uiState.update {
Expand Down Expand Up @@ -102,7 +101,7 @@ class BottomSheetViewModel @Inject constructor(

result.isFailure -> {
val exception = result.exceptionOrNull()!!
_eventFlow.emit(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
Timber.e(exception.message)
}
}
Expand Down Expand Up @@ -132,7 +131,7 @@ class BottomSheetViewModel @Inject constructor(

result.isFailure -> {
val exception = result.exceptionOrNull()!!
_eventFlow.emit(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
Timber.e(exception.message)
}
}
Expand Down Expand Up @@ -162,7 +161,7 @@ class BottomSheetViewModel @Inject constructor(

result.isFailure -> {
val exception = result.exceptionOrNull()!!
_eventFlow.emit(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
Timber.e(exception.message)
}
}
Expand All @@ -189,7 +188,7 @@ class BottomSheetViewModel @Inject constructor(
it.copy(isCellDeleted = false)
}
openDeleteCellDialog(false)
_eventFlow.emit(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(BottomSheetUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
Timber.e(exception.message)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
Expand Down Expand Up @@ -111,8 +110,8 @@ class HomeViewModel @Inject constructor(
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

private val _eventFlow = MutableSharedFlow<HomeUiEvent>()
val eventFlow: SharedFlow<HomeUiEvent> = _eventFlow.asSharedFlow()
private val _eventChannel = Channel<HomeUiEvent>()
val eventFlow = _eventChannel.receiveAsFlow()

init {
_uiState.update { it.copy(isShowSkeleton = true) }
Expand Down Expand Up @@ -272,7 +271,7 @@ class HomeViewModel @Inject constructor(
_uiState.update { it.copy(isNetworking = true) }
viewModelScope.launch {
if (_uiState.value.bandalartList.size + 1 > 5) {
_eventFlow.emit(HomeUiEvent.ShowToast(UiText.StringResource(R.string.limit_create_bandalart)))
_eventChannel.send(HomeUiEvent.ShowToast(UiText.StringResource(R.string.limit_create_bandalart)))
return@launch
}
_uiState.update { it.copy(isShowSkeleton = true) }
Expand All @@ -289,7 +288,7 @@ class HomeViewModel @Inject constructor(
setRecentBandalartKey(bandalart.key)
// 새로운 반다라트를 로컬에 저장
upsertBandalartKey(bandalart.key)
_eventFlow.emit(HomeUiEvent.ShowSnackbar(UiText.StringResource(R.string.create_bandalart)))
_eventChannel.send(HomeUiEvent.ShowSnackbar(UiText.StringResource(R.string.create_bandalart)))
}

result.isSuccess && result.getOrNull() == null -> {
Expand All @@ -304,7 +303,7 @@ class HomeViewModel @Inject constructor(
isShowSkeleton = false,
)
}
_eventFlow.emit(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
}
}
_uiState.update { it.copy(isNetworking = false) }
Expand All @@ -327,7 +326,7 @@ class HomeViewModel @Inject constructor(
openBandalartDeleteAlertDialog(false)
getBandalartList()
deleteBandalartKey(bandalartKey)
_eventFlow.emit(HomeUiEvent.ShowSnackbar(UiText.StringResource(R.string.delete_bandalart)))
_eventChannel.send(HomeUiEvent.ShowSnackbar(UiText.StringResource(R.string.delete_bandalart)))
}

result.isSuccess && result.getOrNull() == null -> {
Expand All @@ -343,7 +342,7 @@ class HomeViewModel @Inject constructor(
isBandalartDeleted = false,
)
}
_eventFlow.emit(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
}
}
_uiState.update { it.copy(isNetworking = false) }
Expand Down Expand Up @@ -376,7 +375,7 @@ class HomeViewModel @Inject constructor(
isShowSkeleton = false,
)
}
_eventFlow.emit(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
}
}
_uiState.update { it.copy(isNetworking = false) }
Expand All @@ -403,7 +402,7 @@ class HomeViewModel @Inject constructor(

result.isFailure -> {
val exception = result.exceptionOrNull()!!
_eventFlow.emit(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
_eventChannel.send(HomeUiEvent.ShowToast(UiText.DirectString(exception.message.toString())))
}
}
_uiState.update { it.copy(isNetworking = false) }
Expand Down Expand Up @@ -482,7 +481,7 @@ class HomeViewModel @Inject constructor(

fun navigateToComplete() {
viewModelScope.launch {
_eventFlow.emit(
_eventChannel.send(
HomeUiEvent.NavigateToComplete(
key = uiState.value.bandalartDetailData.key,
title = uiState.value.bandalartDetailData.title!!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nexters.bandalart.android.core.domain.usecase.bandalart.SetOnboardingCompletedStatusUseCase
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.receiveAsFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -18,8 +17,8 @@ sealed interface OnBoardingUiEvent {
class OnboardingViewModel @Inject constructor(
private val setOnboardingCompletedStatusUseCase: SetOnboardingCompletedStatusUseCase,
) : ViewModel() {
private val _eventFlow = MutableSharedFlow<OnBoardingUiEvent>()
val eventFlow: SharedFlow<OnBoardingUiEvent> = _eventFlow.asSharedFlow()
private val _eventChannel = Channel<OnBoardingUiEvent>()
val eventFlow = _eventChannel.receiveAsFlow()

fun setOnboardingCompletedStatus(flag: Boolean) {
viewModelScope.launch {
Expand All @@ -30,7 +29,7 @@ class OnboardingViewModel @Inject constructor(

private fun navigateToHome() {
viewModelScope.launch {
_eventFlow.emit(OnBoardingUiEvent.NavigateToHome)
_eventChannel.send(OnBoardingUiEvent.NavigateToHome)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import com.nexters.bandalart.android.core.domain.usecase.login.CreateGuestLoginT
import com.nexters.bandalart.android.core.domain.usecase.login.GetGuestLoginTokenUseCase
import com.nexters.bandalart.android.core.domain.usecase.login.SetGuestLoginTokenUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
Expand Down Expand Up @@ -54,8 +53,8 @@ class SplashViewModel @Inject constructor(
private val _uiState = MutableStateFlow(SplashUiState())
val uiState: StateFlow<SplashUiState> = _uiState.asStateFlow()

private val _eventFlow = MutableSharedFlow<SplashUiEvent>()
val eventFlow: SharedFlow<SplashUiEvent> = _eventFlow.asSharedFlow()
private val _eventChannel = Channel<SplashUiEvent>()
val eventFlow = _eventChannel.receiveAsFlow()

init {
viewModelScope.launch {
Expand Down Expand Up @@ -138,13 +137,13 @@ class SplashViewModel @Inject constructor(

fun navigateToOnBoarding() {
viewModelScope.launch {
_eventFlow.emit(SplashUiEvent.NavigateToOnBoarding)
_eventChannel.send(SplashUiEvent.NavigateToOnBoarding)
}
}

fun navigateToHome() {
viewModelScope.launch {
_eventFlow.emit(SplashUiEvent.NavigateToHome)
_eventChannel.send(SplashUiEvent.NavigateToHome)
}
}
}

0 comments on commit fb5c962

Please sign in to comment.