Kotlin Flow, StateFlow and SharedFlow Android

amod kanthe
Level Up Coding
Published in
4 min readMay 7, 2024

--

Kotlin flow provides an efficient way to handle asynchronous data in the form of streams. A Flow basically emits data asynchronously in the form of streams these data streams then are consumed by A consumer.

A flow is a coroutine-based asynchronous stream that can emit multiple values over a period of time. A flow is part of coroutine so it supports concurrency and makes asynchronous programming easy.

Here is an example of a flow :

In the above example we are emitting a random integer 5 times. Now let’s collect this data inside an activity.

Notice that we have launched a coroutine in the above example to collect the data. As collect is a suspend function, it needs to be executed within a coroutine.

What is Cold flow and Hot flow ?

A “cold flow” is ideal when a flow requires a single collector. It remains inactive until a collector is present. Each collector operates with its own instance of the underlying cold flow. Once collected, it concludes its operation and ceases to exist.

On the contrary, a “hot flow” is less concerned about whether it’s currently being collected. It persists in memory until it’s garbage collected and emits events from potentially multiple coroutines. Interested parties can subscribe by invoking collect, and all collectors will receive the same event emitted from that hot flow, similar to an event bus.

What is StateFlow?

StateFlow is a hot flow so it keeps emitting values although there is no collector. StateFlow is an observable data collector which holds the last value.

Android StateFlow is an observable data holder class introduced as part of Kotlin Flows in the Jetpack library. It’s designed specifically for managing state in Android applications. StateFlow is similar to LiveData in that it allows you to observe changes to a value and automatically updates UI components when the value changes. However, it has additional features such as supporting flows and coroutine scopes.

StateFlow will require an initial value unlike LiveData.

Let’s look at following example

In above we have initialised a state flow with value 0 then we have an emit function which will emit value 20.

Here is the code to collect this value in an activity.

Difference between MutableStateFlow and MutableLiveData?

  1. Initialisation

StateFlow needs an initial state at the beginning, but LiveData doesn’t.

2. Automatic Stopping

When the view stops, LiveData automatically stops observing. But for StateFlow, you need to manually handle this by collecting it within a specific block called Lifecycle.repeatOnLifecycle.

What is SharedFlow?

A SharedFlow operates as a hot stream that permits multiple collectors at once. Unlike cold flows, it emits values independently of collectors presence, making it ideal for broadcasting data to multiple recipients or enabling numerous subscribers to access the same data stream. Notably, SharedFlow lacks an initial value, but it offers a replay cache feature. This feature allows customisation by storing a set number of previously emitted values, ensuring that new collectors can access a predefined history of data for seamless integration with the flow.

In simple words SharedFlow does not require an initial value and used to send a one time event.

For Example:

What is CallbackFlow?

Callback flow is cold flow, which can be used as an existing callback to a flow.

To emit the data we have to use trySend() and to unregister the callback we have to use awaitClose().

Let’s consider the following example

Notice we are sending a value true or false on connectivity change using trySend() and we are unregistering callback inside awaitClose block.

In callbackFlow, awaitClose plays a critical role because, without it, the coroutine would terminate immediately, failing to wait for any asynchronous task to complete. It’s akin to abruptly ending a phone call without allowing the other person to respond. By utilizing awaitClose, the coroutine remains active until the channel, through which data flows, is closed. Upon closure of the channel, any specified action within awaitClose is executed, ensuring proper completion or termination of the asynchronous operation.

We can collect this as shown below

Handling exceptions:

The catch operator in Kotlin Flow serves to manage exceptions across the entire Flow. Its purpose is to streamline error handling by consolidating the logic for handling errors into a single location.

Alternatively you can also use try catch block

Flow operators:

Kotlin Flow operators are like tools for shaping and managing data streams within a Flow. They’re functions or add-ons that empower you to do things such as sifting through, rearranging, unifying, and managing errors in your data flow.

Following are the operators supported by flow.

  1. map: Changes each item in the Flow to something new according to a rule.
  2. filter: Picks out only the items from the Flow that meet a certain condition.
  3. transform: Changes the items in the Flow and can even make more items from one.
  4. flatMapConcat: Takes each item and turns it into its own Flow, then puts them together one after the other.
  5. catch: Handles problems that happen in the Flow, like finding an alternative if there's an issue.
  6. onEach: Does something with every item in the Flow but doesn't change the items themselves.
  7. collect: Gets all the items in the Flow together for a final action, like adding them up or showing them.
  8. zip: Puts together items from different Flows into pairs or groups.
  9. merge: Combines items from different Flows into one big Flow, no matter when they come in.

Conclusion:

In conclusion Kotlin flows makes it easy to manage asynchronous tasks. Their integration with coroutines simplifies handling background operations and updating the UI. Kotlin Flow helps to write reactive, scalable, and maintainable code.

--

--