Los diálogos (dialogs) en Android Studio

Video thumbnail

Los diálogos (dialogs) en Android son pequeñas ventanas que aparecen sobre el contenido principal para solicitar al usuario que tome una decisión o ingrese información adicional. Son un componente fundamental para la interacción con el usuario y se pueden personalizar completamente.

Siguiendo con el conocimiento de componentes, el siguiente que vamos a ver son los diálogos.

Estas son esas bonitas ventanitas que podemos utilizar para múltiples operaciones: mostrar información, confirmar o cancelar acciones, o configurarlas como tú quieras. Es un elemento bastante abierto y flexible.

Actualmente, existen dos enfoques principales para crear diálogos en Android: el moderno y recomendado Jetpack Compose, y el sistema de vistas tradicional basado en XML, utilizando la biblioteca de Material Components.

En esta entrada, exploraremos ambos métodos, comenzando por el enfoque moderno con Jetpack Compose. Quedamos anteriormente en que aprendimos a implementar FloatingActionButton en Android studio + Variantes

Diálogos con Jetpack Compose (El enfoque moderno)

Jetpack Compose es el conjunto de herramientas de UI declarativo y moderno de Google. Crear diálogos con Compose es más simple e intuitivo. El componente principal es el composable AlertDialog.

Manejo del estado del diálogo

Este componente ya es un poco más complejo que un botón. A diferencia de los botones, que simplemente los colocamos y ya, aquí necesitamos activar y desactivar el diálogo.

Para eso vamos a manejar un estado.

Creamos una variable llamada showDialog. Es una variable (var) porque su valor va a cambiar. No podemos usar una constante:

var showDialog by remember { mutableStateOf(false) }

Aquí aparece algo nuevo: remember y mutableStateOf.

Si vienes de Flutter, de Vue, o de cualquier framework reactivo, esto te va a resultar familiar. Esto es básicamente la respuesta de Compose al manejo de estados reactivos.

Si declaráramos una variable normal, como var showDialog = false, y luego la cambiamos al hacer clic en un botón, la interfaz no se actualizaría automáticamente. Para que eso pase de forma “mágica”, necesitamos un estado.

Con mutableStateOf le estamos diciendo a Compose: oye, esta variable es reactiva. Cuando su valor cambie, Compose va a volver a dibujar la interfaz donde se esté utilizando.

Mostrando el diálogo

Usamos un condicional:

Si showDialog es true, mostramos el diálogo.

Aquí tenemos onDismissRequest, que se ejecuta cuando el usuario toca fuera del diálogo o presiona atrás. Normalmente, lo que hacemos es cerrar el diálogo, es decir, volver a poner showDialog en false.

Dentro del diálogo definimos:

  • El título
  • El texto
  • El botón de confirmación
  • El botón de cancelación (dismissButton)

En ambos botones cerramos el diálogo, aunque la lógica real dependerá de lo que quieras hacer (borrar algo, confirmar una acción, 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 EjemploDialogoCompose() {
        // 1. Definimos el estado (¿se muestra el diálogo?)
        var showDialog by remember { mutableStateOf(false) }

        // Botón que activa el diálogo
        Button(onClick = { showDialog = true }) {
            Text("Eliminar elemento")
        }

        // 2. Declaramos el Diálogo
        if (showDialog) {
            AlertDialog(
                onDismissRequest = {
                    // Se ejecuta cuando el usuario toca fuera del diálogo o pulsa "Atrás"
                    showDialog = false
                },
                title = {
                    Text(text = "Confirmar eliminación")
                },
                text = {
                    Text(text = "¿Estás seguro de que deseas borrar este archivo permanentemente?")
                },
                confirmButton = {
                    TextButton(onClick = {
                        showDialog = false
                        // Aquí iría tu lógica de borrado
                    }) {
                        Text("Confirmar")
                    }
                },
                dismissButton = {
                    TextButton(onClick = { showDialog = false }) {
                        Text("Cancelar")
                    }
                }
            )
        }
    }
}    

@Preview(showBackground = true)
@Composable
fun Preview() {
    MyProyectAndroidTheme {
        EjemploDialogoCompose()
    }
}

¿Qué es remember y mutableStateOf?

En resumen:

  • remember permite que el estado se mantenga entre recomposiciones.
  • mutableStateOf crea un estado observable.

Cuando cambiamos el valor de showDialog, Compose detecta el cambio, recompone el componente y actualiza la interfaz.
Si el valor pasa a true, el diálogo aparece. Si vuelve a false, desaparece.

Compose puede redibujar la pantalla muchas veces, y eso es totalmente normal. Lo importante no es el valor en sí, sino que esté envuelto en un estado reactivo.

Preview vs Emulador

El Preview solo sirve para ver una maqueta de la interfaz. No tiene funcionalidad real.

Para ver el comportamiento del diálogo, necesitamos ejecutar la app en un emulador o dispositivo real.

Le damos al botón de Play, seleccionamos el emulador (si tienes varios) y dejamos que arranque.

