Live data, Flow, Shared flow & State flow with ViewModel

You'll learn the differences between the typical observable classes we have in Android


LiveData:

Live data is part of Android Architecture Components which are basically a collection of libraries that help you design robust, testable, and maintainable apps.

It is an Observable data class — Meaning it can be observed by other components — most profoundly UI controllers (Activities/Fragments). So, instead of having a reference of the activity/fragment in your ViewModel (which you shouldn’t have due to leaks), you now have a reference to the ViewModel in the activity/fragment

It is Lifecycle aware— Meaning it sends updates to our UI (Activities/Fragments) only when our view is in the active state. (No memory leaks)

Set the data in LiveData,

  • The MutableLiveData publicly exposes two methods i.e. setValue and postValue to set the data in LiveData.
  • If you are working on the main thread, then both setValue and postValue will work in the same manner i.e. they will update the value and notify the observers.
  • If working in some background thread, then you can’t use setValue. You have to use postValue.

Observe the data changes,

If there is a change in data then that data will be reflected to all the observers associated with it but it only notifies the changes to the observers that are live or in the active state and not to that observer that are in the inactive state.

Using LiveData provides the following advantages:

  • No memory leaks
  • Ensures your UI matches your data state
  • No crashes due to stopped activities
  • Always up to date data
  • Proper configuration changes
  • Sharing resources

Drawbacks Of LiveData

  • Lack of control over the execution context
  • Threading issues especially when used in Repositories
  • Not built on top of Coroutines and Kotlin
  • Lack of seamless data integration between database and UI especially using Room.
  • Lots of Boiler Plate Codes especially while using Transformations


Flow:

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.

Flow can handle streams of values, and transform data in complex multi-threaded ways.

Flow (cold stream) – In general think of it as a stream of data flowing in a pipe with both ends having a producer and consumer running on a coroutine.


StateFlow:

StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors.

The current state value can also be read through its value property.

To update state and send it to the flow, assign a new value to the value property of the MutableStateFlow class. StateFlow only returns a value that has been updated. State Flow is similar in concept with Observer of RxJava. When collecting value from StateFlow, we always get the latest value as it always has a value that makes it read-safe because at any point in time the StateFlow will have a value. Infact, stateFlow requires an initial value.


StateFlow(hot stream) does similar things to LiveData but it is made using flow by kotlin guys and only difference compared to LiveData is it’s not lifecycle aware but this is also been solved using repeatOnLifecycle APIs, so whatever LiveData can do Stateflow can do much better with the power of flow’s api. Stateflow won’t emit the same value.


SharedFlow:

The shareIn function returns a SharedFlow, a hot flow that emits values to all consumers that collect from it. A SharedFlow is a highly-configurable generalization of StateFlow.

SharedFlow(hot stream) – the name itself says it is shared, this flow can be shared by multiple consumers, I mean if multiple collect calls happen on the sharedflow there will be a single flow that will get shared across all the consumers, unlike normal flow.

StateFlow and SharedFlow:

StateFlow and SharedFlow are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.


Fragment:
package com.hilt.basicapplication.ui.home

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import com.hilt.basicapplication.databinding.FragmentHomeBinding
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

class HomeFragment : Fragment() {

private var _binding: FragmentHomeBinding? = null

// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
)
: View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root

val homeViewModel: HomeViewModel by viewModels()

//.......................................................
// trigger live data
binding.liveDataButton.setOnClickListener {
homeViewModel.triggerLiveData()
}

// observe live data
homeViewModel.text.observe(viewLifecycleOwner) {
binding.liveDataButton.text = it
}


//.......................................................
// trigger state flow
binding.stateFlowButton.setOnClickListener {
homeViewModel.triggerStateFlow()
}
// observe state flow
lifecycleScope.launchWhenStarted {
homeViewModel.stateFlow.collectLatest {
binding.stateFlowButton.text = it
}
}

//.......................................................
// flow
binding.flowButton.setOnClickListener {
//use launch, bez it is not state flow
lifecycleScope.launch() {
homeViewModel.triggerFlow().collectLatest {
binding.flowButton.text = it
}
}
}

//.......................................................
// shared flow
binding.sharedFlowButton.setOnClickListener {
homeViewModel.triggerSharedFlow()

lifecycleScope.launchWhenStarted {
homeViewModel.sharedFlow.collectLatest {
Snackbar.make(binding.coordinator, it,Snackbar.LENGTH_SHORT)
.show()
}
}
}

return root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
View Model:
package com.hilt.basicapplication.ui.home

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

class HomeViewModel : ViewModel() {

//Creating an instance of LiveData,
private val _textLiveData = MutableLiveData("Hello World")
val text: LiveData<String> = _textLiveData

//Set the data in LiveData,
fun triggerLiveData(){
_textLiveData.value="Live Data"
}


private val _textStateFlow = MutableStateFlow("Hello World")
val stateFlow =_textStateFlow.asStateFlow()
fun triggerStateFlow(){
_textStateFlow.value="State flow"
}


fun triggerFlow() : Flow<String> {
return flow {
repeat(10){
emit("Item $it")
delay(1000L)
}
}
}


private val _textSharedFlow = MutableSharedFlow<String>()
val sharedFlow =_textSharedFlow.asSharedFlow()

fun triggerSharedFlow(){
viewModelScope.launch {
_textSharedFlow.emit("Shared flow")
}
}


}
..

Comments