ViewModel:
class RulesViewModel : ViewModel() {
private val _sharedFlow = MutableSharedFlow<ScreenEvents>()
val sharedFlow = _sharedFlow.asSharedFlow()
sealed class ScreenEvents {
data class ShowSnackbar(val message: String) : ScreenEvents()
data class Navigate(val route: String) : ScreenEvents()
}
}
Composable:
@Composable
fun EventListener(
rulesVm: RulesViewModel,
) {
LaunchedEffect(key1 = true) {
rulesVm.sharedFlow.collect { event ->
when(event) {
is RulesViewModel.ScreenEvents.ShowSnackbar -> {
SnackbarScreen("snackbar ${event.message}")
}
is RulesViewModel.ScreenEvents.Navigate -> {
// todo
}
}
}
}
}
This gives an error message: @Composable invocations can only happen from the context of a @Composable function
What is the best practice for collecting flows then from viewModels and actioning them in composables?
It's better to use extension - Flow.collectAsState()
In Your case it will be:
val screenState = rulesVm.sharedFlow.collectAsState
Then in the body of a composable function You go:
@Composable
fun EventListener(
rulesVm: RulesViewModel,
) {
val screenState = rulesVm.sharedFlow.collectAsState()
when(screenState) {
is RulesViewModel.ScreenEvents.ShowSnackbar -> {
SnackbarScreen("snackbar ${event.message}")
}
is RulesViewModel.ScreenEvents.Navigate -> {
// todo
}
}
}
You can't invocate composable function inside LaunchedEffect body and inside Flow.collect() because it's an extension on coroutine scope, not a composable function.
I think what you would like to see is the following behavior: you dispatch an event and the snackbar appears on the current screen without going to the another screen. If you do not need to take into account the application lifecycle when receiving events, then the example below will suit you. I create a scaffoldState, which I use to display the Snaskbar. The subscription occurs once due to the fact that Unit is an object.
val scaffoldState = rememberScaffoldState()
LaunchedEffect(Unit) {
rulesVm.sharedFlow.collect { event ->
when(event) {
is RulesViewModel.ScreenEvents.Navigate -> TODO("Add navigation to another screen")
is RulesViewModel.ScreenEvents.ShowSnackbar ->
scaffoldState.snackbarHostState.showSnackbar(
message = "snackbar ${event.message}"
)
}
}
}
Scaffold(
scaffoldState = scaffoldState,
snackbarHost = { snackbarHostState ->
SnackbarHost(snackbarHostState) { data ->
Snackbar(
snackbarData = data
)
}
}
) {
TODO("Add your screen content")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With