简体   繁体   English

Kotlin Flow 与 Android LiveData

[英]Kotlin Flow vs Android LiveData

I have some questions about Kotlin Flow我有一些关于Kotlin Flow的问题

  1. I can observe LiveData from multiple Fragments.我可以从多个片段观察LiveData Can I do this with Flow ?我可以用Flow做到这一点吗? If yes then how?如果是那么如何?
  2. We can have multiple LiveData from a single LiveData using map & switchMap .我们可以使用mapLiveData从单个LiveData获得多个switchMap Is there any way to have multiple Flow from a single source Flow ?有什么办法可以从一个源Flow获得多个Flow吗?
  3. Using MutableLiveData I can update data from anywhere using the variable reference.使用MutableLiveData我可以使用变量引用从任何地方更新数据。 Is there any way to do the same with Flow ?有什么办法可以对Flow做同样的事情吗?

I have a use-case like: I will observe a SharedPreferences using callbackFlow{...} which will give me a single source Flow.我有一个这样的用例:我将使用callbackFlow{...}观察SharedPreferences ,这将给我一个单一的源流。 From that Flow, I want to create multiple Flow for each key-value pair.从该流中,我想为每个键值对创建多个流。

These might sound silly questions.这些问题听起来可能很愚蠢。 I am new to Rx and Flow world.我是 Rx 和 Flow 世界的新手。

I can observe LiveData from multiple Fragments.我可以从多个片段中观察 LiveData。 Can I do this with Flow?我可以用 Flow 做到这一点吗? If yes then how?如果是,那怎么办?

Yes.是的。 You can do this with emit and collect .您可以使用emitcollect来做到这一点。 Think emit is similar to live data postValue and collect is similar to observe .认为emit类似于实时数据postValue ,而collect类似于observe Lets give an example.让我们举个例子。

Repository存储库

// I just faked the weather forecast
val weatherForecast = listOf("10", "12", "9")

// This function returns flow of forecast data
// Whenever the data is fetched, it is emitted so that
// collector can collect (if there is any)
fun getWeatherForecastEveryTwoSeconds(): Flow<String> = flow { 
    for (i in weatherForecast) {
        delay(2000)
        emit(i)
    }
}

ViewModel视图模型

fun getWeatherForecast(): Flow<String> {
    return forecastRepository.getWeatherForecastEveryTwoSeconds()
}

Fragment分段

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // Collect is suspend function. So you have to call it from a 
    // coroutine scope. You can create a new coroutine or just use 
    // lifecycleScope
    // https://developer.android.com/topic/libraries/architecture/coroutines
    lifecycleScope.launch {
            viewModel.getWeatherForecast().collect {
                    // Use the weather forecast data
                    // This will be called 3 times since we have 3 
                    // weather forecast data
            }
    }
}

We can have multiple LiveData from a single LiveData using map& switchMap.我们可以使用 map&switchMap 从单个 LiveData 中获取多个 LiveData。 Is there any way to have multiple Flow from a single source Flow?有没有办法从单个源流中获得多个流?

Flow is very handy.流量非常好用。 You can just create flow inside flow.您可以在流中创建流。 Lets say you want to append degree sign to each of the weather forecast data.假设您要对每个天气预报数据使用 append 度数符号。

ViewModel视图模型

fun getWeatherForecast(): Flow<String> {
    return flow {
        forecastRepository
            .getWeatherForecastEveryTwoSeconds(spendingDetailsRequest)
                .map {
                    it + " °C"
                }
                .collect {
                    // This will send "10 °C", "12 °C" and "9 °C" respectively
                    emit(it) 
                }
    }
}

Then collect the data in Fragment same as #1.然后收集与#1相同的片段中的数据。 Here what happens is view model is collecting data from repository and fragment is collecting data from view model.这里发生的是视图 model 正在从存储库中收集数据,片段正在从视图 model 中收集数据。

Using MutableLiveData I can update data from anywhere using the variable reference.使用 MutableLiveData 我可以使用变量引用从任何地方更新数据。 Is there any way to do the same with Flow?有没有办法对 Flow 做同样的事情?

You cant emit value outside of flow.你不能在流量之外发出价值。 The code block inside flow is only executed when there is any collector. flow里面的代码块只有在有collector的时候才会执行。 But you can convert flow to live data by using asLiveData extension from LiveData.但是您可以使用 LiveData 中的 asLiveData 扩展将流转换为实时数据。

ViewModel视图模型

fun getWeatherForecast(): LiveData<String> {
    return forecastRepository
    .getWeatherForecastEveryTwoSeconds()
    .asLiveData() // Convert flow to live data
}

In your case you can do this在你的情况下,你可以这样做

private fun getSharedPrefFlow() = callbackFlow {
    val sharedPref = context?.getSharedPreferences("SHARED_PREF_NAME", MODE_PRIVATE)
    sharedPref?.all?.forEach {
        offer(it)
    }
}

getSharedPrefFlow().collect {
    val key = it.key
    val value = it.value
}

Edit编辑

Thanks to @mark for his comment.感谢@mark 的评论。 Creating a new flow in the view model for getWeatherForecast function is actually unnecessary.在视图 model 中为getWeatherForecast function 创建一个新流实际上是不必要的。 It could be re-written as它可以重写为

