Freelancing for Pale Blue

Looking for flexible work opportunities that fit your schedule?


StateFlow and SharedFlow: the new hot stream APIs in town

Android Feb 7, 2021

There's been quite a hype around the (kind of) newly introduced StateFlow and SharedFlow in Kotlin/Android community.

These are the new Kotlin Flow APIs. But these are not for "cold" streams (i.e. data are generated and emitted when there's a subscriber). These are for "hot" streams (i.e. data are emitted anyway and any active subscribers will receive them).

If this reminds you of LiveData it's because this is another "hot" stream implementation. The data are emitted anyway from the ViewModel, and any subscriber in the View will get those values.

So should we replace LiveData with StateFlow? Is it that simple? And does it worth it?

StateFlow

The API for StateFlow is almost identical to LiveData. You have a MutableStateFlow that "drives" the StateFlow (similar to how a MutableLiveData "drives" a LiveData).

class MyViewModel(private val repository: Repository) : ViewModel() {

    private val userNameFlow = MutableStateFlow("") // 1.
    
    val userName: StateFlow<String> = userNameFlow

    init {
        viewModelScope.launch {
            userNameFlow.value = repository.fetchUserName()
        }
    }
}
ViewModel
class MyActivity : AppCompatActivity() {
    private val viewModel = getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launchWhenStarted { // 2.
            viewModel.userName.collect { userName ->
                userNameLabel.text = userName
            }
        }
    }
}
View
  1. In contrast with LiveData, StateFlow always needs an initial value.
  2. This runs when the lifecycle is at least in the STARTED state.

Note that (2) might be an issue. LiveData automatically unregisters the consumer when the view goes to the STOPPED state. When collecting a StateFlow this is not handled automatically.

To address this issue you can either start and stop collecting values from StateFlow manually, or just convert your Flow to a LiveData for getting the best of both worlds (this extension method is included in the lifecycle-livedata-ktx library).

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        viewModel.userName.asLiveData().observe(viewLifecycleOwner) { 
            userName -> userNameLabel.text = userName
        }
    }
}

SharedFlow

You can think of SharedFlow as a generalization of StateFlow.

  • StateFlow by default emits the last known value when there's a new subscriber. With SharedFlow, you can configure how many previous values to be emitted.
  • You can define what happens when the buffer of values is full (e.g. drop values, suspend caller, etc).
  • It provides a subscriptionCount that indicates how many active collectors exist to define business logic accordingly.
  • It provides a resetReplayCache() for not emitting the last known values if there are new subscribers.

This offers great flexibility for more advanced use cases that LiveData cannot fulfil easily.

Is it worth it?

The idea is that we can get rid of  Jetpack's LiveData and use the platform-independent StateFlow/SharedFlow in all the layers of an app (view, model, storage). LiveData being so Android Lifecycle-depended is no good fit for using it outside of Views/ViewModels.

StateFlow API is almost identical to LiveData and the SharedFlow offers flexibility for more advanced use cases. Since you can get all the advantages of LiveData (which is the automatic subscribing/unsubscribing when the app goes to the background) I think that these new APIs are the way to go if you are building a new app.

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.