Luego, en la MainActivity, eliminamos o comentamos lo que no necesitamos y llamamos al composable del diálogo que creamos:

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

Cada vez que hagamos un cambio importante, la aplicación se recompila. Una vez cargada, ya podemos ver el resultado real.

Diálogo de confirmación simple

Para un diálogo con un título, un mensaje y botones de confirmación y cancelación, puedes usar AlertDialog directamente. Se controla su visibilidad a través de un estado.

var showDialog by remember { mutableStateOf(false) }

if (showDialog) {
    AlertDialog(
        onDismissRequest = {
            // Se ejecuta cuando el usuario toca fuera del diálogo o el botón de atrás
            showDialog = false
        },
        title = { Text("Título del Diálogo") },
        text = { Text("Este es un mensaje de ejemplo para el diálogo. ¿Estás seguro de continuar?") },
        confirmButton = {
            TextButton(
                onClick = {
                    // Acción de confirmación
                    showDialog = false
                }
            ) {
                Text("Confirmar")
            }
        },
        dismissButton = {
            TextButton(
                onClick = {
                    // Acción de cancelación
                    showDialog = false
                }
            ) {
                Text("Cancelar")
            }
        }
    )
}

// Botón para mostrar el diálogo
Button(onClick = { showDialog = true }) {
    Text("Mostrar Diálogo")
}

El resultado es un diálogo funcional y estilizado según los principios de Material Design 3.

Diálogo con una lista de opciones

Para mostrar una lista de elementos seleccionables, puedes colocar un LazyColumn dentro del contenido del diálogo.

val items = listOf("Opción 1", "Opción 2", "Opción 3")

AlertDialog(
    onDismissRequest = { /* ... */ },
    title = { Text("Selecciona una opción") },
    text = {
        LazyColumn {
            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable {
                            // Acción al seleccionar un item
                            println("$item seleccionado")
                        }
                        .padding(vertical = 12.dp)
                )
            }
        }
    },
    confirmButton = { /* ... */ }
)

Diálogo completamente personalizado

Si necesitas un control total sobre el diseño, puedes usar el composable Dialog y colocar cualquier contenido de Compose en su interior.

Dialog(onDismissRequest = { /* ... */ }) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(16.dp),
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Diálogo Personalizado", style = MaterialTheme.typography.titleLarge)
            // Aquí puedes agregar cualquier composable: sliders, checkboxes, etc.
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = { /* ... */ }) {
                Text("Cerrar")
            }
        }
    }
}

Ejemplo con lista interactiva

Aquí tenemos otro ejercicio un poco más interesante.

De nuevo usamos estado reactivo, pero ahora con una lista. Declaramos una lista simple y la mostramos dentro de un diálogo.

Aquí aparece un componente nuevo: LazyColumn.

Este es el equivalente al ListView o RecyclerView del XML antiguo, o al ListView.builder de Flutter. Sirve para mostrar listados de forma eficiente.

Cada elemento de la lista se dibuja de manera individual y podemos hacerlos clickeables. Cuando el usuario hace clic, imprimimos un mensaje en la consola.

Si miras la terminal (Logcat), puedes ver qué elemento fue seleccionado.

@Composable
fun EjemploDialogoListCompose() {
    // 1. Definimos el estado (¿se muestra el diálogo?)
    var showDialog by remember { mutableStateOf(false) }

    val items = listOf("Opción 1", "Opción 2", "Opción 3")

    // Botón que activa el diálogo
    Button(onClick = { showDialog = true }) {
        Text("Eliminar elemento")
    }

    // 2. Declaramos el Diálogo
    if (showDialog) {
        AlertDialog(
            onDismissRequest = {
                // Se ejecuta cuando el usuario toca fuera del diálogo o pulsa "Atrás"
                showDialog = false
            },
            title = {
                Text(text = "Confirmar eliminación")
            },
            text = {
                LazyColumn {
                    items(items) { item ->
                        Text(
                            text = item,
                            modifier = Modifier
                                .fillMaxWidth()
                                .clickable {
                                    // Acción al seleccionar un item
                                    println("$item seleccionado")
                                }
                                .padding(vertical = 12.dp)
                        )
                    }
                }
            },
            confirmButton = {
                TextButton(onClick = {
                    showDialog = false
                    // Aquí iría tu lógica de borrado
                }) {
                    Text("Confirmar")
                }
            },
            dismissButton = {
                TextButton(onClick = { showDialog = false }) {
                    Text("Cancelar")
                }
            }
        )
    }
}

Diálogos con XML y Kotlin (Enfoque Legacy)

Creando un diálogo básico

Lo primero que hacemos es crear una constante, ya que no va a cambiar. Aquí es importante analizar siempre lo que estás creando:

¿Va a cambiar en algún punto? Si no estás seguro, no pasa nada, lo declaras como var y luego lo cambias si hace falta.

Creamos entonces un AlertDialog.Builder. Este builder es el que nos permite construir el diálogo.

AlertDialog.Builder = AlertDialog.Builder(context)

