16

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.

remarcoble
  • 537
  • 1
  • 4
  • 19
Tonnie
  • 4,865
  • 3
  • 34
  • 50
  • 1
    https://stackoverflow.com/questions/71363542/not-able-to-use-rememberscaffoldstate-in-android-compose-material3 – Martin Zeitler Jul 10 '22 at 05:40
  • 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 ... – Tonnie Jul 10 '22 at 06:08

3 Answers3

22

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()
        )
    }
)
Damian Petla
  • 8,963
  • 5
  • 44
  • 47
  • 4
    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 @Damian – Tonnie Jul 17 '22 at 07:15
  • 1
    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`. – Joaquin Iurchuk Feb 28 '23 at 16:05
5

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()
        )
    }
)
Joaquin Iurchuk
  • 5,499
  • 2
  • 48
  • 64
3

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

mochadwi
  • 1,190
  • 9
  • 32
  • 87