简体   繁体   中英

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 .

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 should look into StateFlow , where you get access to the value.

https://developer.android.com/kotlin/flow/stateflow-and-sharedflow

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 required default value when initialised. Whereas MutableSharedFlow does not need any default value.

But, if you don't want to receive stream of data, (ie) 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM