Kotlin Flow vs Android LiveData

30,831

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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
                }
        }
    }
}
Share:
30,831
zoha131
Author by

zoha131

Updated on July 05, 2022

Comments

  • zoha131
    zoha131 almost 2 years

    I have some questions about Kotlin Flow

    1. I can observe LiveData from multiple Fragments. Can I do this with Flow? If yes then how?
    2. 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?
    3. Using MutableLiveData I can update data from anywhere using the variable reference. Is there any way to do the same with Flow?

    I have a use-case like: I will observe a SharedPreferences using callbackFlow{...} which will give me a single source Flow. 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.

  • zoha131
    zoha131 over 4 years
    I don't know why but I had an assumption that I can't call collect () function in multiple places for a single Flow. thanks for the answer.
  • Fatih
    Fatih over 4 years
    No. You can collect the same flow in multiple places. val sharedPref = getSharedPref()and you can use collect in multiple places sharedPref.collect {}. The only thing is because collect is suspend, you need to call it from coroutine block. And happy to help np :)
  • zoha131
    zoha131 over 4 years
    for my third question, the workaround might be a broadcast channel.
  • Fatih
    Fatih over 4 years
    You can check this commit for using channels instead of live data. github.com/android/plaid/pull/770/commits/…
  • IgorGanapolsky
    IgorGanapolsky over 4 years
    @Faith Google recommends staying away from Channels API unless really necessary
  • Fatih
    Fatih over 4 years
    Yes you are right. This is where flow comes in. Channels have lots of things you have to take care of and they are hot meaning they are always open even if there is no observers. But with flow you can get the same benefits without any concerns because they are cold. So instead of channel I think it is better to use flow
  • IgorGanapolsky
    IgorGanapolsky about 4 years
    There is emit api on LiveData as well as Flow?
  • IgorGanapolsky
    IgorGanapolsky about 4 years
    When do we need to use this?
  • IgorGanapolsky
    IgorGanapolsky about 4 years
    For one-shot operations (i.e. Database read), LiveData is sufficient.
  • Fatih
    Fatih about 4 years
    There is emit api on liveData builder developer.android.com/topic/libraries/architecture/…
  • Samuel Urbanowicz
    Samuel Urbanowicz about 4 years
    When you want to satisfy an API which requires LiveData with a Flow instance
  • Mark
    Mark about 4 years
    In the second example, why are you collecting and emitting? Why not just return the mapped flow?
  • Fatih
    Fatih about 4 years
    Actually you are right. It could just return the flow itself after mapping. I will edit it. Thanks :)
  • Ted Hopp
    Ted Hopp over 3 years
    In the Fragment, shouldn't it be viewModel.getWeatherForecast().collect {...} instead of viewModel.getWeatherForecastEveryTwoSeconds().collect {...}? The latter method doesn't exist in the view model.
  • Psijic
    Psijic over 3 years
    How does it possible to emit data within Firebase listeners? override fun onDataChange(dataSnapshot: DataSnapshot) { override fun onCancelled(databaseError: DatabaseError) {
  • Amr
    Amr about 3 years
    when you have room database, and you want to observe all changes happens inside your database, but because you are using clean architecture your repository shouldn't return live data -since it's platform related- as a clean solution your doa classes should return flow also your repository should return flow, then transform them into livedata as shown in the answer, make sure that the same doa instance is used for retrieving and updating data in order to get flow works properly
  • Astha Garg
    Astha Garg almost 3 years
    Great answer! But can you please explain why is Flow even required when we already have LiveData? I'm new to Kotlin flow
  • Astha Garg
    Astha Garg almost 3 years
    "Don't confuse it with LiveData." How? I'm new to Kotin flow and so far I have understood that same functionality can be achieved using LiveData as well. Can you please explain in detail. Thanks in advance
  • gts13
    gts13 almost 3 years
    @AsthaGarg as the documentation says LiveData is a lifecycle-aware observable and it is mainly used between ViewModel and Fragments/Activities. Of course, you can use LiveData to get updates from for example databases, but for this reason, now you should use Flow. This page developer.android.com/kotlin/flow explains it better than I can do. And keep in mind now the StateFlow/SharedFlow is the new LiveData
  • JacksOnF1re
    JacksOnF1re almost 3 years
    Be aware that you should not collect directly after launch. Use repeatOnLifecycle
  • Filip Östermark
    Filip Östermark about 2 years
    @Amr Is there any reason not to prefer lifecycleScope.launch { flow.collect() { [...] } } (except for the 'boilerplatiness') in order to avoid the additional dependency of using LiveData?
  • Amr
    Amr about 2 years
    @FilipÖstermark In fact now all are replacing LiveData with Flow. Here is more info techs are sunsetting LiveData is included ---------------- Guy from google writes how to migrate from LiveData to Flow ---------------- Here how to collect flow normally