Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle one-shot operations in Jetpack Compose?

Note - This question is pretty much the same as this one.
I am looking for a better approach if there is any.

As per Android Docs,

Once the transient message has been shown, the UI needs to notify the ViewModel of that, causing another UI state update:

For example, when I show a Toast message on a button click, should the UI notify the ViewModel that the toast is shown successfully?
Is that the intended best approach to handle one-shot operations like toasts, snackbar, etc?

Sample code,

@Composable
fun OneShotOperation(
    viewmodel: OneShotOperationViewModel = viewModel(),
) {
    val context = LocalContext.current

    LaunchedEffect(
        key1 = viewmodel.toastMessage,
    ) {
        if (viewmodel.toastMessage.isNotBlank()) {
            Toast.makeText(
                context,
                viewmodel.toastMessage,
                Toast.LENGTH_SHORT,
            ).show()
            viewmodel.toastMessage = "" // Is this the correct way?
        }
    }

    Button(
        onClick = {
            viewmodel.toastMessage = "Sample Toast"
        },
    ) {
        Text(text = "Show Toast")
    }
}

class OneShotOperationViewModel : ViewModel() {
    var toastMessage by mutableStateOf(
        value = "",
    )
}

If I remove this line,
viewmodel.toastMessage = "" // Is this the correct way?
The toast is shown only once, the subsequent button press do not show a toast since the mutable state has not changed.

like image 927
Abhimanyu Avatar asked Dec 10 '25 02:12

Abhimanyu


1 Answers

I prefer using SharedFlow for a job like this.

Note that if you send a message from another view while collect is not running, it will not show toast when you finally start it. It does not store the value, it only passes it on to all the collectors that are currently connected.

class OneShotOperationViewModel : ViewModel() {
    private val _toastMessage = MutableSharedFlow<String>()
    val toastMessage = _toastMessage.asSharedFlow()

    fun sendMessage(message: String) {
        viewModelScope.launch {
            _toastMessage.emit(message)
        }
    }
}

@Composable
fun TestScreen() {
    val context = LocalContext.current

    val viewModel = viewModel<OneShotOperationViewModel>()
    LaunchedEffect(Unit) {
        viewModel
            .toastMessage
            .collect { message ->
                Toast.makeText(
                    context,
                    message,
                    Toast.LENGTH_SHORT,
                ).show()
            }
    }
    Button(
        onClick = {
            viewModel.sendMessage("Sample Toast")
        },
    ) {
        Text(text = "Show Toast")
    }
}
like image 136
Philip Dukhov Avatar answered Dec 12 '25 16:12

Philip Dukhov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!