0

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)
}
2
  • What is state.snackbarMessage? What updates its value? ViewModel? Commented Dec 30, 2022 at 16:21
  • It is a resourceId for the string resource to display, it is updated from the viewmodel via the state
    – A.Danibo
    Commented Dec 31, 2022 at 17:24

1 Answer 1

1

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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.