17

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.

1
  • If you remove viewmodel.toastMessage = "" you will not be able to show same message again. And instead of modifying viewmodel's properties, it is supposed to call the appropriate methods, you need something like fun toastShown() {toastMessage = ""} in your viewmodel. Commented Feb 8, 2022 at 13:43

2 Answers 2

52

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") } } 
Sign up to request clarification or add additional context in comments.

5 Comments

@Patrick you'll show the toast twice if you collect it twice. Make sure LaunchedEffect only run in one place.
hey @Phil I have a question here, would be a better way to collectAsState before the launched effect and as a key for the LaunchedEffect pass the toast message ? then inside , when its executed you can remove the message in order to not display it again
@GastónSaillén I think this way is cleaner - collectAsState is gonna trigger recomposition, both when new message is received, and when it's removed. On the other hand my method triggers no recompositions at all. You have to pass callback to remove old event, which is extra lines of code, and it's harder to manage case when multiple messages sent around the same time. I don't see any benefits in collectAsState, but it's up to you.
how can i use to show a custom composable view rather than just showing toast
@Ammar you'd set a value to some state variable that would then trigger recomposition, probably, in which you'd see this value and show what you want
0

Instead of using

viewmodel.toastMessage = "" // Is this the correct way? 

you can wrap your message into a simple wrapper:

class ToastMessageWrapper(val value: String) 

and use this class in mutableStateOf():

val toastMessage: ToastMessageWrapper by mutableStateOf(null) 

Then LaunchedEffect will get updated on every new value. Note that I'm using class, not data class. Because we have to keep these classes different each time even for same values.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.