Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the value of a Flow outside a coroutine?

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.

like image 791
maxbeaudoin Avatar asked Apr 13 '20 20:04

maxbeaudoin


People also ask

How do you collect data from flow?

Collecting from a flowUse a terminal operator to trigger the flow to start listening for values. To get all the values in the stream as they're emitted, use collect . You can learn more about terminal operators in the official flow documentation.

What is buffer in Kotlin flow?

The buffer function actually creates a second coroutine that allows the flow and collect functions to execute concurrently. Without the call to buffer , each element must be done with the collect before flow can continue with the next element, because both functions are executed by the same coroutine.

Is flow lifecycle aware?

Bookmark this question. Show activity on this post. We know that LiveData is Lifecycle aware and if there's a configuration change LiveData object doesn't re-query from Database (local/remote) each and every time and it gets updated only if there's any update in the data.

How do you wait in a coroutine?

To wait for a coroutine to finish, you can call Job. join . join is a suspending function, meaning that the coroutine calling it will be suspended until it is told to resume. At the point of suspension, the executing thread is released to any other available coroutines (that are sharing that thread or thread pool).


Video Answer


4 Answers

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.

like image 151
Max Avatar answered Oct 20 '22 01:10

Max


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.

like image 42
CommonsWare Avatar answered Oct 20 '22 00:10

CommonsWare


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.

like image 32
Nandha Kumar Avatar answered Oct 20 '22 02:10

Nandha Kumar


You could use something like this:

    fun <T> SharedFlow<T>.getValueBlockedOrNull(): T? {
        var value: T?
        runBlocking(Dispatchers.Default) {
            value = when ([email protected]()) {
                true -> null
                else -> [email protected]()
            }
        }
        return value
    }
like image 41
braintrapp Avatar answered Oct 20 '22 02:10

braintrapp