fun getWeatherForecast(): Flow<String> {
        return forecastRepository
                .getWeatherForecastEveryTwoSeconds(spendingDetailsRequest)
                    .map {
                        it + " °C"
                    }
    }

There is a new Flow.asLiveData() extension function in the new androidx.lifecycle ktx packages.在新的androidx.lifecycle ktx 包中有一个新的Flow.asLiveData()扩展 function。 You can learn more in my article: https://www.netguru.com/codestories/android-coroutines-%EF%B8%8Fin-2020您可以在我的文章中了解更多信息: https://www.netguru.com/codestories/android-coroutines-%EF%B8%8Fin-2020

In a 3-tier architecture: data-domain-presentation, Flow should take place in the data layer (databases, network, cache...) and then as Samuel Urbanowicz mentioned you can map Flow to LiveData.在 3 层架构中:数据域表示,流应该发生在数据层(数据库、网络、缓存......),然后正如Samuel Urbanowicz提到的,您可以将 map 流到 LiveData。

In general, Flow is almost what the Observable (or Flowable) is for RxJava.一般来说,Flow 几乎就是 RxJava 的 Observable(或 Flowable)。 Don't confuse it with LiveData.不要将它与 LiveData 混淆。

more here: https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9更多信息: https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9

Just wanted to add on to Fatih's answer here since it's been sometime.只是想在这里补充 Fatih 的回答,因为已经有一段时间了。

I can observe LiveData from multiple Fragments.我可以从多个片段观察 LiveData。 Can I do this with Flow?我可以用 Flow 做到这一点吗? If yes then how?如果是那么如何?

Yes.是的。 But the way you should do that has changed a bit.但是您应该这样做的方式有所改变。 You should use repeatOnLifecycle to more safely post to the UI from Flows.您应该使用repeatOnLifecycle来更安全地从 Flows 发布到 UI。 It's new and the docs are scarce but this is what it looks like:它是新的,文档很少,但这就是它的样子:

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.getWeatherForecast().collect {
           // Safely update the UI
        }
    }
}

This ensures that the weather forecast only updates the UI when it's showing and doesn't waste resources.这可确保天气预报仅在显示时更新 UI,而不会浪费资源。 And yes, you can do this on multiple Fragments from the same Flow at the same time.是的,您可以同时对来自同一流的多个片段执行此操作。

We can have multiple LiveData from a single LiveData using map& switchMap.我们可以使用 map 和 switchMap 从单个 LiveData 中获得多个 LiveData。 Is there any way to have multiple Flow from a single source Flow?有什么办法可以从一个源流中获得多个流?

This is an obvious yes.这是一个明显的肯定。 Flows have tons of operators like map and switchMap流有大量的运算符,如 map 和 switchMap

Using MutableLiveData I can update data from anywhere using the variable reference.使用 MutableLiveData 我可以使用变量引用从任何地方更新数据。 Is there any way to do the same with Flow?有什么办法可以对 Flow 做同样的事情吗?

Yes.是的。 We now have MutableStateFlow which is very close to and more powerful that MutableLiveData .我们现在有了MutableStateFlow ,它非常接近并且比MutableLiveData更强大。

val textFlow = MutableStateFlow("Hello")

someButton.onPress {
    textFlow.value = "World"
}

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        textFlow.collect {
           someTextView.text = it
        }
    }
}

The SharedPreferences code above can be modified a bit:上面的 SharedPreferences 代码可以稍微修改一下:

private fun getSharedPrefFlow() = callbackFlow {
    val sharedPref = context.getSharedPreferences("SHARED_PREF_NAME", MODE_PRIVATE)
    sharedPref?.all?.forEach {
        trySend(it) // offer is deprecated
    }
}

init {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            getSharedPrefFlow()
                .filter { it.key == BIRTHDAY_KEY }
                .collect {
                    birthdayTextView.text = it.value
                }
        }
    }
}
  1. Of course you can.当然可以。 You just launch it wherever you want using a combination of launchIn(scope), onEach() etc.您只需使用 launchIn(scope)、onEach() 等的组合在任何您想要的地方启动它。
  2. There are plenty of operators/transformations for flow.有很多用于流的运算符/转换。 It's easy enough to write one yourself - just look at the source and try:)自己写一个很容易 - 只需查看源代码并尝试:)
  3. You could probably work around it by having eg a private broadcast channel and then consuming it using consumeAsFlow(if I remember the name correctly)您可能可以通过拥有一个私人广播频道然后使用consumeAsFlow(如果我没记错的话)使用它来解决它

For your usecase - you could do:对于您的用例 - 您可以这样做:

''' yourFlow.filter(...) - check for key value.onEach(...) - do your magical logic.launchIn(viewModelScope) - or wherever ''' Sorry for the terrible formatting, I'm writing from phone. ''' yourFlow.filter(...) - 检查键 value.onEach(...) - 做你的神奇 logic.launchIn(viewModelScope) - 或任何地方 ''' 对不起,糟糕的格式,我从电话。

Hope this helps.希望这可以帮助。 Cheers!干杯!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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