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:
2. Establece un modelo de referencia
Cómo crear un proyecto
- En Android Studio, haz clic en File > New > New Project.
- En el diálogo New Project, selecciona Empty Activity y haz clic en Next.
- En el campo Name, ingresa
Dice Roller
. - En el campo Minimum SDK, selecciona un nivel mínimo de API de 24 (Nougat) del menú y, luego, haz clic en Finish.
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.
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.
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:
- Quita la función
GreetingPreview()
. - 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.
- Quita la función
Greeting(name: String, modifier: Modifier = Modifier)
. - 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.
- Borra todo el código dentro de la lambda
setContent{}
que se encuentra en el métodoonCreate()
. - En el cuerpo de lambda
setContent{}
, llama a la lambdaDiceRollerTheme{}
y, dentro de la lambdaDiceRollerTheme{}
, llama a la funciónDiceRollerApp()
.
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DiceRollerTheme {
DiceRollerApp()
}
}
}
- En la función
DiceRollerApp()
, llama a la funciónDiceWithButtonAndImage()
.
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:
- Modifica la función
DiceWithButtonAndImage()
para que acepte un argumentomodifier
de tipoModifier
y asígnale un valor predeterminado deModifier
.
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í.
- 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ónDiceWithButtonAndImage()
cambió, se debe pasar un objetoModifier
con las decoraciones deseadas cuando se lo llame. La claseModifier
es responsable de la decoración, la adición o el comportamiento de un elemento componible en la funciónDiceRollerApp()
. En este caso, hay algunas decoraciones importantes para agregar al objetoModifier
que se pasa a la funciónDiceWithButtonAndImage()
.
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)
- Encadena un método
fillMaxSize()
al objetoModifier
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()
)
- Encadena el método
wrapContentSize()
al objetoModifier
y, luego, pasaAlignment.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:
Para crear un diseño vertical, haz lo siguiente:
- En la función
DiceWithButtonAndImage()
, agrega una funciónColumn()
.
- Pasa el argumento
modifier
de la firma del métodoDiceWithButtonAndImage()
al argumento modificador deColumn()
.
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
.
- Pasa un argumento
horizontalAlignment
a la funciónColumn()
y, luego, configúralo en un valor deAlignment.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
- En el archivo
strings.xml
, agrega una string y establécela en un valorRoll
.
res/values/strings.xml
<string name="roll">Roll</string>
- En el cuerpo de lambda de
Column()
, agrega una funciónButton()
.
- En el archivo
MainActivity.kt
, agrega una funciónText()
aButton()
en el cuerpo de la lambda de la función. - Pasa el ID del recurso de cadenas de la cadena
roll
a la funciónstringResource()
y, luego, pasa el resultado al elementoText
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
- 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.
- 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
- En Android Studio, haz clic en View > Tool Windows > Resource Manager.
- Haz clic en + > Import Drawables para abrir un navegador de archivos.
- Busca y selecciona la carpeta de seis imágenes de dados. Luego, súbelas.
Las imágenes subidas aparecerán de la siguiente manera.
- Haz clic en Siguiente.
Aparecerá el diálogo Import drawables y mostrará dónde van los archivos de recursos en la estructura de archivos.
- Haz clic en Import para confirmar que deseas importar las seis imágenes.
Las imágenes deberían aparecer en el panel Resource Manager.
¡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 componibles de manera vertical. Por lo tanto, el elemento que se declare primero dentro de la función Column()
se mostrará antes que el elemento declarado posteriormente en la misma función Column()
.
Para agregar un elemento Image
componible, haz lo siguiente:
- En el cuerpo de la función
Column()
, crea una funciónImage()
antes de la funciónButton()
.
MainActivity.kt
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image()
Button(onClick = { /*TODO*/ }) {
Text(stringResource(R.string.roll))
}
}
- Pasa un argumento
painter
a la funciónImage()
y asígnale un valorpainterResource
que acepte un argumento de ID de recurso de elementos de diseño. Por ahora, pasa el siguiente ID de recurso: argumentoR.drawable.dice_1
.
MainActivity.kt
Image(
painter = painterResource(R.drawable.dice_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í.
- Para solucionar ese problema, agrega un elemento
Spacer
componible entre los elementosImage
yButton
componibles. Un objetoSpacer
toma un elementoModifier
como parámetro. En este caso, laImage
está por encima delButton
, por lo que debe haber un espacio vertical entre ellos. Por lo tanto, se puede establecer la altura deModifier
para que se aplique aSpacer
. Intenta establecer la altura en16.dp
. Por lo general, las dimensiones de dp se cambian en incrementos de4.dp
.
MainActivity.kt
Spacer(modifier = Modifier.height(16.dp))
- En el panel Preview, haz clic en Build & Refresh.
Deberías ver algo similar a esta imagen:
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
- En la función
DiceWithButtonAndImage()
antes de la funciónColumn()
, crea una variableresult
y establécela en un valor1
. - Observa el elemento
Button
de componibilidad. Notarás que se le pasa un parámetroonClick
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.
- En la función
Button()
, quita el comentario/*TODO*/
del valor del cuerpo de lambda del parámetroonClick
. - 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 lambdaonClick
, establece la variableresult
en un rango entre 1 y 6. Luego, llama al métodorandom()
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(R.drawable.dice_1),
contentDescription = "1"
)
Spacer(modifier = Modifier.height(16.dp))
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
.
- Haz que la variable
result
sea un elementoremember
de componibilidad.
El elemento remember
componible requiere que se pase una función.
- En el cuerpo de componibilidad
remember
, pasa una funciónmutableStateOf()
y, luego, pasa la función a un argumento1
.
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.
- Debajo de la creación de instancias de la variable
result
, crea una variableimageResource
inmutable establecida en una expresiónwhen
que acepte una variableresult
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
}
- Cambia el ID que se pasa al parámetro
painterResource
del elemento de componibilidadImage
desde elemento de diseñoR.drawable.dice_1
a la variableimageResource
. - Cambia el parámetro
contentDescription
del elementoImage
componible para reflejar el valor de la variableresult
. Para ello, convierte la variableresult
en una cadena contoString()
y pásala comocontentDescription
.
MainActivity.kt
Image(
painter = painterResource(imageResource),
contentDescription = result.toString()
)
- Ejecuta tu app.
Ahora la app de Dice Roller debería funcionar por completo.
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.
Si deseas ver el código de la solución, puedes hacerlo en GitHub.
- Navega a la página de repositorio de GitHub del proyecto.
- 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.
- En la página de GitHub de este proyecto, haz clic en el botón Code, el cual abre una ventana emergente.
- 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.
- Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
- 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
- Inicia Android Studio.
- En la ventana Welcome to Android Studio, haz clic en Open.
Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.
- En el navegador de archivos, ve hasta donde se encuentra la carpeta del proyecto descomprimida (probablemente en Descargas).
- Haz doble clic en la carpeta del proyecto.
- Espera a que Android Studio abra el proyecto.
- Haz clic en el botón Run 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.