I've read similar topics but couldn't find a proper answer:
In my Repository class I have a cold Flow that I want to share to 2 Presenters/ViewModels so my choice is to use shareIn operator.
Let's take a look on Android docs' example:
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope, // e.g. CoroutineScope(Dispatchers.IO)?
replay = 1,
started = SharingStarted.WhileSubscribed()
)
What docs suggests for externalScope parameter:
A CoroutineScope that is used to share the flow. This scope should live longer than any consumer to keep the shared flow alive as long as needed.
However, looking for answer on how to stop subscribing a Flow, the most voted answer in 2nd link says:
A solution is not to cancel the flow, but the scope it's launched in.
For me, these answers are contradictory in SharedFlow's case. And unfortunately my Presenter/ViewModel still receives newest data even after its onCleared was called.
How to prevent that? This is an example how I consume this Flow in my Presenter/ViewModel:
fun doSomethingUseful(): Flow<OtherModel> {
return repository.latestNews.map(OtherModel)
If this might help, I'm using MVI architecture so doSomethingUseful reacts to some intents created by the user.
Following the examples from Kotlin flows, a StateFlow can be exposed from the LatestNewsViewModel so that the View can listen for UI state updates and inherently make the screen state survive configuration changes. The class responsible for updating a MutableStateFlow is the producer, and all classes collecting from the StateFlow are the consumers.
Kotlin flows on Android 1 Creating a flow. To create flows, use the flow builder APIs. ... 2 Modifying the stream. ... 3 Collecting from a flow. ... 4 Catching unexpected exceptions. ... 5 Executing in a different CoroutineContext. ... 6 Flows in Jetpack libraries. ... 7 Convert callback-based APIs to flows. ... 8 Additional flow resources
You can turn cold flows hot by using the shareIn operator. Using the callbackFlow created in Kotlin flows as an example, instead of having each collector create a new flow, you can share the data retrieved from Firestore between collectors by using shareIn . You need to pass in the following: A CoroutineScope that is used to share the flow.
StateFlow and SharedFlow are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers. StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors. The current state value can also be read through its value property.
Thanks to Mark Keen's comments and post I think I managed to get a satisfactory result.
I've understand that scope defined in shareIn parameter doesn't have to be a same scope that my consumer operates. Changing scope in BasePresenter/BaseViewModel from CoroutineScope to viewModelScope seems to solve the main problem. You don't even need to manually cancel this scope, as defined in Android docs:
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
Just keep in mind that default viewModelScope dispatcher is Main which is not obvious and it might not be what you want! To change dispatcher, use viewModelScope.launch(YourDispatcher).
What is more, my hot SharedFlow is transformed from another cold Flow that is created on callbackFlow callback API (which is based on Channels API - this is complicated...)
After changing collection scope to viewModelScope, I was getting ChildCancelledException: Child of the scoped flow was cancelled exception when emitting new data from that API. This problem is well documented in both issues on GitHub:
As stated, there is a subtle difference between emission using offer and send:
offer is for non-suspending context, while send is for suspending ones.
offer is, unfortunately, non-symmetric to send in terms of propagated exceptions (CancellationException from send is usually ignored, while CancellationException from offer in nom-suspending context is not).
We hope to fix it in #974 either with offerOrClosed or changing offer semantics
As for Kotlin Coroutines of 1.4.2, #974 is not fixed yet - I hope it will in nearest future to avoid unexpected CancellationException.
Lastly, I recommend to play with started parameter in shareIn operator. After all these changes, I had to change from WhileSubscribed() to Lazily in my use case.
I will update this post if I will find any new information. Hopefully my research would save someone's time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With