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.
Related
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.
Currently struggling with this one, and so far no combination of SharedFlow and StateFlow have worked.
I have a flow that might have already started with a value, or not.
Using that flow I want to collect any new values that are emitted after I start collecting.
At this moment all my attempts have always failed, no matter what I try it always gets the current value as soon as I start collecting.
An example of what I am trying to achieve:
Given a Flow (could be any type, Int is just for simplification)
with the following timeline: value 4 is emitted | value 2 is emitted | value 10 is emitted
I want to be able to do the following:
If I start collecting after value 4 has already been emitted, I want to only receive anything after that, in this case it would collect 2 and 10 once emitted
If I start collecting after value 2 then it would only receive the 10
If I start collecting before 4 then it would receive 4, 2 and 10
Tried SharedFlow and Stateflow, tried with replay = 0 and WhileSubscribed, no combination I could find would do what I am looking for.
The only workaround so far that I found was to locally register the time I start my .collect{ } and compare with the start time of the item I receive in the collect. In this case I have the object I am using has a specific origin time, but this workaround will not work for everything like the example above with Integers.
EDIT: Adding implementation example as requested for SharedFlow
This is tied to a Room database call that returns a Flow<MyObject>
MyFragment.kt
lifecycleScope.launch(Dispatchers.IO) {
viewModel.getMyObjectFlow.shareIn(
viewModel.viewModelScope, // also tried with fragment lifecyclescope
SharingStarted.WhileSubscribed(), // also tried with the other 2 options
replay = 0,
).collect{
...
}
}
You have a misconception of how flows work. They always emit only after you start collecting. They emit on-demand. Let's get this example:
val flow1 = flow {
println("Emitting 1")
emit(1)
delay(10.seconds)
println("Emitting 2")
emit(2)
}
delay(5.seconds)
println("Start collecting")
flow1.collect {
println("Collected: $it")
}
The output is:
Start collecting
Emitting 1
Collected: 1
not:
Emitting 1
Start collecting
Collected: 1
This is because flow starts emitting only after you start collecting it. Otherwise, it would have nowhere to emit.
Of course, there are flows which emit from some kind of a cache, queue or a buffer. For example shared flows do this. In that case it looks like you collect after emitting. But this is not really the case. Technically speaking, it works like this:
val buffer = listOf(1 , 2, 3)
val flow1 = flow {
buffer.forEach {
println("Emitting $it")
emit(it)
}
}
It still emits after you start collecting, but it just emits from the cache. Of course, the item was added to the cache before you started collecting, but this is entirely abstracted from you. You can't know why a flow emitted an item. From the collector perspective it always emitted just now, not in the past. Similarly, you can't know if a webserver read the data from the DB or a cache - this is abstracted from you.
Summing up: it is not possible to collect only new items from just any flow in an universal way. Flows in general don't understand the concept of "new items". They just emit, but you don't know why they do this. Maybe they somehow generate items on-the-fly, maybe they passively observe external events or maybe they re-transmit some items that they collected from another flow. You don't know that.
While developing your solution, you need to understand what was the source of items and develop your code accordingly. For example, if the source is a regular cold flow, then it never starts doing anything before you start collecting. If the source is a state flow, you can just drop the first item. If it is a shared flow or a flow with some replay buffer, then the situation is more complicated.
One possible approach would be to start collecting earlier than we need, initially ignore all collected items and at some point in time start processing them. But this is still far from perfect and it may not work as we expect.
It doesn't make sense to use shareIn at the use site like that. You're creating a shared Flow that cannot be shared because you don't store the reference for other classes to access and use.
Anyway, the problem is that you are creating the SharedFlow at the use site, so your shared flow only begins collecting from upstream when the fragment calls this code. If the upstream flow is cold, then you will be getting the first value emitted by the cold flow.
The SharedFlow should be created in the ViewModel and put in a property so each Fragment can collect from the same instance. You'll want to use SharingStarted.Eagerly to prevent the cold upstream flow from restarting from the beginning when there are new subscribers after a break.
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
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>
I would like to use a LiveData for handling kind of notifications, as it is already lifecycle aware, between a custom view and its wrapping fragment. But it seems that a LiveData may loose values : it will only update to its most recent state and also won't fire values during inactive state of its observers.
I've looked at the SingleLiveEvent purpose from Google code samples, but that solution does not seems to be battle tested yet, and the ticket is still open with recent tries to improve the solution.
So I am looking for a simple way to get notified about events, and at the same time not being worried about Lifecycles (that was why I went for LiveData as a first solution), and that could handle multiple observers.
Is there an existing solution for that ? If I try to implement it, it is sure that I will land into at least an anti-pattern.
One easy way (perhaps too easy) is to use callbacks : but the problem is that I need this feature for several callbacks in my component, leading me in a poor architecture. And also, I want a subscribe system, meaning that there could be more than one observer.
One other way, could be to use RxJava and tranform it into a LiveData, with LiveDataReactiveStreams.fromPublisher() : but now the question is whether I will get all values or only the last one. That's the closest solution I could deal with.
As an interesting alternative there could be AutoDispose or RxLifecycle. And an interesting resource I've found : Blog post on LiveData
What are your thoughts, suggestions ?
Also, please notice that I need this communication from a component wrapped into a Fragment (ChessBoard) toward another Fragment (ChessHistory). So they are both lifecycle aware.
It is not ideal, but this does the trick for me:
/**
* This LiveData will deliver values even when they are
* posted very quickly one after another.
*/
class ValueKeeperLiveData<T> : MutableLiveData<T>() {
private val queuedValues: Queue<T> = LinkedList<T>()
#Synchronized
override fun postValue(value: T) {
// We queue the value to ensure it is delivered
// even if several ones are posted right after.
// Then we call the base, which will eventually
// call setValue().
queuedValues.offer(value)
super.postValue(value)
}
#MainThread
#Synchronized
override fun setValue(value: T) {
// We first try to remove the value from the queue just
// in case this line was reached from postValue(),
// otherwise we will have it duplicated in the queue.
queuedValues.remove(value)
// We queue the new value and finally deliver the
// entire queue of values to the observers.
queuedValues.offer(value)
while (!queuedValues.isEmpty())
super.setValue(queuedValues.poll())
}
}
The main problem with this solution is that if the observers are inactive at the time the values are delivered via super.setValue(), then the values will be lost regardless. However, it solves the issue of losing values when several new ones are posted very quickly – which, in my opinion, is usually a bigger problem than losing values because your observer is inactive. After all, you can always do myLiveData.observeForever() from a non-lifecycle-aware object in order to receive all notifications.
Not sure this will be enough for you, but I hope it can help you or give you some ideas about how to implement your own approach.