Reputation: 53
I am using Channel and Flow to send one-off events from ViewModel to Fragment and Activity to make UI changes, such as showing dialogs. This doesn't seem working (or I may missed something in the logic) when creating UI with composables.
In my app, when the viewmodel performs some tasks, I need to send an one-off event to UI to show a dialog, such as the AlertDialog composable. The dialog is dismissed by setting a boolean with MutableState as in the official tutorial. But, when I send the event again, the MutableState value stays false and the dialog cannot be shown again.
Could anyone tell me the right way of sending one-off event and updating UI in Android Compose, please.
Upvotes: 5
Views: 3755
Reputation: 29320
The recommendation for this is to use Events as State.
In your state, you would define something like this
data class MyState(
val showDialog: Boolean
)
then when you want to show the dialog, you update your state to have the showDialog
flag set to true
.
In the UI you would observe this flag and, when it toggles to true
, you would show the dialog. When the user dismisses the dialog, you call a method in the viewmodel that toggles the flag back to false
, like shown here, so the dialog goes away
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow<MyState>(MyState(showDialog = false))
val state: StateFlow<MyState>
get() = _state.asStateFlow()
// call this when you want to show the dialog
fun onShowDialog() {
_state.update { state ->
state.copy(showDialog = true)
}
}
fun onDismiss() {
_state.update { state ->
state.copy(showDialog = false)
}
}
}
data class MyState(
val showDialog: Boolean
)
@Composable
fun MyScreen(
viewModel: MyViewModel = hiltViewModel()
) {
val state = viewModel.state.collectAsStateWithLifecycle()
MyScreen(
state = state,
onDismiss = viewModel::onDismiss,
)
}
@Composable
fun MyScreen(
state: MyState
onDismiss: () -> Unit,
) {
// other stuff
if (state.showDialog) {
AlertDialog(
onDismissRequest = onDismiss,
buttons = { /*TODO*/ },
title = { /*TODO*/ },
text = { /*TODO*/ },
)
}
}
Upvotes: 8