Show Snackbar in Material Design 3 using Scaffold
Asked Answered
K

3

19

Decided to try out Material Design 3 in my new Jetpack Compose project. Everything was cozy until I needed to show a Snackbar when I hit a wall.

In MD2 this was super-easy and you would show the snackbar in a Scaffold done with the SnackbarHostState.showSnackbar() function inside a Coroutine scope. I observed you only needed to import androidx.compose.material.rememberScaffoldState from Material Library.

import androidx.compose.material.rememberScaffoldState


@Composable
fun MyScreenInMaterial2() {
    val scaffoldState = rememberScaffoldState()
}

When I try the same in MD3 the rememberScaffoldState() function is not being resolved. enter image description here

For those who have dived in MD3 world how do you show a Snackbar in a Scaffold? I have checked the docs and online resources but I haven't come across a solution.

Ken answered 10/7, 2022 at 5:38 Comment(2)
#71364042Proglottis
Thx @MartinZeitler, looks like we will have to wait for MD Team to do something. I will leave the question here in case someone else comes across the same issue ...Ken
M
30

Here you have an example from the official documentation.

val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
    snackbarHost = { SnackbarHost(snackbarHostState) },
    floatingActionButton = {
        var clickCount by remember { mutableStateOf(0) }
        ExtendedFloatingActionButton(
            onClick = {
                // show snackbar as a suspend function
                scope.launch {
                    snackbarHostState.showSnackbar(
                        "Snackbar # ${++clickCount}"
                    )
                }
            }
        ) { Text("Show snackbar") }
    },
    content = { innerPadding ->
        Text(
            text = "Body content",
            modifier = Modifier.padding(innerPadding).fillMaxSize().wrapContentSize()
        )
    }
)
Melpomene answered 16/7, 2022 at 17:11 Comment(3)
Gosh, how did I miss this. MDC3 eliminates scaffoldState parameter which you could use on MD2 like this: scaffoldState.snackbarHostState.showSnackbar(). You now need remember { SnackbarHostState() } which you use like this snackbarHostState.showSnackbar(). So no more need for ScaffoldState on MD3!! Thx @DamianKen
Avoid using a CoroutineScope here because you'll get a warning: CoroutineCreationDuringComposition. I've added an answer based on this one. TLDR: Replace CoroutineScope with LaunchedEffect.Augmenter
So you just ignore the it: SnackbarHostState passed to snackbarHost's lambda? I wouldn't call this an intuitive solution :-\ Why do they give us this parameter when they suggest ignorig it?Discord
A
8

Bsed on Damian Petla's answer.

If you use a CoroutineScope then you'll get a warning saying CoroutineCreationDuringComposition.

Official documentation suggests using LaunchedEffect instead to avoid this side-effect.

So the suggested code would look like this:

val snackbarHostState = remember { SnackbarHostState() }

Scaffold(
    snackbarHost = { SnackbarHost(snackbarHostState) },
    floatingActionButton = {
        var clickCount by remember { mutableStateOf(0) }
        ExtendedFloatingActionButton(
            onClick = {
                LaunchedEffect(snackbarHostState) {
                    snackbarHostState.showSnackbar(
                        "Snackbar # ${++clickCount}"
                    )
                }
            }
        ) { Text("Show snackbar") }
    },
    content = { innerPadding ->
        Text(
            text = "Body content",
            modifier = Modifier.padding(innerPadding).fillMaxSize().wrapContentSize()
        )
    }
)
Augmenter answered 28/2, 2023 at 16:10 Comment(3)
This is right n Docs support it, thx for the insightsKen
what is content? I have gone through docs and they don't explain what it is and why it is mandatoryJanina
@MuhammadSarimMehdi you have a Scaffold that is like the foundation of your view. And you want to see something in it, right? Well, that something is the content. In the Scaffold's documentation the content is explained.Augmenter
B
4

In my case, there's compile error with:

@Composable invocations can only happen from the context of a @Composable function

When calling the LaunchedEffect inside the onClickChip lambda.

There's an alternative options, inspired from this answer

For those wants to show the Compose Snackbar inside a lambda (w/o @Composable)

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun ScreenA() {
    val snackbarHostState = remember { SnackbarHostState() }
    val snackbarMessage = "Succeed!"
    val showSnackbar = remember { mutableStateOf(false) }

    LaunchedEffect(showSnackbar.value) {
        if (showSnackbar.value)
            snackbarHostState.showSnackbar(snackbarMessage)
    }
    Surface {
        Scaffold(
            snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
        ) {
            ChipIcon(
                text = "Click me: ${showSnackbar.value}",
                onClickChip = {
                    // unable to use LaunchedEffect in here
                    showSnackbar.value = !showSnackbar.value
                }
            )
        }
    }
}

// Custom Composable
@Composable
fun ChipIcon(
    text: String,
    onClickChip: () -> Unit, // no @Composable, unable to use LaunchedEffect in this lambda
) {
    Row(
        modifier = Modifier
            .clickable(
                enabled = true,
                onClick = {
                    onClickChip()
                }
            ),
    ) {
        Text(text = text)
    }
}

Observe the showSnackbar.value changes instead on LaunchedEffect for cancle/re-launch, then show snackbar if the showSnackbar.value == true, otherwise do nothing

Bala answered 15/3, 2023 at 4:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.