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.
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")
}
}
}
Toasts have two standard durations:
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")
}
}
}
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>
*/
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()
}
}
}
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")
}
}
}
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"
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()
Too many toast messages can annoy users. Use them sparingly for important but non-critical information.
Toast messages disappear automatically, which can cause issues for users with accessibility needs. Consider providing alternative feedback methods for critical information.
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")
}
}
}
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
}
}
}
}
While Toasts provide simple feedback, Snackbars offer more features. Here's a comparison to help you choose:
Feature | Toast | Snackbar |
---|---|---|
User Action | No action required | Can include an action button |
Positioning | Primarily bottom of screen, customizable | Typically bottom of screen |
Duration | Fixed durations (SHORT, LONG) | More flexible duration control |
Dismissal | Auto-dismissal only | Can be swiped away or dismissed by action |
Integration | Android framework component | Material Design component |
Stacking | Multiple toasts queue up | Only one shown at a time |
Compose Implementation | Requires context | Native Compose implementation available |