Jetpack Compose: Snackbar and Toasts

In modern Android app development with Jetpack Compose, providing feedback to users is essential for a good user experience. Two common ways to provide feedback are using Snackbars and Toasts. While Toasts are straightforward, Snackbars in Compose offer more interactive capabilities and can be elegantly integrated into your UI.

What are Snackbars and Toasts?

  • Toasts: Simple, non-interactive messages displayed briefly on the screen. They are typically used for quick notifications that don’t require user interaction.
  • Snackbars: Provide more interactive feedback, typically appearing at the bottom of the screen. They can include an action button for the user to interact with.

Why Use Snackbars over Toasts in Compose?

  • Interactive: Snackbars can include an action button for users.
  • Integration: Easier to integrate into Compose’s declarative UI paradigm.
  • Contextual: They provide feedback within the UI’s context.

How to Implement Toasts in Jetpack Compose

Toasts in Jetpack Compose are similar to traditional Android development. You use the Toast.makeText() method within your composable function.

Step 1: Basic Toast Implementation


import android.content.Context
import android.widget.Toast
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun ToastExample() {
    val context = LocalContext.current

    Button(onClick = {
        Toast.makeText(context, "This is a Toast!", Toast.LENGTH_SHORT).show()
    }) {
        Text("Show Toast")
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewToastExample() {
    ToastExample()
}

Explanation:

  • LocalContext.current provides the current context.
  • The Button triggers a Toast when clicked.
  • Toast.makeText() creates and displays the Toast message.

How to Implement Snackbars in Jetpack Compose

Implementing Snackbars in Jetpack Compose requires the use of SnackbarHost and SnackbarHostState. The SnackbarHostState manages the showing and dismissing of Snackbars, while SnackbarHost is a composable that displays the Snackbar when it is shown.

Step 1: Add Dependencies

Ensure you have the Material 3 dependencies in your build.gradle file:

dependencies {
    implementation("androidx.compose.material3:material3:1.1.1") // or newer
}

Step 2: Implement Snackbar with SnackbarHostState


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.Box
import androidx.compose.ui.Alignment

@Composable
fun SnackbarExample() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Box(
        contentAlignment = Alignment.Center
    ) {
        Button(onClick = {
            scope.launch {
                snackbarHostState.showSnackbar(
                    message = "This is a Snackbar!",
                    actionLabel = "Dismiss",
                    duration = SnackbarDuration.Short
                )
            }
        }) {
            Text("Show Snackbar")
        }

        SnackbarHost(hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter))
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewSnackbarExample() {
    SnackbarExample()
}

Explanation:

  • SnackbarHostState is used to manage the Snackbar’s visibility and lifecycle.
  • rememberCoroutineScope() creates a CoroutineScope tied to the composable’s lifecycle.
  • scope.launch launches a coroutine to show the Snackbar.
  • snackbarHostState.showSnackbar() displays the Snackbar with a message and an optional action label.
  • SnackbarHost is the composable that actually renders the Snackbar on the screen, listening to the snackbarHostState.

Step 3: Adding an Action to the Snackbar


import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.Box
import androidx.compose.ui.Alignment

@Composable
fun SnackbarWithActionExample() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Box(
        contentAlignment = Alignment.Center
    ) {
        Button(onClick = {
            scope.launch {
                val result = snackbarHostState.showSnackbar(
                    message = "This is a Snackbar with an Action!",
                    actionLabel = "Undo",
                    duration = SnackbarDuration.Indefinite
                )
                when (result) {
                    SnackbarResult.ActionPerformed -> {
                        // Handle the action (e.g., undo something)
                        println("Undo action performed!")
                    }
                    SnackbarResult.Dismissed -> {
                        // Handle the dismissal
                        println("Snackbar dismissed.")
                    }
                }
            }
        }) {
            Text("Show Snackbar with Action")
        }

        SnackbarHost(hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter))
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewSnackbarWithActionExample() {
    SnackbarWithActionExample()
}

Explanation:

  • The showSnackbar function returns a SnackbarResult indicating whether the action was performed or the Snackbar was dismissed.
  • Based on the SnackbarResult, you can execute different logic, such as undoing an action when ActionPerformed is returned.
  • SnackbarDuration.Indefinite makes the Snackbar remain on the screen until an action is taken or it is manually dismissed.

Advanced Snackbar Usage

Customizing the Snackbar

You can customize the appearance of the Snackbar by providing a custom SnackbarData to the SnackbarHost. Here is an example that modifies the color of the text and action:


import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Button
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.material3.Snackbar
import androidx.compose.material3.MaterialTheme

@Composable
fun CustomSnackbarExample() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Box(
        contentAlignment = Alignment.Center
    ) {
        Button(onClick = {
            scope.launch {
                val result = snackbarHostState.showSnackbar(
                    message = "This is a Customized Snackbar!",
                    actionLabel = "Okay",
                    duration = SnackbarDuration.Short
                )
                when (result) {
                    SnackbarResult.ActionPerformed -> {
                        // Handle action performed
                    }
                    SnackbarResult.Dismissed -> {
                        // Handle dismissed
                    }
                }
            }
        }) {
            Text("Show Customized Snackbar")
        }

        SnackbarHost(hostState = snackbarHostState) { data ->
            Snackbar(
                snackbarData = data,
                contentColor = Color.Yellow,  // Custom content color
                actionColor = Color.Green    // Custom action color
            )
        }
    }
}

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

In this example, we define a custom Snackbar within the SnackbarHost, which allows us to set custom colors for the text (contentColor) and the action (actionColor).

Conclusion

Jetpack Compose offers excellent ways to provide feedback to users through Toasts and Snackbars. While Toasts are simple for quick, non-interactive messages, Snackbars provide more flexibility and interactivity, fitting perfectly within Compose’s modern UI paradigm. By leveraging SnackbarHost and SnackbarHostState, you can easily integrate Snackbars into your Composable functions to enhance the user experience.