Cómo crear una app interactiva de Dice Roller

1. Antes de comenzar

En este codelab, crearás una app interactiva de Dice Roller que les permite a los usuarios presionar un elemento Button componible para lanzar un dado. El resultado del lanzamiento se muestra con un elemento Image componible en la pantalla.

Usa Jetpack Compose con Kotlin para compilar el diseño de tu app y, luego, escribe la lógica empresarial para controlar lo que sucede cuando se presiona el elemento Button componible.

Requisitos previos

  • Poder crear y ejecutar una app básica de Compose en Android Studio
  • Conocer el uso del elemento Text componible en una app
  • Poder extraer texto y convertirlo en un recurso de strings para facilitar la traducción de tu app y reutilizar las strings
  • Conocer los conceptos básicos de programación de Kotlin

Qué aprenderás

  • Cómo agregar un elemento Button componible a una app para Android con Compose
  • Cómo agregar un comportamiento a un elemento Button componible en una app para Android con Compose
  • Cómo abrir y modificar el código Activity de una app para Android

Qué compilarás

  • Una app interactiva para Android llamada Dice Roller que permite a los usuarios lanzar un dado y mostrarles el resultado.

Requisitos

  • Una computadora que tenga Android Studio instalado

Cuando completes este codelab, la app se verá de la siguiente manera:

524ad07a9b61f729.png

2. Establece un modelo de referencia

Cómo crear un proyecto

  1. En Android Studio, haz clic en File > New > New Project.
  2. En el diálogo New Project, selecciona Empty Activity y haz clic en Next.

Aparecerá un diálogo con una lista de plantillas de proyectos de Android. En cada plantilla, se muestra una imagen de la plantilla base seguida del nombre de la plantilla.

  1. En el campo Name, ingresa Dice Roller.
  2. En el campo Minimum SDK, selecciona un nivel mínimo de API de 24 (Nougat) del menú y, luego, haz clic en Finish.

f59332f7db364338.png

3. Crea la infraestructura de diseño

Cómo obtener una vista previa del proyecto

Para obtener una vista previa del proyecto, sigue estos pasos:

  • Haz clic en Build & Refresh en el panel Split o Design.

c367df1b2c82b224.png

Ahora deberías ver una vista previa en el panel Design. Si se ve pequeño, no te preocupes porque cambia cuando modificas el diseño.

c968f0707e081b8f.png

Cómo reestructurar el código de muestra

Debes cambiar parte del código generado para que se parezca más al tema de una app de lanzamiento de dados.

Como pudiste ver en la captura de pantalla de la app final, hay una imagen de un dado y un botón para lanzarlo. Asignarás una estructura a las funciones de componibilidad para reflejar esta arquitectura.

Para reestructurar el código de muestra, sigue estos pasos:

  1. Quita la función DefaultPreview().
  2. Crea una función DiceWithButtonAndImage() con la anotación @Composable.

Esta función de componibilidad representa los componentes de la IU del diseño y también contiene la lógica de visualización de imágenes y la de botón de clic.

  1. Quita la función Greeting(name: String).
  2. Crea una función DiceRollerApp() con las anotaciones @Preview y @Composable.

Dado que esta app solo consiste en un botón y una imagen, piensa en esta función de componibilidad como la app en sí. Por eso se llama función DiceRollerApp().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {

}

@Composable
fun DiceWithButtonAndImage() {

}

Debido a que quitaste la función Greeting(), la llamada a Greeting("Android") en el cuerpo de lambda DiceRollerTheme() se destaca en rojo. Esto se debe a que el compilador ya no puede encontrar una referencia a esa función.

  1. Borra todo el código dentro de la lambda setContent{} que se encuentra en el método onCreate().
  2. En el cuerpo de lambda setContent{}, llama a la lambda DiceRollerTheme{} y, dentro de la lambda DiceRollerTheme{}, llama a la función DiceRollerApp().

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
  1. En la función DiceRollerApp(), llama a la función DiceWithButtonAndImage().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}

Cómo agregar un modificador

Compose usa un objeto Modifier, que es una colección de elementos que decoran o modifican el comportamiento de los elementos de la IU de Compose. Lo usarás para diseñar los componentes de la IU de los componentes de la app de Dice Roller.

Para agregar un modificador, haz lo siguiente:

  1. Modifica la función DiceWithButtonAndImage() para que acepte un argumento modifier de tipo Modifier y asígnale un valor predeterminado de Modifier.

MainActivity.kt

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}

