Jetpack Compose Toast

Toasts are brief notifications that provide simple feedback about an operation. They appear at the bottom of the screen, contain only text messages, and automatically disappear after a timeout.

Implementing Toast in Jetpack Compose

Since Toast is part of the Android framework and not specific to Compose, we need to access the context to display it in our Compose UI.

import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp

@Composable
fun ToastExample() {
    // Get the current context
    val context = LocalContext.current
    
    Column(modifier = Modifier.padding(16.dp)) {
        Button(
            onClick = {
                // Show a short toast message
                Toast.makeText(
                    context,
                    "This is a short Toast message",
                    Toast.LENGTH_SHORT
                ).show()
            }
        ) {
            Text("Show Short Toast")
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        Button(
            onClick = {
                // Show a long toast message
                Toast.makeText(
                    context,
                    "This is a longer Toast message that stays visible for more time",
                    Toast.LENGTH_LONG
                ).show()
            }
        ) {
            Text("Show Long Toast")
        }
    }
}

Key Properties of Toast in Compose Applications

1. Duration

Toasts have two standard durations:

  • Toast.LENGTH_SHORT: Displays for approximately 2 seconds
  • Toast.LENGTH_LONG: Displays for approximately 3.5 seconds

2. Position

By default, toasts appear at the bottom of the screen, but you can customize their position:

import android.view.Gravity
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp

@Composable
fun CustomPositionToastExample() {
    val context = LocalContext.current
    
    Column(modifier = Modifier.padding(16.dp)) {
        Button(
            onClick = {
                val toast = Toast.makeText(
                    context,
                    "Toast at the top of screen",
                    Toast.LENGTH_SHORT
                )
                // Set the gravity to top
                toast.setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL, 0, 50)
                toast.show()
            }
        ) {
            Text("Top Toast")
        }
        
        Button(
            onClick = {
                val toast = Toast.makeText(
                    context,
                    "Toast at the center of screen",
                    Toast.LENGTH_SHORT
                )
                // Set the gravity to center
                toast.setGravity(Gravity.CENTER, 0, 0)
                toast.show()
            }
        ) {
            Text("Center Toast")
        }
        
        Button(
            onClick = {
                val toast = Toast.makeText(
                    context,
                    "Toast at the bottom-right of screen",
                    Toast.LENGTH_SHORT
                )
                // Set the gravity to bottom-right
                toast.setGravity(Gravity.BOTTOM or Gravity.END, 20, 20)
                toast.show()
            }
        ) {
            Text("Bottom-Right Toast")
        }
    }
}

3. Custom Toast Layout

For more complex needs, you can create custom toast layouts:

import android.view.Gravity
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp

@Composable
fun CustomLayoutToastExample() {
    val context = LocalContext.current
    
    Button(
        onClick = {
            // Inflate the custom layout
            val inflater = LayoutInflater.from(context)
            val layout = inflater.inflate(R.layout.custom_toast_layout, null)
            
            // Set the text and image
            val text = layout.findViewById<TextView>(R.id.toast_text)
            text.text = "Custom Toast with Image"
            
            val image = layout.findViewById<ImageView>(R.id.toast_image)
            image.setImageResource(R.drawable.ic_notification)
            
            // Create and show the Toast
            val toast = Toast(context)
            toast.setGravity(Gravity.CENTER, 0, 0)
            toast.duration = Toast.LENGTH_LONG
            toast.view = layout
            toast.show()
        },
        modifier = Modifier.padding(16.dp)
    ) {
        Text("Show Custom Toast")
    }
}

// Note: You'll need to create a layout file named custom_toast_layout.xml
// Example layout:
/*
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/custom_toast_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable oast_background"
    android:orientation="horizontal"
    android:padding="16dp">

    <ImageView
        android:id="@+id oast_image"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginEnd="8dp"
        android:contentDescription="Toast Icon" />

    <TextView
        android:id="@+id oast_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#FFFFFF" />
</LinearLayout>
*/

Creating a Reusable Toast Function for Compose

For better code organization and reusability, let's create a helper function for showing toasts:

import android.content.Context
import android.view.Gravity
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.launch

