I want to show snackbar with a button onclick in Jetpack Compose
I tried this
Button(onClick = {
Snackbar(action = {}) {
Text("hello")
}
}
But AS said "@Composable invocations can only happen from the context of a @Composable function"
Shall you give me a nice program.
I wish it can run in Button.onclick()
You'll need two things:
SnackbarHostState
- which will manage the state of your Snackbar
( you would usually get that from the ScaffoldState
) CoroutineScope
- which will be responsible for showing
your Snackbar
@Composable
fun SnackbarDemo() {
val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
val coroutineScope = rememberCoroutineScope()
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
) {
Button(
onClick = {
coroutineScope.launch { // using the `coroutineScope` to `launch` showing the snackbar
// taking the `snackbarHostState` from the attached `scaffoldState`
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something."
)
when (snackbarResult) {
Dismissed -> Log.d("SnackbarDemo", "Dismissed")
ActionPerformed -> Log.d("SnackbarDemo", "Snackbar's button clicked")
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
}
Building on Bartek's answer you can also use Compose's side-effects . Then you don't have to manage the CoroutineScope itself.
Since Composables
itself should be side-effect free , it is recommended to make use of Compose's Effect APIs
so that those side effects are executed in a predictable manner.
In your specific case you can use the LaunchedEffect API
to run suspend functions in the scope of a composable. The example code would look like the following:
@Composable
fun SnackbarDemo() {
val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
val (showSnackBar, setShowSnackBar) = remember {
mutableStateOf(false)
}
if (showSnackBar) {
LaunchedEffect(scaffoldState.snackbarHostState) {
// Show snackbar using a coroutine, when the coroutine is cancelled the
// snackbar will automatically dismiss. This coroutine will cancel whenever
// `showSnackBar` is false, and only start when `showSnackBar` is true
// (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "Error message",
actionLabel = "Retry message"
)
when (result) {
SnackbarResult.Dismissed -> {
setShowSnackBar(false)
}
SnackbarResult.ActionPerformed -> {
setShowSnackBar(false)
// perform action here
}
}
}
}
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
) {
Button(
onClick = {
setShowSnackBar(true)
}
) {
Text(text = "A button that shows a Snackbar")
}
}
}
If you use new Material3 there is new field in Scaffold: "snackbarHost"
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
...
// for showing snackbar in onClick for example:
scope.launch {
snackbarHostState.showSnackbar(
"Snackbar Test"
)
}
Just in case you need a snackbar as a separate function to call in other screens with custom parameters, and without using scaffold, I use this below.
The parameter openSnackbar: (Boolean) -> Unit
is used to reset the opening condition var openMySnackbar by remember { mutableStateOf(false) }
and allow open snackbar other times.
@Composable
fun SnackbarWithoutScaffold(
message: String,
showSb: Boolean,
openSnackbar: (Boolean) -> Unit
) {
val snackState = remember { SnackbarHostState() }
val snackScope = rememberCoroutineScope()
SnackbarHost(
modifier = Modifier,
hostState = snackState
){
Snackbar(
snackbarData = it,
backgroundColor = Color.White,
contentColor = Color.Blue
)
}
if (showSb){
LaunchedEffect(Unit) {
snackScope.launch { snackState.showSnackbar(message) }
openSnackbar(false)
}
}
}
And used like this:
@Composable fun MyScreen() {
var openMySnackbar by remember { mutableStateOf(false) }
var snackBarMessage by remember { mutableStateOf("") }
Column() {
Button(
shape = RoundedCornerShape(50.dp),
onClick = {
openMySnackbar = true
snackBarMessage = "Your message"
},
) {
Text(text = "Open Snackbar")
}
SnackbarWithoutScaffold(snackBarMessage, openMySnackbar, , {openMySnackbar = it})
}
}
You can use this
@Composable
fun MainScreen() {
val coroutineScope = rememberCoroutineScope()
val showSnackBar: (
message: String?,
actionLabel: String,
actionPerformed: () -> Unit,
dismissed: () -> Unit
) -> Unit = { message, actionLabel, actionPerformed, dismissed ->
coroutineScope.launch {
val snackBarResult = scaffoldState.snackbarHostState.showSnackbar(
message = message.toString(),
actionLabel = actionLabel
)
when (snackBarResult) {
SnackbarResult.ActionPerformed -> actionPerformed.invoke()
SnackbarResult.Dismissed -> dismissed.invoke()
}
}
}
showSnackBar.invoke(
"YOUR_MESSAGE",
"ACTION_LABEL",
{
//TODO ON ACTION PERFORMED
},
{
//TODO ON DISMISSED
}
)
}
Let me add an other answer, i was not using scaffold and was using state hoisting
val loginState by viewModel.uiState.collectAsState() // STATE HOISTING
val snackbarHostState = remember { SnackbarHostState() }
val message = stringResource(id = R.string.error_login)
if (loginState.snackbarVisible) { // When passing true, show snackbar
LaunchedEffect(snackbarHostState) {
snackbarHostState.showSnackbar(
message = message,
actionLabel = "Do something."
)
}
}
Box(
modifier = Modifier.fillMaxSize()
) {
// Your screen content
// ...
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(Alignment.BottomCenter)
) // Snackbar location
}
First of all you need a SnackbarHostState, you can pass this state down to your composable where you want to trigger a snackbar message. or if you use a scaffold use that one scaffoldState.snackbarHostState
val snackbarHostState = remember { SnackbarHostState() }
Showing a snackbar is a side effect and should be wrapped in a LaunchEffect
Composable
@Composable
fun MyScreen(
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
Scaffold(scaffoldState = scaffoldState) {
Button(onClick = {
LaunchedEffect(scaffoldState.snackbarHostState) {
scaffoldState.snackbarHostState.showSnackbar(
message = "hello world",
actionLabel = "Retry"
)
}
// ...
}
}
See more information here https://developer.android.com/topic/architecture/ui-layer/events#consuming-trigger-updates
and https://developer.android.com/jetpack/compose/side-effects
You can show snackBar without Scaffold using SnackBar
Composable either.
Box(modifier = Modifier.fillMaxSize()) {
var showSnackbar by remember {
mutableStateOf(false)
}
LaunchedEffect(key1 = showSnackbar) {
if (showSnackbar) {
delay(2000)
showSnackbar = false
}
}
Column {
Button(onClick = {
showSnackbar = !showSnackbar
}) {
Text("Show Snackbar")
}
}
if (showSnackbar) {
androidx.compose.material.Snackbar(modifier = Modifier.align(Alignment.BottomStart),
action = {
Text(text = "Action",
color = Color(0xffCE93D8),
modifier = Modifier.clickable {
showSnackbar = false
}
)
}) {
androidx.compose.material.Text("Message")
}
}
}
LaunchedEffect is to remove it from composition. You can use a slide animation if you want to.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.