Es posible que el fragmento de código anterior te resulte confuso. Por eso, vamos a desglosarlo. La función permite que se pase un parámetro modifier. El valor predeterminado del parámetro modifier es un objeto Modifier, lo que explica la parte = Modifier de la firma del método. El valor predeterminado de un parámetro permite que cualquier persona que llame a este método en el futuro decida si desea pasar un valor para el parámetro. Si la persona pasa su propio objeto Modifier, puede personalizar el comportamiento y la decoración de la IU. Si decide no pasar un objeto Modifier, se asume el valor predeterminado, que es el objeto Modifier sin formato. Puedes aplicar esta práctica a cualquier parámetro. Para obtener más información sobre los argumentos predeterminados, consulta aquí.

  1. Ahora que el elemento DiceWithButtonAndImage() componible tiene un parámetro modificador, pasa un modificador cuando se lo llame. Como la firma del método para la función DiceWithButtonAndImage() cambió, se debe pasar un objeto Modifier con las decoraciones deseadas cuando se lo llame. La clase Modifier es responsable de la decoración, la adición o el comportamiento de un elemento componible en la función DiceRollerApp(). En este caso, hay algunas decoraciones importantes para agregar al objeto Modifier que se pasa a la función DiceWithButtonAndImage().

Quizás te preguntes por qué deberías preocuparte por pasar un argumento Modifier cuando hay un valor predeterminado. Esto se debe a que los elementos componibles pueden pasar por una recomposición, lo que básicamente significa que el bloque de código del método @Composable se vuelve a ejecutar. Si se crea un objeto Modifier en un bloque de código, es posible que se vuelva a crear, lo que no resulta eficiente. Más adelante en este codelab, trataremos el tema de la recomposición.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier)
  1. Encadena un método fillMaxSize() al objeto Modifier para que el diseño ocupe toda la pantalla.

Este método especifica que los componentes deben llenar el espacio disponible. Anteriormente en este codelab, viste una captura de pantalla de la IU final de la app de Dice Roller. Es importante destacar que el dado y el botón están centrados en la pantalla. El método wrapContentSize() especifica que el espacio disponible debe ser al menos tan grande como los componentes que contiene. Sin embargo, como se usa el método fillMaxSize(), si los componentes dentro del diseño son más pequeños que el espacio disponible, se puede pasar un objeto Alignment al método wrapContentSize() que especifica el modo en que se deben alinear los componentes dentro del espacio disponible.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
  1. Encadena el método wrapContentSize() al objeto Modifier y, luego, pasa Alignment.Center como un argumento para centrar los componentes. Alignment.Center especifica que un componente se centra de forma vertical y horizontal.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)

4. Crea un diseño vertical

En Compose, los diseños verticales se crean con la función Column().

La función Column() es un diseño componible que ubica sus elementos secundarios en una secuencia vertical. En el diseño previsto de la app, puedes ver que la imagen del dado se muestra en vertical sobre el botón de lanzamiento:

524ad07a9b61f729.png

Para crear un diseño vertical, haz lo siguiente:

  1. En la función DiceWithButtonAndImage(), agrega una función Column().
  2. Pasa el argumento modifier de la firma del método DiceWithImageAndButton() al argumento modificador de Column().

El argumento modifier garantiza que los elementos componibles que se encuentran en la función Column() cumplan con las restricciones a las que se llamó en la instancia modifier.

  1. Pasa un argumento horizontalAlignment a la función Column() y, luego, configúralo en un valor de Alignment.CenterHorizontally.

Esto garantiza que los elementos secundarios dentro de la columna estén centrados en la pantalla del dispositivo con respecto al ancho.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

5. Agrega un botón

  1. En el archivo strings.xml, agrega una string y establécela en un valor Roll.

res/values/strings.xml

<string name="roll">Roll</string>
  1. En el cuerpo de lambda de Column(), agrega una función Button().
  1. En el archivo MainActivity.kt, agrega una función Text() a Button() en el cuerpo de la lambda de la función.
  2. Pasa el ID del recurso de strings de la string roll a la función stringResource() y, luego, pasa el resultado al elemento Text que admite composición.

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}

6. Agrega una imagen

Otro componente esencial de la app es la imagen del dado, que muestra el resultado cuando el usuario presiona el botón Roll. La imagen con un elemento componible Image que agregaste requiere un recurso de imagen, por lo que primero debes descargar algunas de las imágenes que se proporcionan para esta app.

Cómo descargar las imágenes de dados

  1. Abre esta URL para descargar en tu computadora un archivo ZIP con imágenes de dados y, luego, espera a que se complete la descarga.

Busca el archivo en tu computadora. Es probable que se encuentre en la carpeta Descargas.

  1. Extrae el archivo ZIP para crear una nueva carpeta dice_images que contenga seis archivos de imagen de dados con valores de dado del 1 al 6.