/**
 * Shows a toast message with the specified parameters.
 * 
 * @param message The message to display.
 * @param duration The duration for which the toast should be visible.
 * @param gravity The position of the toast on the screen.
 * @param xOffset The x-offset from the specified gravity.
 * @param yOffset The y-offset from the specified gravity.
 */
fun Context.showToast(
    message: String,
    duration: Int = Toast.LENGTH_SHORT,
    gravity: Int = Gravity.BOTTOM,
    xOffset: Int = 0,
    yOffset: Int = 0
) {
    val toast = Toast.makeText(this, message, duration)
    toast.setGravity(gravity, xOffset, yOffset)
    toast.show()
}

/**
 * A composable function that provides an easy way to show toasts from within composable functions.
 */
@Composable
fun rememberToastHelper(): ToastHelper {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    return remember { ToastHelper(context, scope) }
}

/**
 * Helper class for showing toasts from composable functions.
 */
class ToastHelper(
    private val context: Context,
    private val scope: CoroutineScope
) {
    /**
     * Shows a toast message.
     * 
     * @param message The message to display.
     * @param duration The duration for which the toast should be visible.
     */
    fun showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
        scope.launch {
            Toast.makeText(context, message, duration).show()
        }
    }
    
    /**
     * Shows a positioned toast message.
     * 
     * @param message The message to display.
     * @param duration The duration for which the toast should be visible.
     * @param gravity The position of the toast on the screen.
     * @param xOffset The x-offset from the specified gravity.
     * @param yOffset The y-offset from the specified gravity.
     */
    fun showPositionedToast(
        message: String, 
        duration: Int = Toast.LENGTH_SHORT,
        gravity: Int = Gravity.CENTER,
        xOffset: Int = 0,
        yOffset: Int = 0
    ) {
        scope.launch {
            val toast = Toast.makeText(context, message, duration)
            toast.setGravity(gravity, xOffset, yOffset)
            toast.show()
        }
    }
}

Using the Toast Helper in Compose UI

Now let's see how to use our reusable toast helper in a practical example:

import android.view.Gravity
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp

