Kotlin Flow vs Android liveData

我有一些关于 Kotlin Flow的问题

  1. 我可以从多个片段中观察到 LiveData。我可以用 Flow做到这一点吗? 如果可以,那么怎么做?
  2. 我们可以有多个 LiveData从一个单一的 LiveData使用 mapswitchMap。有没有什么办法可以从一个源 Flow获得多个 Flow
  3. 使用 MutableLiveData,我可以使用变量引用从任何地方更新数据。有没有办法对 Flow做同样的事情?

我有一个用例: 我将使用 callbackFlow{...}观察一个 SharedPreferences,它将给我一个单一的源流。从这个 Flow,我想为每个键-值对创建多个 Flow。

这些问题可能听起来很愚蠢,我对 Rx 和 Flow 的世界还是个新手。

43816 次浏览

I can observe LiveData from multiple Fragments. Can I do this with Flow? If yes then how?

Yes. You can do this with emit and collect. Think emit is similar to live data postValue and collect is similar to 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. 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.

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. Here what happens is view model is collecting data from repository and fragment is collecting data from view model.

Using MutableLiveData I can update data from anywhere using the variable reference. Is there any way to do the same with Flow?

You cant emit value outside of flow. The code block inside flow is only executed when there is any collector. But you can convert flow to live data by using asLiveData extension from LiveData.

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. Creating a new flow in the view model for getWeatherForecast function is actually unnecessary. 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. You can learn more in my article: 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.

In general, Flow is almost what the Observable (or Flowable) is for RxJava. Don't confuse it with LiveData.

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

Just wanted to add on to Fatih's answer here since it's been sometime.

I can observe LiveData from multiple Fragments. Can I do this with 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. 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. 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. 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

Using MutableLiveData I can update data from anywhere using the variable reference. Is there any way to do the same with Flow?

Yes. We now have MutableStateFlow which is very close to and more powerful that 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:

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
}
}
}
}