Cómo agregar imágenes de dados a tu app

  1. En Android Studio, haz clic en View > Tool Windows > Resource Manager.
  2. Haz clic en + > Import Drawables para abrir un navegador de archivos.

En el menú desplegable para agregar recursos de Resource Manager, se muestra la opción para importar elementos de diseño.

  1. Busca y selecciona la carpeta de seis imágenes de dados. Luego, súbelas.

Las imágenes subidas aparecerán de la siguiente manera.

Aparecerá la ventana Import Drawables con los recursos que están listos para importar.

  1. Haz clic en Next.

Aparecerá el diálogo de confirmación de importación y mostrará dónde van los archivos de recursos en la estructura de archivos.

Aparecerá el diálogo Import drawables y mostrará dónde van los archivos de recursos en la estructura de archivos.

  1. Haz clic en Import para confirmar que deseas importar las seis imágenes.

Las imágenes deberían aparecer en el panel Resource Manager.

En el panel Resource Manager, se muestran los recursos contenidos en este proyecto.

¡Buen trabajo! En la próxima tarea, usarás estas imágenes en tu app.

Cómo agregar un elemento Image de componibilidad

La imagen del dado debería aparecer encima del botón Roll. Compose coloca los componentes de la IU de manera inherente de forma secuencial. En otras palabras, el elemento de componibilidad que se declara primero se muestra en primer lugar. Eso podría significar que la primera declaración se muestra arriba, o antes, del elemento de componibilidad que se declara después. Los elementos de componibilidad dentro de un elemento Column aparecerán uno encima o debajo del otro en el dispositivo. En esta app, se usa Column para apilar elementos de componibilidad de manera vertical. Por lo tanto, el elemento que se declare primero adentro de la función Column() se mostrará antes que el elemento declarado posteriormente en la misma función Column().

Para agregar un elemento Image de componibilidad, haz lo siguiente:

  1. En el cuerpo de la función Column(), crea una función Image() antes de la función Button().

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
  1. Pasa un argumento painter a la función Image() y asígnale un valor painterResource que acepte un argumento de ID de recurso de elementos de diseño. Por ahora, pasa el siguiente ID de recurso: argumento R.drawable.dice_1.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. Cada vez que creas una imagen en tu app, debes proporcionar lo que se denomina una "descripción del contenido". Estas son una parte importante del desarrollo de Android. Sirven para adjuntar descripciones a sus respectivos componentes de IU a fin de aumentar la accesibilidad. Para obtener más información sobre las descripciones de contenido, consulta Describe cada elemento de IU. Puedes pasar una descripción de contenido a la imagen como parámetro.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)

Ahora todos los componentes necesarios de la IU están presentes. Sin embargo, Button y Image se superponen entre sí.

92a1023933ee638a.png

  1. Para solucionar ese problema, agrega un elemento Spacer componible entre los elementos componibles Button y Image. Un objeto Spacer toma un elemento Modifier como parámetro. En este caso, la Image está por encima del Button, por lo que debe haber un espacio vertical entre ellos. Por lo tanto, se puede establecer la altura de Modifier para que se aplique a Spacer. Intenta establecer la altura en 16.dp. Por lo general, las dimensiones de dp se cambian en incrementos de 4.dp.

MainActivity.kt

Spacer(modifier = Modifier.height(16.dp))
  1. En el panel Preview, haz clic en Build & Refresh.

Deberías ver algo similar a esta imagen:

d893fc8fccb05813.png

7. Compila la lógica del dado

Ahora que todos los elementos que admiten composición necesarios están presentes, modificas la app para que, cuando se presione el botón, se lance el dado.

Cómo hacer que el botón sea interactivo

  1. En la función DiceWithButtonAndImage() antes de la función Column(), crea una variable result y establécela en un valor 1.
  2. Observa el elemento Button de componibilidad. Notarás que se le pasa un parámetro onClick configurado como un par de llaves con el comentario /*TODO*/ dentro de las llaves. Las llaves, en este caso, representan lo que se conoce como una lambda; el área dentro de las llaves es el cuerpo de lambda. Cuando se pasa una función como argumento, también se la puede denominar "devolución de llamada".

MainActivity.kt

Button(onClick = { /*TODO*/ })