@Composable
fun ToastDemoScreen() {
    val context = LocalContext.current
    // Using our custom toast helper
    val toastHelper = rememberToastHelper()
    
    var messageText by remember { mutableStateOf("Hello, Toast!") }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        OutlinedTextField(
            value = messageText,
            onValueChange = { messageText = it },
            label = { Text("Toast Message") }
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = {
            // Using extension function directly
            context.showToast(messageText)
        }) {
            Text("Show Simple Toast")
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        Button(onClick = {
            // Using toast helper
            toastHelper.showToast(messageText, Toast.LENGTH_LONG)
        }) {
            Text("Show Long Toast")
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        Button(onClick = {
            // Using positioned toast
            toastHelper.showPositionedToast(
                message = messageText,
                duration = Toast.LENGTH_SHORT,
                gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL,
                yOffset = 50
            )
        }) {
            Text("Show Top Toast")
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        Button(onClick = {
            // Using extension function with custom position
            context.showToast(
                message = messageText,
                duration = Toast.LENGTH_SHORT,
                gravity = Gravity.CENTER,
                xOffset = 0,
                yOffset = 0
            )
        }) {
            Text("Show Center Toast")
        }
    }
}

Best Practices for Using Toast in Jetpack Compose

1. Keep Messages Short and Concise

Toasts are designed for brief messages. If your message is longer than a few words, consider using a Snackbar instead.

// Good
"Item saved"

// Bad
"Your item has been successfully saved to the database and will be available for future use"

2. Use Toast for Non-Critical Information

Toasts automatically disappear and don't require user interaction. Use them for informational messages that don't require user action.

// Good usage of Toast
Toast.makeText(context, "Settings updated", Toast.LENGTH_SHORT).show()

// Better with Snackbar (when action is needed)
Snackbar.make(view, "No internet connection", Snackbar.LENGTH_LONG)
    .setAction("Retry") { /* retry logic */ }
    .show()

3. Don't Overuse Toasts

Too many toast messages can annoy users. Use them sparingly for important but non-critical information.

4. Consider Accessibility

Toast messages disappear automatically, which can cause issues for users with accessibility needs. Consider providing alternative feedback methods for critical information.

Common Toast Implementation Patterns in Compose

1. Toast with ViewModel

import android.app.Application
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow

// Event class for showing toast
sealed class UiEvent {
    data class ShowToast(val message: String, val duration: Int = Toast.LENGTH_SHORT) : UiEvent()
}

// ViewModel with toast event
class ToastViewModel(application: Application) : AndroidViewModel(application) {
    
    private val _uiEvent = MutableSharedFlow<UiEvent>()
    val uiEvent = _uiEvent.asSharedFlow()
    
    // Function to show toast
    suspend fun showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
        _uiEvent.emit(UiEvent.ShowToast(message, duration))
    }
    
    // Example function that might trigger a toast
    fun saveData(data: String) {
        // Save logic...
        // After saving
        viewModelScope.launch {
            showToast("Data saved successfully")
        }
    }
}

// Composable to collect toast events
@Composable
fun ToastEventCollector(viewModel: ToastViewModel = viewModel()) {
    val context = LocalContext.current
    
    LaunchedEffect(key1 = true) {
        viewModel.uiEvent.collect { event ->
            when (event) {
                is UiEvent.ShowToast -> {
                    Toast.makeText(context, event.message, event.duration).show()
                }
            }
        }
    }
}

// Usage in your UI
@Composable
fun ToastWithViewModelScreen(viewModel: ToastViewModel = viewModel()) {
    // Collect toast events
    ToastEventCollector(viewModel)
    
    // Your UI content
    Column {
        Button(onClick = {
            viewModel.viewModelScope.launch {
                viewModel.showToast("Button clicked!")
            }
        }) {
            Text("Show Toast from ViewModel")
        }
    }
}

2. Creating a Custom Toast-Like Composable

For full control over appearance and behavior, you can create a toast-like composable:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay

/**
 * A custom composable that mimics the behavior of a Toast but with full control
 * over appearance and animation.
 * 
 * @param message The message to display.
 * @param duration How long the toast should be visible in milliseconds.
 * @param modifier Modifier for the toast container.
 */
@Composable
fun ComposeToast(
    message: String,
    duration: Long = 2000L,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.BottomCenter
    ) {
        val visible = remember { MutableTransitionState(false).apply { targetState = true } }
        
        LaunchedEffect(key1 = message) {
            delay(duration)
            visible.targetState = false
        }
        
        AnimatedVisibility(
            visibleState = visible,
            enter = fadeIn(animationSpec = tween(300)) + 
                   slideInVertically(animationSpec = tween(300)) { fullHeight -> fullHeight },
            exit = fadeOut(animationSpec = tween(300)) + 
                   slideOutVertically(animationSpec = tween(300)) { fullHeight -> fullHeight }
        ) {
            Card(
                modifier = modifier
                    .padding(16.dp),
                shape = RoundedCornerShape(8.dp),
                backgroundColor = Color(0xFF323232)
            ) {
                Text(
                    text = message,
                    modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp),
                    color = Color.White,
                    fontSize = 14.sp,
                    textAlign = TextAlign.Center
                )
            }
        }
    }
}

// Usage example
@Composable
fun CustomToastDemo() {
    val showToast = remember { mutableStateOf(false) }
    val toastMessage = remember { mutableStateOf("") }
    
    Box(modifier = Modifier.fillMaxSize()) {
        Button(
            onClick = {
                toastMessage.value = "This is a custom Compose Toast!"
                showToast.value = true
            },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text("Show Custom Toast")
        }
        
        if (showToast.value) {
            ComposeToast(
                message = toastMessage.value,
                duration = 2000L
            )
            
            // Reset the state after showing
            LaunchedEffect(key1 = Unit) {
                delay(2300L) // slightly longer than the toast duration
                showToast.value = false
            }
        }
    }
}

Toast vs. Snackbar: When to Use Each

While Toasts provide simple feedback, Snackbars offer more features. Here's a comparison to help you choose:

FeatureToastSnackbar
User ActionNo action requiredCan include an action button
PositioningPrimarily bottom of screen, customizableTypically bottom of screen
DurationFixed durations (SHORT, LONG)More flexible duration control
DismissalAuto-dismissal onlyCan be swiped away or dismissed by action
IntegrationAndroid framework componentMaterial Design component
StackingMultiple toasts queue upOnly one shown at a time
Compose ImplementationRequires contextNative Compose implementation available