Reputation: 7022
Simple code below for showing the Compose Snackbar
This code correctly shows the Snackbar, when onClick event occurs.
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) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d("SnackbarDemo", "Snackbar's button clicked")
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
How to dismiss snackbar on right/left-swipe?
Upvotes: 10
Views: 3925
Reputation: 882
Material 2 solution
Solved this by using SwipeToDismiss
Material component.
@OptIn(ExperimentalMaterialApi::class)
@Composable fun AppSwipeableSnackbarWrapper(
state: AppSnackbarHostState,
modifier: Modifier = Modifier,
dismissSnackbarState: DismissState = rememberDismissState { value ->
if (value != Default) {
state.currentSnackbarData?.dismiss()
true
} else {
false
}
},
dismissContent: @Composable RowScope.() -> Unit
) {
LaunchedEffect(dismissSnackbarState.currentValue) {
if (dismissSnackbarState.currentValue != Default) {
dismissSnackbarState.reset()
}
}
SwipeToDismiss(
modifier = modifier,
state = dismissSnackbarState,
background = {},
dismissContent = dismissContent,
)
}
And then maybe you can use this wrapper like this or adapt it was you need:
snackbarHost = {
AppSwipeableSnackbarWrapper(
state = snackbarHostState,
dismissContent = { AppSnackbarHost(hostState = snackbarHostState) }
)
},
Material 3 solution (Edit January 2024)
Check M3Experiment2
implementation using SwipeToDismissBox
.
Upvotes: 2
Reputation: 87894
The SnackbarHost
has no such functionality. But you can extend it with the nackbarHost
argument.
Also if you want the snackbar to disappear only with a swipe, you probably need to set the duration to Indefinite
:
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState,
snackbarHost = { SwipeableSnackbarHost(it) } // modification 1
) {
Button(
onClick = {
coroutineScope.launch {
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something.",
duration = SnackbarDuration.Indefinite, // modification 2
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d(
"SnackbarDemo",
"Snackbar's button clicked"
)
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
SwipeableSnackbarHost
inspired by this answer
enum class SwipeDirection {
Left,
Initial,
Right,
}
@Composable
fun SwipeableSnackbarHost(hostState: SnackbarHostState) {
if (hostState.currentSnackbarData == null) { return }
var size by remember { mutableStateOf(Size.Zero) }
val swipeableState = rememberSwipeableState(SwipeDirection.Initial)
val width = remember(size) {
if (size.width == 0f) {
1f
} else {
size.width
}
}
if (swipeableState.isAnimationRunning) {
DisposableEffect(Unit) {
onDispose {
when (swipeableState.currentValue) {
SwipeDirection.Right,
SwipeDirection.Left -> {
hostState.currentSnackbarData?.dismiss()
}
else -> {
return@onDispose
}
}
}
}
}
val offset = with(LocalDensity.current) {
swipeableState.offset.value.toDp()
}
SnackbarHost(
hostState,
snackbar = { snackbarData ->
Snackbar(
snackbarData,
modifier = Modifier.offset(x = offset)
)
},
modifier = Modifier
.onSizeChanged { size = Size(it.width.toFloat(), it.height.toFloat()) }
.swipeable(
state = swipeableState,
anchors = mapOf(
-width to SwipeDirection.Left,
0f to SwipeDirection.Initial,
width to SwipeDirection.Right,
),
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
)
}
Upvotes: 10