Aquí aparece un concepto importante: el contexto.

El contexto es básicamente una referencia a lo que estás viendo, algo similar a un this en las clases. En este caso, el diálogo va a vivir dentro de MainActivity, por lo tanto ese es el contexto que le pasamos. Esto le da la información interna necesaria para poder mostrarse en esta actividad.

val context = LocalContext.current // <-- Esto obtiene el contexto actual

Luego establecemos el título y el mensaje. Hay muchos más métodos que puedes usar, pero este es el ejemplo mínimo.

Finalmente llamamos a create() para crear el diálogo y, en algún punto de la lógica de negocio (por ejemplo, cuando el usuario hace clic en un botón), lo mostramos.

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()

Diálogos con XML y Kotlin (Enfoque Legacy)

Aunque Jetpack Compose es el futuro, muchas aplicaciones aún utilizan el sistema de vistas. Para este enfoque, la clase recomendada es MaterialAlertDialogBuilder de la biblioteca de Material Components, que proporciona diálogos con el estilo de Material Design.

Asegúrate de tener la dependencia de Material Components en tu archivo build.gradle.kts:

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

Diálogo simple de mensaje

El diálogo más básico muestra un título, un mensaje y un botón de acción. Con Kotlin, el código es muy conciso.

MaterialAlertDialogBuilder(this)
    .setTitle("Hola mundo: Título")
    .setMessage("Hola mundo: Mensaje.")
    .setPositiveButton("OK") { dialog, which ->
        // Acción al presionar OK
    }
    .show()
Diálogo simple de mensaje en Android

Diálogo de confirmación

Para un diálogo de sí/no, simplemente añade un botón negativo con setNegativeButton.

MaterialAlertDialogBuilder(this)
    .setTitle("Confirmación")
    .setMessage("¿Estás seguro de que deseas realizar esta acción?")
    .setNegativeButton("Cancelar") { dialog, which ->
        dialog.dismiss() // Cierra el diálogo
    }
    .setPositiveButton("Aceptar") { dialog, which ->
        // Acción de confirmación
    }
    .show()
Diálogo de confirmación en Android

Diálogo de selección simple (lista)

Para mostrar una lista de opciones donde el usuario puede elegir una, usa el método setItems.

val items = arrayOf("Opción 1", "Opción 2", "Opción 3")

MaterialAlertDialogBuilder(this)
    .setTitle("Elige una opción")
    .setItems(items) { dialog, which ->
        Log.i("Dialog", "Opción seleccionada: ${items[which]}")
    }
    .show()
Diálogos de selección simple en Android

Diálogo de selección simple con radio-buttons

Una variante del anterior que mantiene la selección visible.

val items = arrayOf("Opción 1", "Opción 2", "Opción 3")
var checkedItem = 0 // Opción seleccionada por defecto

MaterialAlertDialogBuilder(this)
    .setTitle("Elige una opción")
    .setSingleChoiceItems(items, checkedItem) { dialog, which ->
        checkedItem = which
    }
    .setPositiveButton("OK") { dialog, which ->
        Log.i("Dialog", "Opción final: ${items[checkedItem]}")
    }
    .show()
Diálogos de selección simple con radio en Android

Diálogo de selección múltiple (checkboxes)

Para permitir que el usuario seleccione varias opciones, usa setMultiChoiceItems.

val items = arrayOf("Opción 1", "Opción 2", "Opción 3")
val checkedItems = booleanArrayOf(true, false, false) // Selecciones iniciales

MaterialAlertDialogBuilder(this)
    .setTitle("Elige tus preferencias")
    .setMultiChoiceItems(items, checkedItems) { dialog, which, isChecked ->
        Log.i("Dialog", "Opción ${items[which]} marcada: $isChecked")
    }
    .setPositiveButton("OK") { dialog, which ->
        // Acceder a `checkedItems` para ver las selecciones finales
    }
    .show()
Diálogos de selección múltiple en Android

Diálogo personalizado

Para diseños complejos, puedes inflar un layout XML personalizado usando setView(). Se recomienda usar View Binding para acceder a las vistas del layout de forma segura.

// 1. Inflar el layout personalizado con View Binding
val binding = DialogCustomLayoutBinding.inflate(layoutInflater)

// 2. Configurar el diálogo
MaterialAlertDialogBuilder(this)
    .setTitle("Formulario Personalizado")
    .setView(binding.root)
    .setPositiveButton("Enviar") { dialog, which ->
        val userInput = binding.editText.text.toString()
        // Hacer algo con los datos
    }
    .setNegativeButton("Cancelar", null)
    .show()
Diálogo personalizados en Android

El siguiente paso, aprende a cómo crear Card Views personalizados en Android Studio.

Acepto recibir anuncios de interes sobre este Blog.

Los diálogos (dialogs) en Android no son más que una pequeña ventana personalizables a través de estilos y layouts y en la SDK de Android cuenta con clases incorporadas; en esta entrada veremos los distintos tipos de dialogs en Android.

| 👤 Andrés Cruz

🇺🇸 In english