Flow, SharedFlow, StateFlow and LiveData - A complete Guide

Flow, SharedFlow, StateFlow and LiveData - A complete Guide

Previously, we used to create one request and then wait for our whole request to get completed. And after all this wait, we get our data to do any calculation.

Example: You order 3 burgers and wait for all of them to get ready. This will be not a good user experience and... you are not going to eat all 3 burgers at once, right? So, what If you are getting served everytime when even one of your burger is ready. Cool, now you can eat them without any delays.

What If I tell you we can also do this with our data requests? We can actually get the continous streams of data, and can work on them when they are being served or when the "data source" is still working to finish the request.

Flow:

In simple words: What is a flow? A flow of water? Yes!! Consider flow as an object that can provide you with a running stream of data. For example, you have a pipe where you send the data from one end and keep collecting it from another end.

Using flows, we can achieve the same thing for our data. Example: You want to continuously fetch live video data from the web, so using flows you can do that. From one end(from server), we can emit the data and from our end, we can just collect the data that was emitted to us via a flow object.

In official words: In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value.

Flows are built on top of coroutines and can provide multiple values. A flow is conceptually a stream of data that can be computed asynchronously.

Example: Suppose, we want to fetch a list from a request and generating one values takes one second. So, using flows we can get each value after it is ready to be emitted(i.e. 1 value/sec) and we don't have to wait for all 5 values to get returned to us as a result.

Code:

fun integerListFlow(): Flow<Int> = flow {//coroutine internally
    for (i in 1..5) {
        delay(1000) // delay of 1 second
        emit(i)
    }
}

fun main() = runBlocking {// scope to collect the stream of data
    integerListFlow().collect { // Use this collect function to get stream
        println(it) // Output: 1, 2, 3, 4, 5 after 1 second delay between each emission
    }
}
  • Create a flow using flow builder - flow{...}.

  • Return the type of Flow with the generic type.

  • Emits value inside a coroutine scope(handled internally by flow builder).

  • Collect values only inside a coroutine scope(runBlocking in our example).

  • Flow being a cold flow, emits fresh "list of numbers' of all its subscribers.

In Kotlin, Flows can be either cold or hot depending on their behavior.

A cold Flow is a Flow that starts emitting values only when it is collected, and it can be collected multiple times, each time starting a fresh sequence of values. A real-life example of a cold Flow is a Stream of data that reads from a file. Each time the Stream is collected, the file is read again, producing a fresh sequence of values.

A hot Flow, on the other hand, starts emitting values as soon as it is created, and it can be collected multiple times, but each collection receives the same sequence of values that are emitted. A real-life example of a hot Flow is a live Stream of data, such as stock prices, that continuously emits new values and multiple collectors can receive the same updated values.

SharedFlow:

In simple words: It is a type of flow that is shared between it's collectors. That's why the values that it emits will be same for all collectors and here data will be also lost if any collector is delayed. Example: Movie will start at 2pm for everyone, and if you are late then you'll never know how Tony reached the earth.

In official words: A hotFlow that shares emitted values among all its collectors in a broadcast fashion, so that all collectors get all emitted values. A shared flow is called hot because its active instance exists independently of the presence of collectors.

We can create MutableSharedFlow object using MutableSharedFlow and SharedFlow.

// Function to create a MutableSharedFlow of integers
fun createMutableSharedFlow(): Flow<Int> {
  // List of integers
  val numbers = listOf(1, 2, 3, 4, 5)
  // MutableSharedFlow with replay = 1
  val sharedFlow = MutableSharedFlow<Int>(replay = 1)
  // Launch coroutine to emit integers from the list
  GlobalScope.launch {
    for (number in numbers) {
      // Emit each integer from the list
      sharedFlow.emit(number)
      // Wait for 1 second before emitting the next integer
      delay(1000)
    }
  }
  // Return the MutableSharedFlow
  return sharedFlow
}
// Main function
fun main() = runBlocking {
  // Get the MutableSharedFlow
  val flow = createMutableSharedFlow()
  // Collect the emitted values from the flow
  flow.collect { value ->
    // Print each collected value
    println("Collected value: $value")
  }
}
  • Here it works almost the same as normal Flow, but it is hot.

  • Use replay param while create SharedFLow to store no. of buffered values. Eg. You can rewind the movie for 1 minute if you are joining late.

  • All, collectors will get the same values once we started emitting.

  • We will not store any state.

