1. Antes de comenzar
En esta ruta de aprendizaje, aprendiste a agregar un botón a una app y a modificar la app para que responda a un clic en un botón. Ahora, es momento de compilar una app para practicar lo que aprendiste.
Crearás una app llamada Lemonade. Primero, consulta los requisitos de la app de Lemonade para comprender el aspecto y el comportamiento que debería tener. Si quieres desafiarte, puedes compilar la app por tu cuenta. Si encuentras algún problema, lee las secciones posteriores para recibir más sugerencias y orientación sobre cómo solucionarlo y abordarlo paso a paso.
Resuelve este problema práctico a un ritmo que te resulte cómodo. Dedica el tiempo que necesites para compilar cada parte de la funcionalidad de la app. El código de solución para la app de Lemonade está disponible al final, pero recomendamos que intentes compilar la app por tu cuenta antes de consultar la solución. Recuerda que la solución proporcionada no es la única forma de compilar la app de Lemonade, por lo que es completamente válido compilarla de una manera diferente, siempre y cuando se cumplan los requisitos de la app.
Requisitos previos
- Poder crear un diseño simple de la IU en Compose con elementos componibles de imagen y texto
- Poder compilar una app interactiva que responda a un clic en un botón
- Tener conocimientos básicos de composición y recomposición
- Conocer los conceptos básicos del lenguaje de programación Kotlin, incluidas funciones, variables, condicionales y lambdas
Requisitos
- Una computadora con acceso a Internet y Android Studio instalado
2. Descripción general de la app
Nos ayudarás a hacer realidad nuestra idea de crear limonada digital. El objetivo es crear una app interactiva y sencilla que te permita exprimir limones cuando presiones la imagen en pantalla hasta beber un vaso de limonada. Puedes considerarla una metáfora o tal vez solo una manera divertida de pasar el tiempo.
La app funciona de la siguiente manera:
- Cuando el usuario inicia la app por primera vez, ve un limonero. Una etiqueta le indica que presione la imagen del limonero para "seleccionar" un limón.
- Después de que presiona el limonero, el usuario ve un limón. Se le solicita que presione el limón para "exprimirlo" y preparar la limonada. Debe presionar el limón varias veces para exprimirlo. La cantidad de presiones necesarias para exprimir el limón es diferente cada vez y es un número entre 2 y 4 (inclusive) generado de forma aleatoria.
- Después de presionar el limón la cantidad requerida de veces, verá un refrescante vaso de limonada. Se le solicita que presione el vaso para "beber" la limonada.
- Después de presionar el vaso de limonada, ve un vaso vacío. Se le pide que presione el vaso vacío para volver a empezar.
- Después de presionar el vaso vacío, ve el limonero y puede volver a comenzar el proceso. ¡Otra limonada, por favor!
A continuación, se muestran capturas de pantalla más grandes del aspecto de la app:
Para cada paso de la preparación de limonada, hay una imagen y una etiqueta de texto diferentes en la pantalla, y diferentes comportamientos para la respuesta de la app ante un clic. Por ejemplo, cuando el usuario presiona el limonero, la app muestra un limón.
Tu trabajo es crear el diseño de la IU de la app e implementar la lógica para que el usuario recorra todos los pasos necesarios para preparar la limonada.
3. Cómo comenzar
Crea un proyecto
En Android Studio, crea un proyecto nuevo con la plantilla Empty Activity con los siguientes detalles:
- Nombre: Lemonade
- Nombre del paquete: com.example.lemonade
- SDK mínimo: 24
Una vez que se haya creado correctamente la app y se haya compilado el proyecto, continúa con la siguiente sección.
Agrega imágenes
Se te proporcionan cuatro archivos de elementos de diseño vectorial que puedes usar en la app de Lemonade.
Obtén los archivos:
- Descarga un archivo ZIP de las imágenes para la app.
- Haz doble clic en el archivo ZIP. En este paso, se descomprimen las imágenes en una carpeta.
- Agrega las imágenes a la carpeta
drawable
de tu app. Si no recuerdas cómo hacerlo, consulta el codelab Cómo crear una app interactiva de Dice Roller.
La carpeta de tu proyecto debería verse como la siguiente captura de pantalla en la que ahora aparecen los elementos lemon_drink.xml
, lemon_restart.xml
, lemon_squeeze.xml
y lemon_tree.xml
en el directorio res > drawable:
- Haz doble clic en un archivo de elemento de diseño vectorial para ver la vista previa de la imagen.
- Selecciona el panel Design (no las vistas Code ni Split) para obtener una vista del ancho completo de la imagen.
Una vez que los archivos de imagen se incluyen en tu app, puedes hacer referencia a ellos en tu código. Por ejemplo, si el archivo de elemento de diseño vectorial se llama lemon_tree.xml
, entonces, en tu código Kotlin, puedes hacer referencia al elemento de diseño con su ID de recurso en el formato de R.drawable.lemon_tree
.
Agrega recursos de cadenas
Agrega las siguientes cadenas al proyecto en el archivo res > values > strings.xml:
Tap the lemon tree to select a lemon
Keep tapping the lemon to squeeze it
Tap the lemonade to drink it
Tap the empty glass to start again
Las siguientes cadenas también son necesarias en tu proyecto. No se muestran en la pantalla de la interfaz de usuario, pero se usan para la descripción de contenido de las imágenes de tu app para describir cuáles son. Agrega estas cadenas adicionales al archivo strings.xml
de tu app:
Lemon tree
Lemon
Glass of lemonade
Empty glass
Si no recuerdas cómo declarar recursos de cadenas en tu app, consulta el codelab Cómo crear una app interactiva de Dice Roller o Cadena. Asigna a cada recurso de cadenas un nombre de identificador adecuado que describa el valor que contiene. Por ejemplo, para la cadena "Lemon"
, puedes declararla en el archivo strings.xml
con el nombre de identificador lemon_content_description
y hacer referencia a ella en tu código con el ID de recurso R.string.lemon_content_description
.
Pasos para preparar limonada
Ahora tienes los recursos de cadenas y los elementos de imagen necesarios para implementar la app. A continuación, se muestra un resumen de cada paso de la app y lo que se muestra en la pantalla:
Paso 1:
- Texto:
Tap the lemon tree to select a lemon
- Imagen: Limonero (
lemon_tree.xml
)
Paso 2:
- Texto:
Keep tapping the lemon to squeeze it
- Imagen: Limón (
lemon_squeeze.xml
)
Paso 3:
- Texto:
Tap the lemonade to drink it
- Imagen: Vaso lleno de limonada (
lemon_drink.xml
)
Paso 4:
- Texto:
Tap the empty glass to start again
- Imagen: Vaso vacío (
lemon_restart.xml
)
Agrega mejoras visuales
Para que tu versión de la app se vea como estas capturas de pantalla finales, debes realizar algunos ajustes visuales más en la app:
- Aumenta el tamaño de la fuente del texto para que sea más grande que el predeterminado (como
18sp
). - Agrega espacio adicional entre la etiqueta de texto y la imagen debajo, de modo que no estén demasiado cerca entre sí (como
16dp
). - Asigna un color de elementos destacados al botón y redondea ligeramente las esquinas para que los usuarios sepan que pueden presionar la imagen.
Si quieres desafiarte, compila el resto de la app según la descripción de cómo debería funcionar. Si quieres obtener más orientación, continúa con la siguiente sección.
4. Planifica la compilación de la app
Cuando compilas una app, se recomienda primero completar una versión mínima de la app que funcione. Luego, agrega gradualmente más funciones hasta alcanzar toda la funcionalidad deseada. Identifica un pequeño fragmento de funcionalidad de extremo a extremo que puedas compilar primero.
En la app de Lemonade, observa que la parte clave de la app es la transición de un paso a otro, con una etiqueta de imagen y texto diferente cada vez. Al principio puedes ignorar el comportamiento especial del estado de exprimido, ya que puedes agregar esta funcionalidad más adelante después de compilar la base de la app.
A continuación, se muestra una propuesta de la descripción general de alto nivel de los pasos que puedes seguir para compilar la app:
- Compila el diseño de la IU del primer paso para preparar limonada, que le solicitará al usuario que elija un limón del limonero. Por ahora, puedes omitir el borde alrededor de la imagen, ya que ese es un detalle visual que puedes agregar más adelante.
- Implementa el comportamiento en la app para que, cuando el usuario presione el limonero, la app muestre la imagen de un limón y su etiqueta de texto correspondiente. Esto abarca los dos primeros pasos para preparar limonada.
- Agrega código para que la app muestre el resto de los pasos para preparar limonada cada vez que se presione la imagen. En este punto, una sola presión en el limón puede crear la transición a que se muestre el vaso de limonada.
- Agrega un comportamiento personalizado para el paso de exprimir el limón, de modo que el usuario deba "exprimir", o presionar, el limón una cantidad específica de veces (de 2 a 4), que se genera de forma aleatoria.
- Finaliza la app con los detalles de mejoras visuales necesarios. Por ejemplo, cambia el tamaño de la fuente y agrega un borde alrededor de la imagen para que la app luzca más prolija. Verifica que la app siga las prácticas recomendadas de programación, como cumplir con los lineamientos de estilo de programación de Kotlin y agregar comentarios al código.
Si puedes usar estos pasos de alto nivel como guía en la implementación de la app de Lemonade, compila la app por tu cuenta. Si necesitas orientación adicional sobre cada uno de estos cinco pasos, continúa con la siguiente sección.
5. Implementa la app
Compila el diseño de la IU
Primero, modifica la app de modo que muestre la imagen del limonero y la etiqueta de texto correspondiente, que dice Tap the lemon tree to select a lemon
, en el centro de la pantalla. También debe haber 16dp
de espacio entre el texto y la imagen debajo.
Si te resulta útil, puedes usar el siguiente código de partida en el archivo MainActivity.kt
:
package com.example.lemonade
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.lemonade.ui.theme.LemonadeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LemonadeTheme {
LemonApp()
}
}
}
}
@Composable
fun LemonApp() {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text(text = "Hello there!")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
LemonadeTheme {
LemonApp()
}
}
Este código es similar al que genera automáticamente Android Studio. Sin embargo, en lugar de un elemento componible Greeting()
, se define un elemento LemonApp()
componible, y este no espera ningún parámetro. El elemento DefaultPreview()
componible también se actualiza para usar el elemento LemonApp()
componible para que puedas obtener una vista previa de tu código con facilidad.
Después de ingresar este código en Android Studio, modifica el elemento LemonApp()
componible, que debe incluir el contenido de la app. Estas son algunas preguntas para guiar tu proceso de pensamiento:
- ¿Qué elementos componibles usarás?
- ¿Existe un componente de diseño de Compose estándar que puede ayudarte a organizar los elementos componibles en las posiciones deseadas?
Implementa este paso para que la etiqueta de texto y el limonero se muestren en la app cuando se inicie. Obtén una vista previa del elemento componible en Android Studio para saber cómo se ve la IU mientras modificas el código. Ejecuta la app para asegurarte de que se vea como la captura de pantalla que viste antes en esta sección.
Cuando termines, regresa a estas instrucciones si necesitas más orientación para agregar un comportamiento cuando se presiona la imagen.
Agrega el comportamiento de clics
A continuación, agregarás código para que, cuando el usuario presione la imagen del limonero, aparezca la imagen del limón junto con la etiqueta de texto Keep tapping the lemon to squeeze it
. En otras palabras, cuando presionas el limonero, cambian el texto y la imagen.
Anteriormente en esta ruta de aprendizaje, obtuviste información para crear un botón en el que se puede hacer clic. En el caso de la app de Lemonade, no hay elementos Button
componibles. Sin embargo, puedes hacer que cualquier elemento componible, no solo botones, permita hacer clic en él cuando especificas el modificador clickable
. Para ver un ejemplo, consulta la página de documentación de elementos que admiten clics.
¿Qué debe suceder cuando se hace clic en la imagen? El código para implementar este comportamiento es no trivial, así que vuelve para revisar una app que ya conoces.
Vistazo a la app de Dice Roller
Revisa el código de la app de Dice Roller para observar cómo la app muestra diferentes imágenes de dados según el valor del lanzamiento del dado:
MainActivity.kt en la app de Dice Roller
...
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
var result by remember { mutableStateOf(1) }
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
}
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Image(painter = painterResource(id = imageResource), contentDescription = result.toString())
Button(onClick = { result = (1..6).random() }) {
Text(stringResource(id = R.string.roll))
}
}
}
...
Responde estas preguntas sobre el código de la app de Dice Roller:
- ¿Qué valor de variable determina la imagen de dado apropiada que se mostrará?
- ¿Qué acción del usuario activa el cambio de esa variable?
La función de componibilidad DiceWithButtonAndImage()
almacena el lanzamiento de dado más reciente, en la variable result
, que se definió con el elemento remember
componible y la función mutableStateOf()
en esta línea de código:
var result by remember { mutableStateOf(1) }
Cuando la variable result
se actualiza a un valor nuevo, Compose activa la recomposición del elemento DiceWithButtonAndImage()
componible, lo que significa que este se volverá a ejecutar. El valor result
se recuerda en todas las recomposiciones, por lo que, cuando se vuelve a ejecutar el elemento DiceWithButtonAndImage()
componible, se usa el valor result
más reciente. Cuando usas una sentencia when
sobre el valor de la variable result
, el elemento componible determina el nuevo ID del recurso de elementos de diseño que se mostrará y el elemento Image
componible lo muestra.
Aplica lo que aprendiste a la app de Lemonade
Ahora, responde preguntas similares sobre la app de Lemonade:
- ¿Hay alguna variable que puedas usar para determinar qué imagen y texto se deben mostrar en la pantalla? Define esa variable en tu código.
- ¿Puedes usar condicionales en Kotlin para que la app tenga un comportamiento diferente según el valor de esa variable? Si es así, escribe esa sentencia condicional en tu código.
- ¿Qué acción del usuario activa el cambio de esa variable? Busca el sitio de tu código correcto donde eso ocurre. Agrega código allí para actualizar la variable.
Esta sección puede ser bastante difícil de implementar y requiere cambios en varios lugares de tu código para funcionar correctamente. No te desanimes si la app no funciona como esperas de inmediato. Recuerda que existen varias formas correctas de implementar este comportamiento.
Cuando termines, ejecuta la app y verifica que funcione. Cuando inicies la app, debería aparecer la imagen del limonero y la etiqueta de texto correspondiente. Con una sola presión de la imagen del limonero, se debería actualizar la etiqueta de texto y debería aparecer la imagen del limón. Por el momento, no debería ocurrir nada si presionas la imagen del limón.
Agrega los pasos restantes
Ahora tu app puede mostrar dos de los pasos para preparar limonada. En este punto, es posible que el elemento LemonApp()
componible se parezca al siguiente fragmento de código: No hay problema si el código no se ve igual, siempre que el comportamiento en la app sea el mismo.
MainActivity.kt
...
@Composable
fun LemonApp() {
// Current step the app is displaying (remember allows the state to be retained
// across recompositions).
var currentStep by remember { mutableStateOf(1) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
when (currentStep) {
1 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_select))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.lemon_tree),
contentDescription = stringResource(R.string.lemon_tree_content_description),
modifier = Modifier
.wrapContentSize()
.clickable {
currentStep = 2
}
)
}
}
2 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_squeeze))
Spacer(modifier = Modifier.height(32
.dp))
Image(
painter = painterResource(R.drawable.lemon_squeeze),
contentDescription = stringResource(R.string.lemon_content_description),
modifier = Modifier.wrapContentSize()
)
}
}
}
}
}
...
A continuación, agregarás el resto de los pasos para preparar limonada. Con una sola presión de la imagen, el usuario debería avanzar al siguiente paso para preparar limonada, en el que se actualizan tanto el texto como la imagen. Deberás cambiar el código para que sea más flexible y admita todos los pasos de la app, no solo los dos primeros.
Para que el comportamiento sea diferente cada vez que se hace clic en la imagen, debes personalizar el comportamiento de hacer clic. Específicamente, la lambda que se ejecuta cuando se hace clic en la imagen debe saber a qué paso nos estamos moviendo.
Es posible que empieces a notar que hay código repetido en tu app para cada paso de la preparación de limonada. Para la sentencia when
del fragmento de código anterior, el código correspondiente al caso 1
es muy similar al caso 2
, con pequeñas diferencias. Si te resulta útil, crea una nueva función de componibilidad por ejemplo, LemonTextAndImage()
, que muestre texto sobre una imagen en la IU. Cuando creas una nueva función de componibilidad que toma algunos parámetros de entrada, obtienes una función reutilizable que es útil en varias situaciones, siempre y cuando cambies las entradas que pasas. Es tu trabajo descubrir cuáles deberían ser los parámetros de entrada. Después de crear esta función de componibilidad, actualiza tu código existente para llamar a la nueva función en lugares relevantes.
Otra ventaja de tener un elemento componible separado, como LemonTextAndImage()
, es que tu código será más organizado y sólido. Cuando llamas a LemonTextAndImage()
, puedes asegurarte de que el texto y la imagen se actualizarán a los valores nuevos. De lo contrario, es fácil pasar por alto un caso en el que una etiqueta de texto actualizada se muestra con la imagen incorrecta.
Sugerencia adicional: Incluso puedes pasar una función lambda a un elemento componible. Asegúrate de usar la notación de tipo de función para especificar qué tipo de función se debe pasar. En el siguiente ejemplo, se define un elemento WelcomeScreen()
componible que acepta dos parámetros de entrada: una cadena name
y una función onStartClicked()
de tipo () -> Unit
. Eso significa que la función no toma entradas (los paréntesis vacíos antes de la flecha) y no tiene un valor que se muestre (Unit
después de la flecha). Se puede usar cualquier función que coincida con el tipo de función () -> Unit
para configurar el controlador onClick
de este Button
. Cuando se hace clic en el botón, se llama a la función onStartClicked()
.
@Composable
fun WelcomeScreen(name: String, onStartClicked: () -> Unit) {
Column {
Text(text = "Welcome $name!")
Button(
onClick = onStartClicked
) {
Text("Start")
}
}
}
Pasar una lambda a un elemento componible es útil porque se puede volver a usar el elemento WelcomeScreen()
componible en diferentes situaciones. El nombre del usuario y el comportamiento del botón onClick
pueden ser diferentes cada vez porque se pasan como argumentos.
Con este conocimiento adicional, vuelve a tu código para agregar los pasos restantes para preparar limonada a tu app.
Regresa a estas instrucciones si necesitas orientación adicional para agregar la lógica personalizada para exprimir el limón una cantidad aleatoria de veces.
Agrega la lógica de exprimido
Bien hecho. Ahora tienes la base de la app. Si presionas la imagen, deberías ir de un paso al siguiente. Es hora de agregar el comportamiento de tener que exprimir el limón varias veces para hacer la limonada. La cantidad de veces que el usuario necesita exprimir, o presionar, el limón debe ser un número aleatorio entre 2 y 4 (inclusive). Este número aleatorio es diferente cada vez que el usuario elige un nuevo limón del árbol.
Estas son algunas preguntas para guiar tu proceso de pensamiento:
- ¿Cómo se generan los números aleatorios en Kotlin?
- ¿En qué punto de tu código debes generar el número aleatorio?
- ¿Cómo puedes asegurarte de que el usuario presionó el limón la cantidad necesaria de veces antes de continuar con el siguiente paso?
- ¿Necesitas que se almacenen las variables con el elemento
remember
componible para que no se restablezcan los datos cada vez que se vuelva a dibujar la pantalla?
Cuando termines de implementar este cambio, ejecuta la app. Verifica que se deba presionar varias veces la imagen del limón para pasar al siguiente paso y que la cantidad de presiones necesaria cada vez sea un número aleatorio entre 2 y 4. Si el vaso de limonada aparece después de presionar una vez la imagen del limón, revisa tu código para saber qué falta y vuelve a intentarlo.
Regresa a estas instrucciones si necesitas orientación adicional sobre cómo finalizar la app.
Finaliza la app
Ya casi terminas. Agrega algunos detalles para perfeccionar la app.
Recuerda que estas son las capturas de pantalla finales de cómo se ve la app:
- Centra el texto y las imágenes de forma vertical y horizontal en la pantalla.
- Establece el tamaño de fuente del texto en
18sp
. - Agrega
16dp
de espacio entre el texto y la imagen. - Agrega un borde fino de
2dp
alrededor de las imágenes con esquinas ligeramente redondeadas de4dp
. El borde tiene un valor de color RGB de105
para rojo,205
para verde y216
para azul. Puedes buscar en Google ejemplos de cómo agregar un borde. También puedes consultar la documentación sobre bordes.
Cuando hayas completado estos cambios, ejecuta la app y, luego, compárala con las capturas de pantalla finales para asegurarte de que coincidan.
Como parte de las prácticas recomendadas de programación, vuelve y agrega comentarios al código para que cualquier persona que lo lea pueda comprender tu proceso de pensamiento con mayor facilidad. Quita las declaraciones de importación de la parte superior del archivo que no se usen en el código. Asegúrate de que tu código cumpla con la guía de estilo de Kotlin. Gracias a todo este esfuerzo, tu código será más fácil de mantener y de leer para otras personas.
¡Bien hecho! Hiciste un trabajo increíble para implementar la app de Lemonade. Fue una app desafiante con muchas partes para descifrar. Date un gusto con un refrescante vaso de limonada. ¡Salud!
6. Obtén el código de la solución
Descarga el código de la solución:
Como alternativa, puedes clonar el repositorio de GitHub para el código:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-lemonade.git
Recuerda que no es necesario que el código coincida exactamente con el código de la solución, ya que hay varias formas de implementar la app.
También puedes explorar el código en el repositorio de GitHub de la app de Lemonade.