Snackbars are a fundamental UI element in modern Android apps, providing lightweight feedback to users about an operation, with the option to include actionable buttons. With Jetpack Compose’s declarative UI paradigm, implementing and customizing Snackbars has become more flexible and powerful.
In this blog, we’ll explore how to add action buttons to Snackbars in Jetpack Compose while adhering to best practices, ensuring accessibility, and using the latest APIs. Whether you're designing a simple undo operation or implementing more complex interactions, this guide is for you.
Understanding Snackbars in Jetpack Compose
In Jetpack Compose, Snackbars are handled using the SnackbarHost
and SnackbarHostState
classes. The SnackbarHost
is a composable that displays Snackbars, while the SnackbarHostState
manages the state of the Snackbar queue. This decoupled architecture allows greater control and flexibility when displaying messages.
Here’s a basic example of creating and displaying a Snackbar:
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) {
Button(onClick = {
scope.launch {
snackbarHostState.showSnackbar("Hello, Snackbar!")
}
}) {
Text("Show Snackbar")
}
}
This snippet shows a simple Snackbar without any action button. Let’s enhance it by adding actionable buttons next.
Adding Action Buttons to Snackbars
Action buttons provide interactivity within Snackbars, allowing users to respond immediately to the message. To include an action button, the SnackbarData
class offers the actionLabel
parameter. Here’s how you can implement it:
Button(onClick = {
scope.launch {
snackbarHostState.showSnackbar(
message = "Item deleted",
actionLabel = "Undo"
)
}
}) {
Text("Delete Item")
}
When an actionLabel
is provided, the Snackbar renders a button alongside the message. Users can tap this button to perform the desired action. To handle user interactions with this button, use the return value of showSnackbar()
:
val result = scope.launch {
val snackbarResult = snackbarHostState.showSnackbar(
message = "Item deleted",
actionLabel = "Undo"
)
if (snackbarResult == SnackbarResult.ActionPerformed) {
// Handle the undo action
println("Undo clicked")
}
}
Customizing Snackbars in Jetpack Compose
While the default Snackbar appearance works for many use cases, Jetpack Compose allows full customization of its design and behavior. Let’s look at some advanced customization options.
1. Styling the Snackbar
To change the default look, wrap the Snackbar
composable in a custom SnackbarHost
:
SnackbarHost(hostState = snackbarHostState) { data ->
Snackbar(
snackbarData = data,
backgroundColor = Color.Black,
contentColor = Color.White,
actionColor = Color.Cyan
)
}
Here, you can customize colors and typography to match your app’s theme.
2. Custom Layout for the Snackbar
Compose’s flexibility allows you to create completely custom layouts for your Snackbars:
SnackbarHost(hostState = snackbarHostState) { data ->
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.DarkGray)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = data.message,
color = Color.White,
style = MaterialTheme.typography.body1
)
Button(onClick = { /* Custom action */ }) {
Text("Retry")
}
}
}
This approach gives you complete control over how the Snackbar appears and behaves.
Best Practices for Using Snackbars
When implementing Snackbars in your app, consider the following best practices:
Use Snackbars Sparingly: Avoid overwhelming users with too many Snackbars. Use them for essential feedback only.
Ensure Accessibility: Use descriptive messages and action labels. Avoid jargon or technical terms.
Provide Timely Actions: The action should be relevant to the context, like undoing a delete operation.
Limit Action Count: Stick to a single action to keep the UI simple and intuitive.
Respect Timing: Snackbars automatically dismiss after a few seconds. If more time is needed, consider alternative feedback mechanisms.
Advanced Use Cases for Snackbars
Snackbars can be used in creative and complex ways to enhance user experience. Here are some advanced scenarios:
1. Snackbar Queuing
Compose’s SnackbarHostState
manages a queue of Snackbars, displaying them one at a time. Here’s how to enqueue multiple messages:
scope.launch {
snackbarHostState.showSnackbar("First Message")
snackbarHostState.showSnackbar("Second Message")
}
Snackbars will appear sequentially, maintaining a clean and orderly experience.
2. Conditional Snackbar Display
Snackbars can be conditionally displayed based on state changes. For example, show a Snackbar when a network error occurs:
val isError by remember { mutableStateOf(false) }
if (isError) {
scope.launch {
snackbarHostState.showSnackbar("Network error occurred")
}
}
3. Global Snackbar Management
For apps with multiple screens, managing Snackbars globally ensures consistency. Use a shared SnackbarHostState
across your app’s Scaffold
:
val snackbarHostState = remember { SnackbarHostState() }
NavHost(...) {
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { ... }
}
Additional Resources
To deepen your understanding of Snackbars in Jetpack Compose, explore these external resources:
Snackbars in Jetpack Compose - Official Android documentation.
Material Design Guidelines for Snackbars - Comprehensive guide on Material Design principles.
Jetpack Compose Basics - Learn more about building UIs with Jetpack Compose.
Conclusion
Snackbars are a versatile and essential component of any Android app, providing quick feedback and actionable options for users. By leveraging Jetpack Compose’s declarative approach, you can create customized, accessible, and contextually relevant Snackbars to enhance the user experience.
With the knowledge and best practices shared in this guide, you’re now equipped to implement Snackbars like a pro. Experiment with different designs and use cases to make your app’s UI more interactive and engaging.
Happy coding!