Dialogs (dialogs) in Android

Video thumbnail

Dialogs in Android are small windows that appear over the main content to ask the user to make a decision or enter additional information. They are a fundamental component for user interaction and can be fully customized.

Continuing with our knowledge of components, the next one we will look at are dialogs.

These are those nice little windows that we can use for multiple operations: displaying information, confirming or canceling actions, or configuring them as you wish. It is a very open and flexible element.

Currently, there are two main approaches to creating dialogs in Android: the modern and recommended Jetpack Compose, and the traditional XML-based view system, using the Material Components library.

In this post, we will explore both methods, starting with the modern approach with Jetpack Compose. Previously, we learned how to implement a FloatingActionButton in Android Studio + Variants

Dialogs with Jetpack Compose (The Modern Approach)

Jetpack Compose is Google's modern and declarative UI toolkit. Creating dialogs with Compose is simpler and more intuitive. The main component is the AlertDialog composable.

Managing Dialog State

This component is a bit more complex than a button. Unlike buttons, which we just place and that's it, here we need to activate and deactivate the dialog.

For that, we are going to manage a state.

We create a variable called showDialog. It is a variable (var) because its value will change. We cannot use a constant:

var showDialog by remember { mutableStateOf(false) }

Here something new appears: remember and mutableStateOf.

If you come from Flutter, Vue, or any reactive framework, this will be familiar to you. This is basically Compose's answer to reactive state management.

If we declared a normal variable, like var showDialog = false, and then changed it by clicking a button, the interface would not update automatically. For that to happen "magically", we need a state.

With mutableStateOf we are telling Compose: hey, this variable is reactive. When its value changes, Compose will redraw the interface where it is being used.

Showing the Dialog

We use a conditional:

If showDialog is true, we show the dialog.

Here we have onDismissRequest, which is executed when the user touches outside the dialog or presses back. Normally, what we do is close the dialog, that is, set showDialog back to false.

Inside the dialog we define:

  • The title
  • The text
  • The confirmation button
  • The cancellation button (dismissButton)

In both buttons we close the dialog, although the actual logic will depend on what you want to do (delete something, confirm an action, etc.).

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.myproyectandroid.ui.theme.MyProyectAndroidTheme
class MainActivity : ComponentActivity() {
    @Composable
    fun ExampleDialogCompose() {
        // 1. Define the state (is the dialog shown?)
        var showDialog by remember { mutableStateOf(false) }
        // Button that activates the dialog
        Button(onClick = { showDialog = true }) {
            Text("Delete item")
        }
        // 2. Declare the Dialog
        if (showDialog) {
            AlertDialog(
                onDismissRequest = {
                    // Executes when the user touches outside the dialog or presses "Back"
                    showDialog = false
                },
                title = {
                    Text(text = "Confirm deletion")
                },
                text = {
                    Text(text = "Are you sure you want to permanently delete this file?")
                },
                confirmButton = {
                    TextButton(onClick = {
                        showDialog = false
                        // Your deletion logic would go here
                    }) {
                        Text("Confirm")
                    }
                },
                dismissButton = {
                    TextButton(onClick = { showDialog = false }) {
                        Text("Cancel")
                    }
                }
            )
        }
    }
}    
@Preview(showBackground = true)
@Composable
fun Preview() {
    MyProyectAndroidTheme {
        ExampleDialogCompose()
    }
}

What are remember and mutableStateOf?

In summary:

  • remember allows the state to be maintained between recompositions.
  • mutableStateOf creates an observable state.

When we change the value of showDialog, Compose detects the change, recomposes the component, and updates the interface.
If the value becomes true, the dialog appears. If it becomes false, it disappears.

Compose can redraw the screen many times, and that is completely normal. The important thing is not the value itself, but that it is wrapped in a reactive state.

Preview vs Emulator

The Preview is only for viewing a mockup of the interface. It has no real functionality.

To see the dialog's behavior, we need to run the app on an emulator or a real device.

We click the Play button, select the emulator (if you have several), and let it start.

Then, in MainActivity, we delete or comment out what we don't need and call the dialog composable we created:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyProyectAndroidTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    //ExampleDialogCompose()
                    ExampleDialogListCompose()
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

Every time we make a significant change, the application recompiles. Once loaded, we can see the actual result.

Simple Confirmation Dialog

For a dialog with a title, a message, and confirmation and cancellation buttons, you can use AlertDialog directly. Its visibility is controlled through a state.

var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
    AlertDialog(
        onDismissRequest = {
            // Executes when the user touches outside the dialog or the back button
            showDialog = false
        },
        title = { Text("Dialog Title") },
        text = { Text("This is an example message for the dialog. Are you sure you want to continue?") },
        confirmButton = {
            TextButton(
                onClick = {
                    // Confirmation action
                    showDialog = false
                }
            ) {
                Text("Confirm")
            }
        },
        dismissButton = {
            TextButton(
                onClick = {
                    // Cancellation action
                    showDialog = false
                }
            ) {
                Text("Cancel")
            }
        }
    )
}
// Button to show the dialog
Button(onClick = { showDialog = true }) {
    Text("Show Dialog")
}

The result is a functional and stylish dialog according to Material Design 3 principles.

Dialog with a List of Options

