I have this use case where response from data source are guaranteed to be consume and not missed after configuration change, but also do not repeat it when already consume.
StateFlow
seems to be the best candidate as it holds last emitted value but can be problematic on a following scenario:
- Data source response with the same error on each request (maybe due to having no data in local cache or network issue.)
As StateFlow
does not emit the same value, this can be problematic where after a new request to data source, an error state (can be dialog) will not show again in the UI if the previous state is also error.
Using SharedFlow
with configured replay
MutableSharedFlow<T>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
This will behave like a LiveData
with repeatOnLifecycle
, but has its own issue on the following scenario.
Let say we have these states
sealed class AssetState {
data class FetchAssetLoading(val assetList: List<AssetMinDataDomain>?) : AssetState()
data class FetchAssetSuccess(val assetList: List<AssetMinDataDomain>) : AssetState()
data class FetchAssetFailed(val msg: String, val assetList: List<AssetMinDataDomain>?) : AssetState()
}
Inside onCreate
or onViewCreated
// Initial fetch
fetchData(param1, param2)
// Collecting the result of fetch data
with(viewModel) {
viewLifecycleOwner.collectShared(assetState, ::onAssetStateChanged)
}
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) }
}
}
}
When a configuration change happens, the fetchData(param1, param2)
will be triggered and its result will be collected when the LifeCycle
at least reached STARTED
but since we also use repeatOnLifecycle
there will be two emission of data. One as a result of request fetchData
which is more updated and one as the result of repeatOnLifecycle
which can be outdated. The only solution I see as of the moment is to add a empty state or no state then manually set it after receiving success state of failed state in the UI but seems not the best solution and too repetitive. Is there a better way to satisfy the above requirements? Thanks
P.S
If I remove repeat = 1
in SharedFlow
it will solve the issue, however if the activity went onStop
e.g. the user press home button while fetching data then went back to the app causing onStart
and onResume
to trigger then we will lose that event and its result.
As of now, to perform single event using flows is using Channel and turn it to regular cold flow like this
ViewModel
To emit use
send
Collecting the result of fetch data
Update extension function
Note that there is a subtle catch on using this approach where you can still missed an event if Channel sent the event but the consumer just in time suddenly went background based on the set LifeCycle.
Reference: https://medium.com/androiddevelopers/viewmodel-one-off-event-antipatterns-16a1da869b95