Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Flow vs Android LiveData

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.

like image 613
zoha131 Avatar asked Nov 08 '19 20:11

zoha131


People also ask

Is LiveData deprecated?

This function is deprecated. This extension method is not required when using Kotlin 1.4.

What is flow in Android Kotlin?

In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value. For example, you can use a flow to receive live updates from a database. Flows are built on top of coroutines and can provide multiple values.

Why StateFlow is better than LiveData?

StateFlow requires an initial state to be passed in to the constructor, while LiveData does not. LiveData. observe() automatically unregisters the consumer when the view goes to the STOPPED state, whereas collecting from a StateFlow or any other flow does not stop collecting automatically.

Is kotlin flow lifecycle aware?

Kotlin coroutines provide an API that enables you to write asynchronous code. With Kotlin coroutines, you can define a CoroutineScope , which helps you to manage when your coroutines should run. Each asynchronous operation runs within a particular scope.


3 Answers

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

like image 138
Fatih Avatar answered Oct 19 '22 03:10

Fatih


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

like image 23
Samuel Urbanowicz Avatar answered Oct 19 '22 01:10

Samuel Urbanowicz


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

like image 14
gts13 Avatar answered Oct 19 '22 01:10

gts13