flow-lifecycle-observer
flow-lifecycle-observer copied to clipboard
Collection is cancelled and restarted on rotation
Usually ongoing operations should not be interrupted due to rotation. Suggest to add a delay before cancelling the job like the androidx livedata { }
builder.
In their version they have an optional timeout parameter which means that observers in the newly created activity after rotation have time to subscribe before the observers in the old activity unsubscribe. Thereby keeping any upstream shared flow alive.
Actually now that I go back and check i see that asLiveData() creates a live data which cancels in onInactive anway. So maybe simply using asLiveData is the best solution here.
Actually now that I go back and check i see that asLiveData() creates a live data which cancels in onInactive anway. So maybe simply using asLiveData is the best solution here.
Let's talk a bit architecture here.
The problem with converting to LiveData in the ViewModel is that in that scenario, one needs to think twice when injecting repositories directly in use-cases implementations: you have now two sources of data: a LiveData, which is ViewModel-scoped, and a backing State/SharedFlow, implemented in the repositories/data sources, which might be Singleton-scoped. To circumvent that duality, you can make your use-cases ViewModel-scoped and inject the viewModels on them instead. But now, they are ViewModel-dependent and can not be singleton-scoped anymore. If well done, it probably introduces no bugs or inconsistencies, but it surely makes the architecture more complicated. In services, for example, you should not expect any LiveData usage or ViewModel access, so how would you access ViewModel-scoped objects?
One might think you can then convert to LiveData at the repositories and data sources. Then we go back to step 0: the repositories/data sources should not be platform-dependent. I implement mine as pure Kotlin modules. This enables a solid multi-platform core on your projects.
Hi @tom-pratt ,
Revisiting this question, I see that your worries about a delay for keeping upstream flow alive can (and should) be easily handled by the SharedFlow itself -- the observer need not worry about that, nor should it.
You should set a delay in shareIn()
using the stopTimeoutMillis
param.
- [stopTimeoutMillis] — configures a delay (in milliseconds) between the disappearance of the last
- subscriber and the stopping of the sharing coroutine. It defaults to zero (stop immediately).
Example:
myFlow.shareIn(coroutineScope, SharingStarted.WhileSubscribed(stopTimeoutMillis=2000), replay=1)
Then, if all observers are gone (e.g. screen rotation), the flow will keep sharing for at most 2s (upstream flows will not be stopped).
Weighing in my limited understanding, the Flow<T>.asLiveData
function (or alternatively the liveData
builder function) should be thought of as equivalent to the Flow<T>.shareIn
function (or alternatively Flow<T>.stateIn
).
In both cases you're doing the same thing: When the SharedFlow/LiveData becomes "active", it starts collecting the upstream flow (i.e. the flow you called the function on which might be a cold flow, or the contents the block you pass to liveData
) and publishes it itself in a hot way. When the SharedFlow/LiveData becomes "inactive", wait for the timeout and then cancel the original flow and stop publishing.
The meaning of "active/inactive" means different things in different contexts. If it's a LiveData, then it's active if it has >0 active observers. If it's a SharedFlow, then similarly we would use SharingStarted,WhileSubscribed
to only do the sharing when it has >0 subscribers.
So, @tom-pratt I'd recommend either using asLiveData
(with timeout) and not using this library, or using shareIn
(or stateIn
) (with timeout) with SharingStarted.WhileSubscribed
and using this library in your activities/fragments.
As @psteiger says, the advantage of the latter is that you can push shareIn
all the way back to platform-independent code, though that timeout does feel like a platform-specific number, so I'm not totally sure.
@chriscoomber ,
I agree with your comments.
I'd add that if you choose to use LiveData
in your views, and you choose to implement timeout logic in ViewModel
using asLiveData()
, and you choose to use pure Flow
on Data Source/Repository, you must know that your data source will only have the 'shared' behavior (1 upstream collection to N downstream collectors) if you only access your pure Flow data sources through your ViewModel's LiveData (as the Flow
itself will really have only one collector: the LiveData
).
Or you can use LiveData
all the way from the Data Sources / Repositories down to the Views. Then, you create a dependency of the domain logic on the platform.
Or you can use SharedFlow
on Data Sources/Repositories using shareIn()
, and convert to LiveData
on ViewModel
setting the timeout. Then, you'll potentially have both timeouts (SharedFlow.WhileSubscribed
) and LiveData
.
There are some other possible combinations.
I prefer using Flow all the way from data sources to views setting SharedFlow.WhileSubscribed
in shareIn()
.
This lib is for enabling the 'sharing' behavior for SharedFlow
configured with SharedFlow.WhileSubscribed
, where collectors are created on onStart
or onResume
and destroyed on onStop
or onPause
, mimicking the concept of 'active' of the LiveData
component.
Choices :)