How can I get the latest value of a Flow? I don't have a StateFlow where I need that latest value. This is the condensed scenario:
There is a repository exposing a StateFlow
val repositoryExposedStateFlow: StateFlow<SomeType> = MutableStateFlow(...)
Additionally there are mappers transforming that StateFlow like
val mappedFlow: Flow<SomeOtherType> = repositoryExposedStateFlow.flatMapLatest { ... }
mappedFlow is no StateFlow anymore, but just a Flow. Thus, I cannot get the latest/current value as I can when there's StateFlow.
Anyhow, I need the latest value in that Flow at some point. Since this point is not in a ViewModel, but some Use Case implementation, I cannot simply perform a stateIn and hold the latest value in the ViewModel all the time the ViewModel is alive -- otherwise I had to pass on the value to all Use Cases. Actually, within a Use Case I trigger a network refresh which leads to emitting of new values on the StateFlow and thus on the mappedFlow, too.
In the Use Cases I have CoroutineScopes though. So I came up with
suspend fun <T> Flow<T>.getState(): T {
return coroutineScope {
val result = stateIn(
scope = this
).value
coroutineContext.cancelChildren()
result
}
}
Without using coroutineContext.cancelChildren() the method will never return, because coroutineScope blocks the caller until all child coroutines have finished. As stateIn never finishes, I manually cancel all children.
Apparently this is a bad thing to do.
But how can I solve this problem in a better way? In my perception the problem arises from StateFlow mapping resulting in regular Flow instances.
Yes, all you need is to call first() on the flow. Since it is backed by a StateFlow upstream, the first() call will get the current value of that backing StateFlow, run it through whatever transformations happen from the downstream operators, and return that value.
This effectively gets you the same result as your attempt above.
The downside is that all the downstream operators must be run, so it is potentially expensive.
This is only possible if there is an upstream StateFlow. Otherwise, there is no concept of a latest value for you to be able to retrieve.
I would challenge your need to get the latest value, though. Typically, you collect flows, so you're already working with a current value. Flows are intended for reactive programming.
Related
Android, Kotlin
I have the following livedata in my datasource class, I cannot change this to StateFlow, so need to convert it to StateFlow in my viewModel
val trackingCatalogInitialLoadLiveData: LiveData<Pair<CatalogTracking, Int>> by lazy {
instantSearchDataSourceLiveData.switchMap { instantSearchDataSource ->
instantSearchDataSource.initialLoadLiveData
}
}
In My ViewModel I have the following, and this is the part I am not sure about if this is the correct way to convert LiveData to StateFlow:
val trackingCatalogInitialLoadStateFlow: StateFlow<Pair<CatalogTracking, Int>> by lazy {
instantSearchDataSourceFactory.trackingCatalogInitialLoadLiveData.asFlow()
.stateIn(viewModelScope, SharingStarted.Lazily, Pair(CatalogTracking(), 0))
}
Then in my fragment I just collect the results
coroutineScope.launch {
mInstantSearchViewModel.trackingCatalogInitialLoadStateFlow.collect { trackingPair ->
// code here
}
Is this the best practice to convert LiveData to StateFlow? Anything I should be looking out for?
You don't need to use by lazy. asFlow() and stateIn() both create simple wrappers, so they are trivial to call directly in the property initializer.
As #Joffrey said, if you use SharingStarted.Lazily, inspecting the flow's value before it has any collectors will incorrectly show your provided initial value. Since LiveData is hot, starting your StateFlow lazily doesn't buy you a lot. The underlying coroutine that transfers LiveData values to the StateFlow is doing a trivial amount of work.
If you don't need to inspect the value (in most cases you probably don't), then it should be fine to leave it as a cold Flow. Even though the Flow from asFlow() is cold, the underlying LiveData is still hot, so when collectors of the flow collect it, they'll always get the latest value. The main behavior difference would be if your data source does not provide a guaranteed initial value for the LiveData, then a StateFlow gives you the opportunity to emit your provided default initially without waiting for the LiveData to publish its first value.
Can I create a StateFlow from a Flow so that I can get the .value of it? Is there a way to do it without using .collect?
Something like
val myStateFlow = StateFlow<MyDataType>(this.existingFlow)
So that later, when someone clicks a button, I can say what the last value of the flow is?
fun handleButton() {
val lastValue = myStateFlow.value
print(lastValue)
}
I would prefer not using collect, since I dont want the flow to flow until someone else decides to collect it.
There is a built-in function called stateIn.
Is there a way to do it without using .collect?
I would prefer not using collect, since I dont want the flow to flow until someone else decides to collect it.
If you use the non-suspending overload of stateIn, the flow collection doesn't have to start right away, but you have to provide an initial value for the state (because this call cannot wait for the first value of the flow if it doesn't suspend).
In that case, you can play with the started argument to make the actual collection lazy, but note that accessing myStateFlow.value won't trigger the collection of the flow and will just return the initial value over and over. Only terminal flow operators (like collect) on the StateFlow will actually trigger the underlying flow collection, which is probably not what you want (but maybe it is!).
Note that you have to have a coroutine running to get the values from the initial flow and set the state accordingly if you want to access values via myStateFlow.value. This is actually what stateIn does by default: it starts a coroutine that collects the flow to set the StateFlow's value - so it technically uses collect.
I am trying to figure out how jobs with coroutines work. Basically, I want to launch this coroutine from FirstFragment and after that navigate to SecondFragment and get notified when this job is done. I call getData() in FirstFragment onViewCreated() and navigate to SecondFragment. Whether I write getData().isCompleted or getData().invokeOnCompletion { } in SecondFragment nothing happens. I don't know if I am missing something or not starting job correctly or something else.
private val _data = MutableStateFlow<GetResource<String>?>(null)
val data: StateFlow<GetResource<String>?> = _data
fun getData() = viewModelScope.launch {
repository.getData().collect {
_data.value = it
}
}
A Flow from a database never completes because it is supposed to monitor the database for changes indefinitely. It only stops when the coroutine is cancelled. Therefore the Job that collects such a Flow will never complete. Also, if you call getData() on the repo again, you are getting a new Flow instance each time.
Regardless of what you're doing, you need to be sure you are using the same ViewModel instance between both fragments by scoping it to the Activity. (Use by activityViewModels() for example.) This is so the viewModelScope won't be cancelled during the transition between Fragments.
If all you need is a single item from the repo one time, probably the simplest thing to do would be to expose a suspend function from the repo instead of a Flow. Then turn it into a Deferred. Maybe by making it a Lazy, you can selectively decide when to start retrieving the value. Omit the lazy if you just want to start retrieving the value immediately when the first Fragment starts.
// In the shared view model:
val data: Deferred<GetResource<String>> by lazy {
viewModelScope.async {
repository.getData() // suspend function returning GetResource<String>
}
}
fun startDataRetrieval() { data } // access the lazy property to start its coroutine
// In second fragment:
lifecycleScope.launch {
val value = mySharedViewModel.data.await()
// do something with value
}
But if you have to have the Flow because you’re using it for other purposes:
If you just want the first available value from the Flow, have the second Fragment monitor your data StateFlow for its first valid value.
lifecycleScope.launch {
val value = mySharedViewModel.data.filterNotNull().first()
// do something with first arrived value
}
And you can use SharedFlow so you don’t have to make the data type nullable. If you do this you can omit filterNotNull() above. In your ViewModel, it’s easier to do this with shareIn than your code that has to use a backing property and manually collect the source.
val data: SharedFlow<GetResource<String>> = repository.getData()
.shareIn(viewModelScope, replay = 1, SharingStarted.Eagerly)
If you need to wait before starting the collection to the SharedFlow, then you could make the property lazy.
Agreed with #Tenfour04 's answer, I would like to contribute a little more.
If you really want to control over the jobs or Structured Concurrency, i would suggest use custom way of handling the coroutine rather than coupled your code with the viewModelScope.
There are couple of things you need to make sure:
1- What happen when cancellation or exception occurrs
2- you have to manage the lifecycle of the coroutine(CoroutineScope)
3- Cancelling scope, depends on usecase like problem facing you are right now
4- Scope of ViewModel e.g: Either it is tied to activity(Shared ViewModel) or for specific fragment.
If you are not handling either of these carefully specifically first 3, your are more likely to leaking the coroutine your are gurenteed gonna get misbehavior of you app.
Whenever you start any coroutine in Custom way you have to make sure, what is going to be the lifecycle, when it gonna end, This is so important, it can cause real problems
Luckily, i have this sample of CustomViewModel using Jobs: Structured Concurrency sample code
I tried using Kotlin Flow to be some kind of message container which should pass this message to all observers (collectors). I do not want to use LiveData on purpose because it need to be aware of lifecycle.
Unfortunately I have noticed that if one collector collects message from flow no one else can receive it.
What could I use to achieve "one input - many output".
You can use StateFlow or SharedFlow, they are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.
From the documentation, available here:
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.
SharedFlow: a hot flow that emits values to all consumers that collect from it. A SharedFlow is a highly-configurable generalization of StateFlow.
A simple example using state flow with view model:
class myViewModel() : ViewModel() {
val messageStateFlow = MutableStateFlow("My inicial awesome message")
}
You can emit a new value using some scope:
yourScope.launch {
messageStateFlow.emit("My new awesome message")
}
You can collect a value using some scope:
yourScope.launch {
messageStateFlow.collect {
// do something with your message
}
}
Attention: Never collect a flow from the UI directly from launch or the launchIn extension function to update UI. These functions process events even when the view is not visible. You can use repeatOnLifecycle as the documentation sugests.
You can try BehaviorSubject from rxJava. Is more comfortable to use than poor kotlin.flow. Seems like this link is for you: BehaviorSubject vs PublishSubject
val behaviorSubject = BehaviorSubject.create<MyObject> {
// for example you can emit new item with it.onNext(),
// finish with error like it.onError() or just finish with it.onComplete()
somethingToEmit()
}
behaviorSubject.subscribe {
somethingToHandle()
}
How can I get the value of a Flow outside a coroutine similarly to LiveData?
// Suspend function 'first' should be called only from a coroutine or another suspend function
flowOf(1).first()
// value is null
flowOf(1).asLiveData().value
// works
MutableLiveData(1).value
Context
I'm avoiding LiveData in the repository layer in favor of Flow. Yet, I need to set, observe and collect the value for immediate consumption. The later is useful for authentication purpose in a OkHttp3 Interceptor.
You can do this
val flowValue: SomeType
runBlocking(Dispatchers.IO) {
flowValue = myFlow.first()
}
Yes its not exactly what Flow was made for.
But its not always possible to make everything asynchronous and for that matter it may not even always be possible to 'just make a synchronous method'. For instance the current Datastore releases (that are supposed to replace shared preferences on Android) do only expose Flow and nothing else. Which means that you will very easiely get into such a situation, given that none of the Lifecycle methods of Activities or Fragments are coroutines.
If you can help it you should always call coroutines from suspend functions and avoid making runBlocking calls. A lot of the time it works like this. But it´s not a surefire way that works all the time. You can introduce deadlocks with runBlocking.
Well... what you're looking for isn't really what Flow is for. Flow is just a stream. It is not a value holder, so there is nothing for you retrieve.
So, there are two major avenues to go down, depending on what your interceptor needs.
Perhaps your interceptor can live without the data from the repository. IOW, you'll use the data if it exists, but otherwise the interceptor can continue along. In that case, you can have your repository emit a stream but also maintain a "current value" cache that your interceptor can use. That could be via:
BroadcastChannel
LiveData
a simple property in the repository that you update internally and expose as a val
If your interceptor needs the data, though, then none of those will work directly, because they will all result in the interceptor getting null if the data is not yet ready. What you would need is a call that can block, but perhaps evaluates quickly if the data is ready via some form of cache. The details of that will vary a lot based on the implementation of the repository and what is supplying the Flow in the first place.
You could use something like this:
fun <T> SharedFlow<T>.getValueBlockedOrNull(): T? {
var value: T?
runBlocking(Dispatchers.Default) {
value = when (this#getValueBlockedOrNull.replayCache.isEmpty()) {
true -> null
else -> this#getValueBlockedOrNull.firstOrNull()
}
}
return value
}
You can use MutableStateFlow and MutableSharedFlow for emitting the data from coroutine and receiving the data inside Activity/Fragment. MutableStateFlow can be used for state management. It requires default value when initialised. Whereas MutableSharedFlow does not need any default value.
But, if you don't want to receive stream of data, (i.e) your API call sends data only once, you can use suspend function inside coroutine scope and the function will perform the task and return the result like synchronous function call.
To get the value of a Flow outside of a coroutine, the best option is to create the flow as a StateFlow and then call the value property on the StateFlow.
class MyClass {
private val mutableProperty = MutableStateFlow(1)
val property = mutableProperty.asStateFlow()
...
mutableProperty.value = 2
}
...
val readProperty = MyClass().property.value
val propertyAsFlow = MyClass().property as Flow<Int>