Reputation: 8112
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.
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.
Upvotes: 19
Views: 12088
Reputation: 1268
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 onLaunchedEffect
for cancle/re-launch, then show snackbar if theshowSnackbar.value == true
, otherwise do nothing
Upvotes: 4
Reputation: 5617
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()
)
}
)
Upvotes: 8
Reputation: 9103
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()
)
}
)
Upvotes: 31