Using Shared ViewModel approach for Activity and its Fragment. Using SharedFlow
to share and update UI state in Activity, the change of state is coming from fragments.
Activity (Host)
val viewModel: MainSharedViewModel by viewModels()
// START OF onCreate
with(viewModel) {
collectShared(mainActivityState, ::onActivityStateChanged)
}
// END OF onCreate
private fun onActivityStateChanged(state: MainActivityState) {
// Listen which Fragment is visible to user
if (state is MainActivityState.ToolbarTitleChanged) {
toolBar.subtitle = state.subTitle
}
}
Extension Function
inline fun <T : Any, L : SharedFlow<T>> LifecycleOwner.collectShared(
sharedFlow: L,
crossinline function: (T) -> Unit,
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED
) {
lifecycleScope.launch {
repeatOnLifecycle(lifecycleState) {
sharedFlow.collect { t -> function(t) }
}
}
}
Fragment
private val mainSharedViewModel: MainSharedViewModel by activityViewModels()
// START OF onCreateView
mainSharedViewModel.setSubTitle() // Empty
// END OF onCreateView
Activity ViewModel
private val _mainActivityState = MutableSharedFlow<MainActivityState>()
val mainActivityState = _mainActivityState.asSharedFlow()
fun setSubTitle(subtitle: String = DEFAULT_VALUE_STRING) {
viewModelScope.launch {
_mainActivityState.emit(MainActivityState.ToolbarTitleChanged(subtitle))
}
}
The above works when switching between fragments via Jetpack Navigation Component, however when a configuration change happens due to switching between Day and Night theme using AppCompatDelegate.setDefaultNightMode
. The collector in Activity is not consuming the emission from Fragment.
Upon debugging, we confirmed that the flow is correct
Execute Configuration Change via
AppCompatDelegate.setDefaultNightMode
Activity's
collectShared(mainActivityState, ::onActivityStateChanged)
triggeredFragment's
mainSharedViewModel.setSubTitle()
triggered
Unfortunately the Activity's onActivityStateChanged
not triggered during this flow. However if I call the mainSharedViewModel.setSubTitle()
via button or when I add delay, the collector in activity is receiving the event. I am guessing there is a sort of race condition happening here?
Tried
val mainActivityState = _mainActivityState.asSharedFlow().shareIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L))
and
val mainActivityState = _mainActivityState.asSharedFlow().shareIn(viewModelScope, SharingStarted.Eagerly)
as well as
val mainActivityState = _mainActivityState.asSharedFlow().shareIn(viewModelScope, SharingStarted.Lazily)
but none of them work.
Are we missing something about SharedFlow
? Why we are losing the event for this case? I run on this interesting article which might also explain this issue but no idea what changes would be a best course.
As I know SharedFlow doesn't cache the last value by default. Maybe you should try to use StateFlow instead?