Snackbar Messaging in Jetpack Compose: A Comprehensive Guide

In modern Android development with Jetpack Compose, providing feedback to the user is crucial for a good user experience. A Snackbar is a lightweight, non-modal message displayed at the bottom of the screen to provide brief information about app processes. It’s commonly used to confirm user actions, display errors, or provide simple notifications. In this comprehensive guide, we’ll explore how to implement Snackbar messaging in Jetpack Compose, covering everything from basic implementation to advanced customization.

What is a Snackbar?

A Snackbar is a UI element that displays brief, important messages to the user. Unlike AlertDialogs, Snackbars do not interrupt the user’s workflow and disappear automatically after a short period or upon user interaction. They are an integral part of providing real-time feedback in a non-intrusive way.

Why Use Snackbars?

  • Non-Intrusive: Does not interrupt the user’s current task.
  • Informative: Provides immediate feedback on actions performed.
  • User-Friendly: Conveys simple and quick messages, enhancing the overall experience.

How to Implement Snackbar Messaging in Jetpack Compose

To implement Snackbar messaging, follow these steps:

Step 1: Set Up Dependencies

Make sure you have the necessary Material Design dependencies in your build.gradle file:

dependencies {
    implementation("androidx.compose.material3:material3:1.1.1")
    implementation("androidx.compose.material:material:1.5.4") // For legacy compatibility
    implementation("androidx.compose.ui:ui:1.6.0")
    implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
    implementation("androidx.compose.runtime:runtime:1.6.0")
    implementation("androidx.compose.material:material-icons-core:1.5.4") // for icons
}

Step 2: Basic Snackbar Implementation

Here’s a basic example of showing a Snackbar in Jetpack Compose using SnackbarHost and SnackbarHostState:


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.rememberCoroutineScope

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleSnackbarExample() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        },
        content = { padding ->
            Column(
                modifier = Modifier.fillMaxSize().padding(padding),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar(
                            message = "Hello Snackbar!",
                            duration = SnackbarDuration.Short
                        )
                    }
                }) {
                    Text("Show Snackbar")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun PreviewSimpleSnackbarExample() {
    SimpleSnackbarExample()
}

In this example:

  • SnackbarHostState is used to control the Snackbar.
  • The Scaffold composable provides a structure to display a SnackbarHost.
  • A CoroutineScope is used to launch the Snackbar via snackbarHostState.showSnackbar.
  • SnackbarDuration specifies how long the Snackbar will be displayed.

Step 3: Adding Action to Snackbar

You can add an action button to the Snackbar, such as an “Undo” or “Retry” option:


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackbarWithActionEvent() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        },
        content = { padding ->
            Column(
                modifier = Modifier.fillMaxSize().padding(padding),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    scope.launch {
                        val result = snackbarHostState.showSnackbar(
                            message = "File deleted",
                            actionLabel = "Undo",
                            duration = SnackbarDuration.Short
                        )
                        when (result) {
                            SnackbarResult.ActionPerformed -> {
                                // Handle the undo action
                                println("Undo action performed")
                            }
                            SnackbarResult.Dismissed -> {
                                // Handle the dismissal
                                println("Snackbar dismissed")
                            }
                        }
                    }
                }) {
                    Text("Delete File")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun PreviewSnackbarWithActionEvent() {
    SnackbarWithActionEvent()
}

Key points in this example:

  • The actionLabel parameter adds a button to the Snackbar.
  • The result of showSnackbar indicates whether the action was performed or the Snackbar was dismissed.

Step 4: Customizing the Snackbar

You can customize the colors and content of the Snackbar using a custom Snackbar composable:


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomSnackbarExample() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState) { data ->
                Card(
                    modifier = Modifier.padding(8.dp),
                    containerColor = Color.DarkGray, // custom color
                    contentColor = Color.White       // custom text color
                ) {
                    Row(
                        modifier = Modifier.padding(16.dp),
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Text(text = data.visuals.message)
                        data.visuals.actionLabel?.let { actionLabel ->
                            Button(onClick = { scope.launch { data.performAction() } }) {
                                Text(text = actionLabel, color = Color.White)
                            }
                        }
                    }
                }
            }
        },
        content = { padding ->
            Column(
                modifier = Modifier.fillMaxSize().padding(padding),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar(
                            message = "Customized Snackbar!",
                            actionLabel = "Dismiss",
                            duration = SnackbarDuration.Short
                        )
                    }
                }) {
                    Text("Show Custom Snackbar")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun PreviewCustomSnackbarExample() {
    CustomSnackbarExample()
}

Key aspects of customization:

  • Wrap the Snackbar content in a Card composable.
  • Customize the containerColor and contentColor for custom styling.
  • Use the data parameter from SnackbarHost to access the Snackbar’s properties.

Step 5: Displaying Snackbars with Different Durations

You can specify the duration a Snackbar is displayed:


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.rememberCoroutineScope

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackbarDurationsExample() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        },
        content = { padding ->
            Column(
                modifier = Modifier.fillMaxSize().padding(padding),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar(
                            message = "Short Snackbar",
                            duration = SnackbarDuration.Short
                        )
                    }
                }) {
                    Text("Show Short Snackbar")
                }
                Spacer(modifier = Modifier.height(16.dp))
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar(
                            message = "Long Snackbar",
                            duration = SnackbarDuration.Long
                        )
                    }
                }) {
                    Text("Show Long Snackbar")
                }
                Spacer(modifier = Modifier.height(16.dp))
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar(
                            message = "Indefinite Snackbar.  Tap to dismiss.",
                            duration = SnackbarDuration.Indefinite
                        )
                    }
                }) {
                    Text("Show Indefinite Snackbar")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun PreviewSnackbarDurationsExample() {
    SnackbarDurationsExample()
}
  • SnackbarDuration.Short: Displayed for a brief period.
  • SnackbarDuration.Long: Displayed for a longer duration.
  • SnackbarDuration.Indefinite: Remains visible until dismissed by the user or programmatically.

Conclusion

Snackbar messaging is a crucial aspect of providing real-time, non-intrusive feedback in Jetpack Compose applications. By using SnackbarHost, SnackbarHostState, and CoroutineScope, you can easily display Snackbars for user confirmations, errors, or simple notifications. Customizing the appearance and adding actions further enhances the user experience, making your app more interactive and user-friendly. Experiment with the various configurations to find what works best for your specific needs!