I'm trying to use the snackbar on Android Compose. However, as I'm trying to display 2 messages in a row on the snackbar, I can only see the first message, does anybody know what can cause this?
I'm putting the code involved below:
Component code related to Snackbar:
/*
The state is hoisted. It contains the snackbar message that is not null if
there is something to display
*/
state.snackbarMessage?.let { resourceId ->
val message = stringResource(resourceId)
viewModel.handleEvent(
AuthenticationEvent.SnackbarMessage(
message = message,
type = when {
state.isFailure -> SnackbarType.ERROR
else -> SnackbarType.VALIDATION
}
)
)
}
/* Custom Component using Scaffold to host snackbar events */
Frame(
modifier = modifier,
snackbarState = viewModel.snackbarState,
onShowSnackbar = { viewModel.onSnackbarDisplayed() }
) { innerPadding ->
// UI Content
}
Frame Component:
@Composable
fun Frame(
modifier: Modifier = Modifier,
header: @Composable () -> Unit = {},
snackbarState: StateFlow<MajorSnackbarData?>? = null,
onShowSnackbar: (() -> Unit)? = null,
content: @Composable (PaddingValues) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
val showSnackbar: (MajorSnackbarData) -> Unit = {
coroutineScope.launch {
onShowSnackbar?.invoke()
scaffoldState.snackbarHostState.showSnackbar(
message = it.message,
actionLabel = it.type.name,
)
}
}
snackbarState?.collectAsState()?.value.also { data ->
if (data != null) {
showSnackbar(data)
}
}
Scaffold(
modifier = modifier,
topBar = header,
scaffoldState = scaffoldState,
snackbarHost = {
SnackbarHost(it) { data ->
MajorSnackbar(data)
}
},
content = content,
)
}
ViewModel base that hosts Snackbar:
open class BaseViewModel : ViewModel() {
private val _snackbarState = MutableStateFlow<MajorSnackbarData?>(null)
val snackbarState: StateFlow<MajorSnackbarData?> = _snackbarState
protected fun showSnackBar(
message: String,
type: SnackbarType = SnackbarType.ERROR
) {
_snackbarState.value = MajorSnackbarData(message, type)
}
open fun onSnackbarDisplayed() {
_snackbarState.value = null
}
}
Event handling in a viewmodel:
fun handleEvent(event: Event) {
when (event) {
// ...
is Event.SnackbarMessage -> displayError(event)
}
}
private fun displayError(event: Event.SnackbarMessage) {
showSnackBar(event.message, event.type)
}
There is a very simple way of handling snackbars that I always use in my projects. You can directly create a SnackbarHostState
in the ViewModel itself and show snackbars directly from the ViewModel.
// ViewModel
val snackbarHostState = SnackbarHostState()
fun doSomething() {
...
// If you need to show a snackbar here, directly call the showSnackbar function
snackbarHostState.showSnackbar(
message = // Your message,
actionLabel = // Your action label,
)
}
// In your composable
val scaffoldState = rememberScaffoldState(snackbarHostState = viewModel.snackbarHostState)
Scaffold(
modifier = modifier,
topBar = header,
scaffoldState = scaffoldState,
snackbarHost = {
SnackbarHost(it) { data ->
MajorSnackbar(data)
}
},
content = content,
)
This will give you a lot more control over the snackbars. If you need to display two snackbars one after the other, just place the two calls together in the viewModel.
snackbarHostState.showSnackbar(
message = message1,
actionLabel = actionLabel1,
)
snackbarHostState.showSnackbar(
message = message2,
actionLabel = actionLabel2,
)
Note that because showSnackbar
is a suspend function which suspends till the snackbar is visible, the second snackbar will appear only after the first one has gone.