Add Dialogs to Your App with Material 3 in Jetpack Compose

Dialogs are a crucial component in mobile app development, providing a flexible way to interact with users. With Jetpack Compose and Material 3 (M3), creating and customizing dialogs has become more intuitive and modern. This blog post dives into implementing dialogs using Material 3 in Jetpack Compose, exploring best practices and advanced use cases to elevate your app's user experience.

Why Use Material 3 in Jetpack Compose?

Material 3, also known as Material You, is Google's latest design system that focuses on personalization and accessibility. When paired with Jetpack Compose, it provides:

  • Dynamic theming that adapts to user preferences.

  • Updated UI components with improved aesthetics and functionality.

  • Declarative syntax for streamlined development.

By integrating Material 3 dialogs into your app, you ensure a cohesive and modern design that aligns with Android’s latest guidelines.

Setting Up Your Project

To use Material 3 dialogs, ensure your project is configured correctly:

  1. Update Dependencies: Add the latest Jetpack Compose and Material 3 libraries to your build.gradle file.

    implementation "androidx.compose.material3:material3:<latest-version>"
    implementation "androidx.compose.ui:ui:<latest-version>"
    implementation "androidx.compose.runtime:runtime:<latest-version>"
  2. Enable Compose: In your build.gradle file, confirm that Jetpack Compose is enabled:

    buildFeatures {
        compose true
    }
    
    composeOptions {
        kotlinCompilerExtensionVersion '<latest-version>'
    }
  3. Set Up Material 3 Theme: Use MaterialTheme from Material 3 in your app’s root composable:

    @Composable
    fun MyApp() {
        MaterialTheme(
            colorScheme = lightColorScheme(),
            typography = Typography
        ) {
            // Content
        }
    }

With these steps complete, you're ready to implement Material 3 dialogs.

Basic Dialog in Jetpack Compose

To create a dialog, use the AlertDialog composable. Here’s a simple example:

@Composable
fun SimpleDialog(showDialog: Boolean, onDismiss: () -> Unit) {
    if (showDialog) {
        AlertDialog(
            onDismissRequest = onDismiss,
            title = {
                Text(text = "Title")
            },
            text = {
                Text(text = "This is a simple Material 3 dialog.")
            },
            confirmButton = {
                TextButton(onClick = onDismiss) {
                    Text("OK")
                }
            },
            dismissButton = {
                TextButton(onClick = onDismiss) {
                    Text("Cancel")
                }
            }
        )
    }
}

Key Components:

  • onDismissRequest: Callback when the dialog is dismissed.

  • title and text: Define the dialog's content.

  • confirmButton and dismissButton: Provide user actions.

Customizing Material 3 Dialogs

Material 3’s AlertDialog allows for extensive customization, enabling you to create visually engaging dialogs. Below are some common customizations:

1. Dynamic Theming

Use Material 3’s dynamicColorScheme to match the dialog’s colors to the system theme:

val colorScheme = dynamicColorScheme(LocalContext.current)
MaterialTheme(colorScheme = colorScheme) {
    SimpleDialog(showDialog, onDismiss)
}

2. Custom Buttons

Replace default buttons with custom designs:

confirmButton = {
    Button(onClick = onConfirm) {
        Text("Confirm", style = MaterialTheme.typography.labelLarge)
    }
}

dismissButton = {
    OutlinedButton(onClick = onDismiss) {
        Text("Cancel")
    }
}

3. Shape and Elevation

Override dialog shape and elevation for a unique appearance:

AlertDialog(
    modifier = Modifier.clip(RoundedCornerShape(16.dp)),
    containerColor = MaterialTheme.colorScheme.primaryContainer,
    tonalElevation = 8.dp,
    // Other parameters
)

Advanced Use Cases

Material 3 dialogs shine in complex scenarios where custom functionality and design are required.

1. Custom Layouts

For a fully customized dialog, use Dialog instead of AlertDialog:

@Composable
fun CustomDialog(showDialog: Boolean, onDismiss: () -> Unit) {
    if (showDialog) {
        Dialog(onDismissRequest = onDismiss) {
            Box(
                modifier = Modifier
                    .size(300.dp)
                    .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(12.dp))
            ) {
                Column(
                    modifier = Modifier.padding(16.dp),
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    Text("Custom Title", style = MaterialTheme.typography.headlineSmall)
                    Text("This is a fully customized dialog layout.")
                    Row(
                        modifier = Modifier.align(Alignment.End),
                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        TextButton(onClick = onDismiss) {
                            Text("Cancel")
                        }
                        Button(onClick = onDismiss) {
                            Text("OK")
                        }
                    }
                }
            }
        }
    }
}

2. Handling State and Events

Manage dialog visibility and actions using a ViewModel:

class DialogViewModel : ViewModel() {
    private val _dialogState = mutableStateOf(false)
    val dialogState: State<Boolean> = _dialogState

    fun showDialog() {
        _dialogState.value = true
    }

    fun hideDialog() {
        _dialogState.value = false
    }
}

@Composable
fun ViewModelDialog(viewModel: DialogViewModel) {
    val showDialog by viewModel.dialogState
    SimpleDialog(showDialog, viewModel::hideDialog)
}

3. Animations

Leverage Compose’s animation APIs to add smooth transitions to your dialogs:

@Composable
fun AnimatedDialog(showDialog: Boolean, onDismiss: () -> Unit) {
    val scale by animateFloatAsState(if (showDialog) 1f else 0f)

    if (showDialog || scale > 0f) {
        Dialog(onDismissRequest = onDismiss) {
            Box(
                modifier = Modifier
                    .graphicsLayer(scaleX = scale, scaleY = scale)
                    .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(16.dp))
                    .padding(16.dp)
            ) {
                Text("Animated Dialog", style = MaterialTheme.typography.bodyLarge)
            }
        }
    }
}

Best Practices for Material 3 Dialogs

  • Accessibility: Use proper content descriptions and focus management.

  • Theming: Align with dynamic themes to enhance user experience.

  • Minimalism: Keep dialogs concise and actionable.

  • Testing: Ensure dialogs render correctly across devices and configurations.

Conclusion

Material 3 in Jetpack Compose simplifies dialog implementation while providing powerful customization options. By leveraging its modern design principles, you can create dialogs that are functional, visually appealing, and aligned with user expectations. Start integrating Material 3 dialogs today to elevate your app’s user interface and engagement.

For more insights on Jetpack Compose and Material 3, follow this blog and stay updated with the latest in Android development!