StateFlow:

In simple words: This type of flow stores the last state(most recent value) and emits it to all it's collectors. It is also hot. Consider it as a box where we will store one value(can be also a collection), and we will let our collectors view the value in box. Everytime we have a new value, we will update it in the box, and any collector will see the updated value till the end.

In official words: A SharedFlow that represents a read-only state with a single updatable data value that emits updates to the value to its collectors. A state flow is a hot flow because its active instance exists independently of the presence of collectors. Its current value can be retrieved via the value property.

Note: If we are not emitting any values(or if we are done emitting) in case of SharedFlow, then observers will not see any ouptut but in case of StateFlow, we will maintain the recent state, so if we are not "emitting" the value, then still our observers can see the value which was most recent in the State.

fun updateStateFlow(): StateFlow<Int> {
  val stateFlow = MutableStateFlow(0) //initial value
  GlobalScope.launch {
    // Update stateFlow with new value after 1 second delay
    delay(1000)
    stateFlow.value = 1

    // Update stateFlow with new value after 1 second delay
    delay(1000)
    stateFlow.value = 2

    // Update stateFlow with new value after 1 second delay
    delay(1000)
    stateFlow.value = 3
  }
  return stateFlow
}

fun main() = runBlocking {
  val flow = updateStateFlow()

  // First collector starts after 1 second delay
  launch {
    delay(1000)
    flow.collect { value ->
      println("First collector: $value")
    }
  }

  // Second collector starts after 2 second delay
  launch {
    delay(2000)
    flow.collect { value ->
      println("Second collector: $value")
    }
  }

  // Third collector starts after 3 second delay
  launch {
    delay(3000)
    flow.collect { value ->
      println("Third collector: $value")
    }
  }
  // Fourth collector starts after 4 second delay
  launch {
    delay(4000)
    flow.collect { value ->
      println("Fourth collector: $value")
    }
  }
}

Output:

First collector: 1
Second collector: 2
Third collector: 3
Fourth collector: 3

Even after this if we have any collector, then it will get 3 as output, but you can try by yourself that in the case of SharedFlow instead of StateFlow, you will not store any state and hence no output after completing emission.

LiveData:

In simple words: It is not a flow, but just a data holder that can store some value. And when it changes it's data it will just send a alert to its observers. Example: Suppose you are observing someone and doing the task that they are guiding you for.

In official words: LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state

Code:

//Generally inside ViewModel
val currentName: MutableLiveData<String> by lazy {
    MutableLiveData<String>()
}

//Add Observer in your UI
val nameObserver = Observer<String> { newName ->
    // Update the UI, in this case, a TextView.
    nameTextView.text = newName
}
model.currentName.observe(this, nameObserver)

It also stores the state and we can also observe it the exact same way we collect our StateFlow. Then, how and why they are different from each other?

StateFlow vs LiveData:

  • LiveData is lifecycle aware. That means it will not easy for us to use anywhere in our project. On other hand, StateFlow comes with a lot of extra functionality and we can also use it anywhere.

  • Performance is impacted in LiveData, not in all cases but still "sometimes" makes the difference. How? In LiveData, if we want to map, filter, modify our output then we will do that on our main thread. But in StateFlow, we can specify our thread by using flowOn().

  • Flows provide us with more functionality and operators.

At this point, you have a good understanding of Kotlin Flow, SharedFlow, StateFlow, LiveData. Now, let me briefly tell you the cases for where to use which one:

  • If you want to execute independent/new stream of data for every collector and also only emit when there is a collector then use Flow.

  • If you want to share your data stream and emit it anytime irrespective of any collector collecting or not, then use SharedFlow.

  • If you want to save the last state of our data stream and want to use it anywhere(not only for lifecycle aware components) then use StateFlow.

  • In other cases, Use LiveData. But, nowadays StateFlow is recommended to use so check this out.

I hope this article clears all your doubts regarding these important android concepts. If yes, then do FOLLOW me for more Android-related content.

#androidWithSagar #android #androiddevelopment #androiddev