Issue
Note - This question is pretty much the same as this one.
I am looking for a better approach if there is any.
As per href="https://developer.android.com/jetpack/guide/ui-layer/events#consuming-trigger-updates" rel="nofollow noreferrer">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.
Solution
I prefer using SharedFlow
for a job like this:
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")
}
}
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.
Answered By - Pylyp Dukhov
Answer Checked By - Pedro (JavaFixing Volunteer)