DEV Community

RockAndNull
RockAndNull

Posted on • Originally published at rockandnull.com on

StateFlow and SharedFlow: the new hot stream APIs in town

StateFlow and SharedFlow: the new hot stream APIs in town

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()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
ViewModel
class MyActivity : AppCompatActivity() {
    private val viewModel = getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launchWhenStarted { // 2.
            viewModel.userName.collect { userName ->
                userNameLabel.text = userName
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
lolicon profile image
lolicon

flow cancels automatically when stopped state as long as you collect it in lifecycleScope