Una lambda es un literal de función, que es como cualquier otra función, pero en lugar de declararse por separado con la palabra clave fun, se escribe intercalada y se pasa como una expresión. El elemento componible Button espera que se pase una función como parámetro onClick. Este es el lugar perfecto para usar una lambda, y escribirás el cuerpo de lambda en esta sección.

  1. En la función Button(), quita el comentario /*TODO*/ del valor del cuerpo de lambda del parámetro onClick.
  2. Un lanzamiento del dado es aleatorio. Para reflejarlo en el código, debes usar la sintaxis correcta a fin de generar un número al azar. En Kotlin, puedes usar el método random() en un rango de números. En el cuerpo de la lambda onClick, establece la variable result en un rango entre 1 y 6. Luego, llama al método random() en ese rango. Recuerda que, en Kotlin, los rangos se designan con dos puntos entre el primer número del rango y el último número del rango.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(painter = painterResource(imageResource), contentDescription = result.toString())
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

Ahora el botón se puede presionar, pero aún no se aplicará ningún cambio visual si lo presionas, ya que es necesario compilar esa funcionalidad.

Cómo agregar un condicional a la app de lanzamiento de dados

En la sección anterior, creaste una variable result y la codificaste en un valor 1. En última instancia, el valor de la variable result se restablece cuando se presiona el botón Roll, y debería determinar qué imagen se muestra.

Los elementos de componibilidad no tienen estado de forma predeterminada, lo que significa que no tienen un valor y el sistema los puede volver a componer en cualquier momento, lo que hace que se restablezca el valor. Sin embargo, Compose proporciona una forma conveniente de evitarlo. Las funciones de componibilidad pueden almacenar un objeto en la memoria con el elemento componible remember.

  1. Haz que la variable result sea un elemento remember de componibilidad.

El elemento remember de componibilidad requiere que se pase una función.

  1. En el cuerpo de componibilidad remember, pasa una función mutableStateOf() y, luego, pasa la función a un argumento 1.

La función mutableStateOf() muestra un elemento observable. Más adelante, aprenderás más sobre los elementos observables, pero, por ahora, esto significa que cuando cambia el valor de la variable result, se activa una recomposición, se refleja el valor del resultado y se actualiza la IU.

MainActivity.kt

var result by remember { mutableStateOf(1) }

Ahora, cuando se presiona el botón, se actualiza la variable result con un valor del número al azar.

En este momento, se puede usar la variable result para determinar qué imagen mostrar.

  1. Debajo de la creación de instancias de la variable result, crea una variable imageResource inmutable establecida en una expresión when que acepte una variable result y, luego, establece cada resultado posible en su elemento de diseño.

MainActivity.kt

val imageResource = when (result) {
    1 -> R.drawable.dice_1
    2 -> R.drawable.dice_2
    3 -> R.drawable.dice_3
    4 -> R.drawable.dice_4
    5 -> R.drawable.dice_5
    else -> R.drawable.dice_6
}
  1. Cambia el ID que se pasa al parámetro painterResource del elemento de componibilidad Image desde elemento de diseño R.drawable.dice_1 a la variable imageResource.
  2. Cambia el parámetro contentDescription del elemento Image componible para reflejar el valor de la variable result. Para ello, convierte la variable result en una cadena con toString() y pásala como contentDescription.

MainActivity.kt

Image(painter = painterResource(id = imageResource), contentDescription = result.toString())
  1. Ejecuta tu app.

Ahora la app de Dice Roller debería funcionar por completo.

524ad07a9b61f729.png

8. Obtén el código de la solución

Para descargar el código del codelab terminado, puedes usar este comando de git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git

También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.

Descargar ZIP

Si deseas ver el código de la solución, puedes hacerlo en GitHub.

  1. Navega a la página de repositorio de GitHub del proyecto.
  2. Verifica que el nombre de la rama coincida con el especificado en el codelab. Por ejemplo, en la siguiente captura de pantalla, el nombre de la rama es main.

2301510b78db9764.png

  1. En la página de GitHub de este proyecto, haz clic en el botón Code, que abre una ventana emergente.

5844a1bc8ad88ce1.png

  1. En la ventana emergente, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open.

4711318ba1db18a2.png

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.

e400aad673cc7e28.png

  1. En el navegador de archivos, ve hasta donde se encuentra la carpeta del proyecto descomprimida (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run 1b472ca0dcd0297b.png para compilar y ejecutar la app. Asegúrate de que funcione como se espera.

9. Conclusión

Creaste una app interactiva de Dice Roller para Android con Compose.

Resumen

  • Define funciones de componibilidad.
  • Crea diseños con composiciones.
  • Crea un botón con el elemento Button componible.
  • Importa recursos drawable.
  • Muestra una imagen con el elemento Image componible.
  • Crea una IU interactiva con elementos de componibilidad
  • Usa el elemento remember componible para almacenar objetos en una composición en la memoria.
  • Actualiza la IU con la función mutableStateOf() para convertirla en un elemento observable.

Más información