StateFlow vs SharedFlow in Compose
March 1, 2022
If you write primarily Android code in Kotlin, it’s time to adopt StateFlow
for tasks you used LiveData
for.
So in your ViewModel
, your LiveData
code:
private val _myLiveData = MutableLiveData<String>() val myLiveData = _myLiveData
can be replaced with:
private val _myStateFlow = MutableStateFlow("") val myStateFlow = _myStateFlow.asStateFlow()
This post will describe when it makes to use Kotlin’s StateFlow
vs SharedFlow
.
StateFlow
StateFlow
is a state-holder observable flow that emits the current and new state updates to its collectors. As the name suggest, StateFlow
is suitable for showing current state and preserving it during configuration changes such as screen rotations.
So in a Compose app, if your ViewModel
contains:
class MainViewModel : ViewModel() { private val messageList = listOf( "apple", "tomato", "banana" ) private val _stateFlowMessage = MutableStateFlow("") val stateFlowMessage = _stateFlowMessage.asStateFlow() fun onButtonClicked() { viewModelScope.launch { val message = messageList.random() _stateFlowMessage.emit(message) } } }
You can observe your StateFlow
in a Composable like so:
class MainActivity : ComponentActivity() { private val viewModel by viewModels<MainViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeFlowsTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { MainScreen(viewModel) } } } } } @Composable fun MainScreen(viewModel: MainViewModel) { val message by viewModel.stateFlowMessage.collectAsState() Text( text = message ) }
Flow<T>.collectAsState()
establishes a subscription to a Flow
maintained by our ViewModel. The ViewModel survives configuration changes and therefore will preserve the contents of the StateFlow
, a behavior similar to LiveData
.
SharedFlow
SharedFlow
is a hot flow that emits values to all consumers that collect from it. SharedFlow
can be used for one-time events. It can achieve similar effect as a custom SingleLiveEvent
(which was never considered to be a good practice—yet exists in many code bases out there).
Your ViewModel
may contain the following code:
class MainViewModel : ViewModel() { private val messageList = listOf( "apple", "tomato", "banana" ) private val _sharedFlowMessage = MutableSharedFlow<String>() val sharedFlowMessage = _sharedFlowMessage.asSharedFlow() fun onButtonClicked() { viewModelScope.launch { val message = messageList.random() _sharedFlowMessage.emit(message) } } }
Which can be accessed from your Composable like so:
class MainActivity : ComponentActivity() { private val viewModel by viewModels<MainViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeFlowsTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { MainScreen(viewModel) } } } } } @Composable fun MainScreen(viewModel: MainViewModel) { val context = LocalContext.current LaunchedEffect(true) { viewModel.sharedFlowMessage.collect { toastMessage -> Toast.makeText(context, "Random fruit: $toastMessage", Toast.LENGTH_SHORT).show() } } }
Note that we cannot use collectAsState()
in our Composable since this is a one time event that does not get assigned an initial value and therefore won’t even compile.
We have to collect
the SharedFlow
instead. Note that if we don’t use LaunchedEffect
, the Flow will be collect
ed every time this Composable is recomposed. Therefore, we use LaunchedEffect(true)
and pass true
as key
to ensure that side effect gets called only once when the Composable gets composed initially. At that point we start observing the SharedFlow
and will react to it when it changes by showing a Toast.
We only start observing the StateFlow
when the Composable leaves the composition.
Visualizing it
Here is a gif comparing both StateFlow
and SharedFlow
.
Data rendered by the StateFlow
(Text Composable) gets preserved after rotation. On the other hand, when using SharedFlow
, the Toast does not get shown again after screen rotation.
And as always, code for this blog post can be found on GitHub. https://github.com/jshvarts/ComposeFlows