To display a list of selectable items, you can place a LazyColumn inside the dialog's content.

val items = listOf("Option 1", "Option 2", "Option 3")
AlertDialog(
    onDismissRequest = { /* ... */ },
    title = { Text("Select an option") },
    text = {
        LazyColumn {
            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable {
                            // Action when an item is selected
                            println("$item selected")
                        }
                        .padding(vertical = 12.dp)
                )
            }
        }
    },
    confirmButton = { /* ... */ }
)

Fully Customized Dialog

If you need full control over the design, you can use the Dialog composable and place any Compose content inside it.

Dialog(onDismissRequest = { /* ... */ }) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(16.dp),
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Custom Dialog", style = MaterialTheme.typography.titleLarge)
            // You can add any composable here: sliders, checkboxes, etc.
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = { /* ... */ }) {
                Text("Close")
            }
        }
    }
}

Example with an Interactive List

Here we have another slightly more interesting exercise.

Again we use reactive state, but now with a list. We declare a simple list and display it inside a dialog.

Here a new component appears: LazyColumn.

This is the equivalent of the old XML's ListView or RecyclerView, or Flutter's ListView.builder. It is used to display lists efficiently.

Each list item is drawn individually, and we can make them clickable. When the user clicks, we print a message to the console.

If you look at the terminal (Logcat), you can see which item was selected.

@Composable
fun ExampleDialogListCompose() {
    // 1. Define the state (is the dialog shown?)
    var showDialog by remember { mutableStateOf(false) }
    val items = listOf("Option 1", "Option 2", "Option 3")
    // Button that activates the dialog
    Button(onClick = { showDialog = true }) {
        Text("Delete item")
    }
    // 2. Declare the Dialog
    if (showDialog) {
        AlertDialog(
            onDismissRequest = {
                // Executes when the user touches outside the dialog or presses "Back"
                showDialog = false
            },
            title = {
                Text(text = "Confirm deletion")
            },
            text = {
                LazyColumn {
                    items(items) { item ->
                        Text(
                            text = item,
                            modifier = Modifier
                                .fillMaxWidth()
                                .clickable {
                                    // Action when an item is selected
                                    println("$item selected")
                                }
                                .padding(vertical = 12.dp)
                        )
                    }
                }
            },
            confirmButton = {
                TextButton(onClick = {
                    showDialog = false
                    // Your deletion logic would go here
                }) {
                    Text("Confirm")
                }
            },
            dismissButton = {
                TextButton(onClick = { showDialog = false }) {
                    Text("Cancel")
                }
            }
        )
    }
}

Dialogs with XML and Kotlin (Legacy Approach)

Creating a Basic Dialog

The first thing we do is create a constant, since it will not change. Here it is important to always analyze what you are creating:

Will it change at some point? If you're not sure, it's okay, you declare it as var and then change it if necessary.

So we create an AlertDialog.Builder. This builder is what allows us to construct the dialog.

AlertDialog.Builder = AlertDialog.Builder(context)

Here an important concept appears: the context.

The context is basically a reference to what you are seeing, something similar to this in classes. In this case, the dialog will live inside MainActivity, so that is the context we pass to it. This gives it the necessary internal information to be able to be displayed in this activity.

val context = LocalContext.current // <-- This gets the current context

Then we set the title and the message. There are many more methods you can use, but this is the minimum example.

Finally, we call create() to create the dialog and, at some point in the business logic (for example, when the user clicks a button), we show it.

val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder
   .setMessage("I am the message")
   .setTitle("I am the title")
   .setPositiveButton("Positive") { dialog, which ->
       // Do something.
   }
   .setNegativeButton("Negative") { dialog, which ->
       // Do something else.
   }
val dialog: AlertDialog = builder.create()
dialog.show()

Dialogs with XML and Kotlin (Legacy Approach)

Although Jetpack Compose is the future, many applications still use the view system. For this approach, the recommended class is MaterialAlertDialogBuilder from the Material Components library, which provides dialogs with the Material Design style.

Make sure you have the Material Components dependency in your build.gradle.kts file:

implementation("com.google.android.material:material:1.12.0")

Simple Message Dialog

The most basic dialog shows a title, a message, and an action button. With Kotlin, the code is very concise.

MaterialAlertDialogBuilder(this)
    .setTitle("Hello world: Title")
    .setMessage("Hello world: Message.")
    .setPositiveButton("OK") { dialog, which ->
        // Action when OK is pressed
    }
    .show()
Simple message dialog in Android

Confirmation Dialog

For a yes/no dialog, simply add a negative button with setNegativeButton.

MaterialAlertDialogBuilder(this)
    .setTitle("Confirmation")
    .setMessage("Are you sure you want to perform this action?")
    .setNegativeButton("Cancel") { dialog, which ->
        dialog.dismiss() // Closes the dialog
    }
    .setPositiveButton("Accept") { dialog, which ->
        // Confirmation action
    }
    .show()
Confirmation dialog in Android

The next step is to learn how to create custom Card Views in Android Studio.

I agree to receive announcements of interest about this Blog.

The dialogs (dialogs) in Android are nothing more than a small customizable window through styles and layouts and in the Android SDK it has built-in classes; in this entry we will see the different types of dialogs in Android.

| 👤 Andrés Cruz

🇪🇸 En español