Riekey
Riekey

Reputation: 53

What's the right way to send one-off event from viewmodel to composable to show dialogs

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

Answers (1)

Francesc
Francesc

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

Related Questions