1. Antes de comenzar
En este codelab, se presenta una nueva app llamada Lunch Tray, que podrás compilar por tu cuenta. En él, seguirás los pasos necesarios para completar el proyecto de la app de Lunch Tray, incluidas la configuración del proyecto y las pruebas en Android Studio.
Este codelab es diferente de los demás en este curso. A diferencia de los codelabs anteriores, el propósito de este no es proporcionar un instructivo paso a paso para compilar una app. En cambio, sirve para configurar un proyecto que completarás de forma independiente, y te proporcionará instrucciones para completar una app y controlar tu trabajo por tu cuenta.
En lugar de código de solución, proporcionamos un conjunto de pruebas como parte de la app que descargarás. Debes ejecutar estas pruebas en Android Studio (más adelante en este codelab, te mostraremos cómo hacerlo) y verás si se aprueba el código. Esto puede demorar algunos intentos; incluso los desarrolladores profesionales rara vez pasan todas las pruebas en el primer intento. Una vez que el código haya pasado todas las pruebas, podrás considerar que este proyecto está completo.
Entendemos que posiblemente solo quieras ver la solución para comparar los resultados. No proporcionamos el código de la solución porque queremos que practiques cómo es ser un desarrollador profesional. Para ello, podría ser necesario que uses diferentes habilidades con las que aún no tienes mucha práctica, como las siguientes:
- Googlear términos, mensajes de error y bits de código de la app que no reconozcas
- Probar código, leer errores, realizar cambios en el código y volver a probarlo
- Regresar al contenido anterior en Aspectos básicos de Android para repasar lo que aprendiste
- Comparar el código que sabes que funciona (es decir, el que se proporciona en el proyecto o el código de solución anterior de otras apps en la Unidad 3) con el que escribes
Esto puede parecer abrumador al principio, pero estamos 100% seguros de que, si pudiste completar la Unidad 3, ya estás listo para este proyecto. Tómate tu tiempo y no te rindas. ¡Puedes lograrlo!
Requisitos previos
- Este proyecto es para usuarios que completaron la Unidad 3 del curso Aspectos básicos de Android en Kotlin.
Qué compilarás
- Tomarás una app con la que puedes pedir comida llamada Lunch Tray, implementarás un ViewModel con vinculación de datos y agregarás navegación entre los fragmentos.
Requisitos
- Una computadora que tenga Android Studio instalado
2. Descripción general de la app finalizada
Te damos la bienvenida al proyecto: Lunch Tray.
Como seguramente ya sabes, la navegación es una parte fundamental del desarrollo de Android. Si estás usando una app para explorar recetas, encontrar instrucciones sobre cómo llegar a tu restaurante favorito o, lo más importante, pedir comida, es probable que navegues por varias pantallas de contenido. En este proyecto, aprovecharás las habilidades que aprendiste en la Unidad 3 para construir una app con la que puedes pedir comida, llamada Lunch Tray, mediante la implementación de un ViewModel, la vinculación de datos y la navegación entre pantallas.
A continuación, se muestran las capturas de pantalla de la app final. Cuando el usuario ejecute por primera vez la app de Lunch Tray, verá una pantalla con un solo botón: Comenzar pedido.
Luego de hacer clic en Comenzar pedido, el usuario podrá seleccionar un plato principal entre las opciones disponibles. El usuario puede cambiar su selección, lo que actualizará el Subtotal que se muestra en la parte inferior.
En la siguiente pantalla, el usuario podrá agregar una guarnición.
En la siguiente pantalla, el usuario podrá seleccionar un acompañamiento para su pedido.
Por último, se muestra al usuario un resumen del costo del pedido, desglosado en subtotal, impuestos sobre las ventas y costo total. También podrá confirmar o cancelar el pedido.
Ambas opciones envían al usuario a la primera pantalla. Si el usuario confirmó el pedido, debería aparecerle un aviso en la parte inferior de la pantalla para informarle que se confirmó el pedido.
3. Cómo comenzar
Descarga el código del proyecto
Ten en cuenta que el nombre de la carpeta es android-basics-kotlin-lunch-tray-app
. Selecciona esta carpeta cuando abras el proyecto en Android Studio.
- 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.
Antes de comenzar a implementar ViewModel
y la navegación, dedica unos minutos a comprobar que el proyecto se compile correctamente mientras te familiarizas con él. Cuando ejecutes la app por primera vez, verás una pantalla vacía. La MainActivity
no presenta ningún fragmento, ya que aún no configuraste el gráfico de navegación.
La estructura del proyecto debe ser similar a otros proyectos con los que ya trabajaste. Se proporcionan paquetes distintos para los datos, el modelo y la IU, así como directorios independientes para los recursos.
La clase MenuItem
del paquete model representa todas las opciones de almuerzo que puede pedir el usuario (plato principal, guarniciones y acompañamientos). Los objetos MenuItem
tienen nombre, descripción, precio y tipo.
data class MenuItem(
val name: String,
val description: String,
val price: Double,
val type: Int
) {
fun getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(price)
}
El tipo se representa con un número entero que proviene del objeto ItemType
en el paquete de constants.
object ItemType {
val ENTREE = 1
val SIDE_DISH = 2
val ACCOMPANIMENT = 3
}
Se pueden encontrar objetos MenuItem
individuales en DataSource.kt
, dentro del paquete de datos.
object DataSource {
val menuItems = mapOf(
"cauliflower" to
MenuItem(
name = "Cauliflower",
description = "Whole cauliflower, brined, roasted, and deep fried",
price = 7.00,
type = ItemType.ENTREE
),
...
}
Este objeto solo contiene un mapa que consta de una clave y un MenuItem
correspondiente. Accederás a DataSource
desde ObjectViewModel
, que implementarás primero.
Define el ViewModel
Como se observó en las capturas de pantalla de la página anterior, la app le solicita al usuario tres cosas: un plato principal, una guarnición y un acompañamiento. Luego, en la pantalla de resumen del pedido, se muestra el subtotal y se calcula el impuesto sobre las ventas según los artículos seleccionados, que se usan para calcular el total del pedido.
En el paquete model, abre OrderViewModel.kt
y verás que algunas variables ya se encuentran definidas. La propiedad menuItems
simplemente te permite acceder a DataSource
desde ViewModel
.
val menuItems = DataSource.menuItems
En primer lugar, además hay algunas variables para previousEntreePrice
, previousSidePrice
y previousAccompanimentPrice
. Como el subtotal se actualiza a medida que el usuario elige (en vez de agregarse al final), estas variables se usan para realizar un seguimiento de la selección previa del usuario si cambia su selección antes de pasar a la siguiente pantalla. Las utilizarás para asegurarte de que el subtotal represente la diferencia entre los precios de los artículos que se seleccionaron antes y ahora.
private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0
También hay variables privadas, _entree
, _side
y _accompaniment
, para almacenar la opción seleccionada. Estas son del tipo MutableLiveData<MenuItem?>
. Cada una va acompañada de una propiedad de copia de seguridad pública, entree
, side
y accompaniment
, del tipo inmutable LiveData<MenuItem?>
. Se accede a ellas mediante los diseños de fragmentos para mostrar el elemento seleccionado en la pantalla. El MenuItem
que se incluye en el objeto de LiveData
también se puede anular, ya que es posible que el usuario no seleccione un plato principal, guarnición o acompañamiento.
// Entree for the order
private val _entree = MutableLiveData<MenuItem?>()
val entree: LiveData<MenuItem?> = _entree
// Side for the order
private val _side = MutableLiveData<MenuItem?>()
val side: LiveData<MenuItem?> = _side
// Accompaniment for the order.
private val _accompaniment = MutableLiveData<MenuItem?>()
val accompaniment: LiveData<MenuItem?> = _accompaniment
También hay variables LiveData
para el subtotal, el total y el impuesto, que usan formato de número para que se muestren como moneda.
// Subtotal for the order
private val _subtotal = MutableLiveData(0.0)
val subtotal: LiveData<String> = Transformations.map(_subtotal) {
NumberFormat.getCurrencyInstance().format(it)
}
// Total cost of the order
private val _total = MutableLiveData(0.0)
val total: LiveData<String> = Transformations.map(_total) {
NumberFormat.getCurrencyInstance().format(it)
}
// Tax for the order
private val _tax = MutableLiveData(0.0)
val tax: LiveData<String> = Transformations.map(_tax) {
NumberFormat.getCurrencyInstance().format(it)
}
Por último, la tasa impositiva es de un valor codificado de 0.08 (8%).
private val taxRate = 0.08
Existen seis métodos en OrderViewModel
que deberás implementar.
setEntree(), setSide(), y setAccompaniment()
Todos estos métodos deben funcionar de la misma manera para el plato principal, la guarnición y el acompañamiento, respectivamente. A modo de ejemplo, setEntree()
debe realizar lo siguiente:
- Si
_entree
no esnull
(es decir, el usuario ya seleccionó un plato principal pero cambió su elección), establecepreviousEntreePrice
al precio decurrent _entree
. - Si el
_subtotal
no esnull
, resta elpreviousEntreePrice
del subtotal. - Actualiza el valor de
_entree
al plato principal que se pasó a la función (accede aMenuItem
usando elmenuItems
). - Llama a
updateSubtotal()
para pasar el precio del plato principal recién seleccionado.
La lógica para setSide()
y setAccompaniment()
es igual a la implementación para setEntree()
.
updateSubtotal()
Se llama a updateSubtotal()
con un argumento para el precio nuevo que se debe agregar al subtotal. Con este método, se deben realizar las siguientes tres tareas:
- Si
_subtotal
no esnull
, agregaitemPrice
a_subtotal
. - De lo contrario, si
_subtotal
esnull
, configura_subtotal
comoitemPrice
. - Después de configurar (o actualizar) el
_subtotal
, llama acalculateTaxAndTotal()
para que esos valores se actualicen y se vean reflejados en el nuevo subtotal.
calculateTaxAndTotal()
calculateTaxAndTotal()
debe actualizar las variables del impuesto y del total en función del subtotal. Implementa el método de la siguiente manera:
- Configura el
_tax
igual a la tasa de impuestos multiplicada por el subtotal. - Configura el
_total
igual al subtotal más el impuesto.
resetOrder()
Se llamará a resetOrder()
cuando el usuario confirme o cancele un pedido. Asegúrate de que tu app no tenga ningún dato guardado cuando el usuario inicie un pedido nuevo.
Implementa resetOrder()
configurando todas las variables que modificaste en OrderViewModel
con su valor original (ya sea 0.0 o nulo).
Crea variables de vinculación de datos
Implementa la vinculación de datos en los archivos de diseño. Abre los archivos de diseño y agrega variables de vinculación de datos de tipo OrderViewModel
o la clase de fragmento correspondiente.
Deberás implementar todos los comentarios TODO
para configurar el texto y hacer clic en los objetos de escucha en los siguientes cuatro archivos de diseño:
fragment_entree_menu.xml
fragment_side_menu.xml
fragment_accompaniment_menu.xml
fragment_checkout.xml
Cada tarea específica se detalla en un comentario TODO en los archivos de diseño, pero los pasos se resumen a continuación.
- En
fragment_entree_menu.xml
, en la etiqueta<data>
, agrega una variable de vinculación para elEntreeMenuFragment
. Para cada uno de los botones de selección, deberás configurar el plato principal en elViewModel
cuando esté seleccionado. El texto de la vista del texto subtotal se debe actualizar según corresponda. También deberás configurar el atributoonClick
para elcancel_button
ynext_button
para cancelar el pedido o navegar a la siguiente pantalla, respectivamente. - Haz lo mismo en el
fragment_side_menu.xml
, pero agrega una variable de vinculación para elSideMenuFragment
, excepto si quieres configurar el lado del modelo de vista cuando se seleccione cada botón de selección. El texto del subtotal también deberá actualizarse, y también deberás configurar el atributoonClick
para los botones Cancelar y Siguiente. - De nuevo, haz lo mismo en el
fragment_accompaniment_menu.xml
pero con una variable de vinculación para elAccompanimentMenuFragment
y establece el acompañamiento cuando se seleccione cada botón de selección. Una vez más, también deberás establecer atributos para el texto del subtotal, el botón Cancelar y el botón Siguiente. - En
fragment_checkout.xml
, deberás agregar la etiqueta<data>
para poder definir variables de vinculación. Dentro de la etiqueta<data>
, agrega dos variables de vinculación, una paraOrderViewModel
y otra paraCheckoutFragment
. En las vistas de texto, deberás configurar los nombres y precios del plato principal, la guarnición y el acompañamiento que seleccionaste enOrderViewModel
. También deberás configurar el subtotal, los impuestos y el total delOrderViewModel
. Luego, configura losonClickAttributes
para cuando se confirme o cancele el pedido, con las funciones apropiadas deCheckoutFragment
.
.
Inicializa las variables de vinculación de datos en los fragmentos
Inicializa las variables de vinculación de datos en los archivos de fragmentos correspondientes dentro del método onViewCreated()
.
EntreeMenuFragment
SideMenuFragment
AccompanimentMenuFragment
CheckoutFragment
Cómo crear el gráfico de navegación
Como aprendiste en la unidad 3, un gráfico de navegación se aloja en un elemento FragmentContainerView
, que una actividad contiene. Abre activity_main.xml
y reemplaza el comentario TODO con el siguiente código para declarar una FragmentContainerView
.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
El gráfico de navegación, mobile_navigation.xml
, se encuentra en el paquete res.navigation.
Este es el gráfico de navegación de la app. Sin embargo, el archivo está vacío en este momento. Tu tarea es agregar destinos al gráfico de navegación y simular la siguiente navegación entre pantallas.
- Navegación de
StartOrderFragment
aEntreeMenuFragment
- Navegación de
EntreeMenuFragment
aSideMenuFragment
- Navegación de
SideMenuFragment
aAccompanimentMenuFragment
- Navegación de
AccompanimentMenuFragment
aCheckoutFragment
- Navegación de
CheckoutFragment
aStartOrderFragment
- Navegación de
EntreeMenuFragment
aStartOrderFragment
- Navegación de
SideMenuFragment
aStartOrderFragment
- Navegación de
AccompanimentMenuFragment
aStartOrderFragment
- El Destino de inicio debería ser
StartOrderFragment
Una vez que configures el gráfico de navegación, deberás realizar la navegación en las clases de fragmentos. Implementa los comentarios TODO
restantes en los fragmentos, así como la MainActivity.kt
.
- Para el método
goToNextScreen()
enEntreeMenuFragment
,SideMenuFragment
yAccompanimentMenuFragment
, navega a la siguiente pantalla en la app. - Para el método
cancelOrder()
enEntreeMenuFragment
,SideMenuFragment
,AccompanimentMenuFragment
yCheckoutFragment
, primero llama aresetOrder()
en elsharedViewModel
y luego navega aStartOrderFragment
. - En
StartOrderFragment
, implementasetOnClickListener()
para navegar aEntreeMenuFragment
. - En
CheckoutFragment
, implementa el métodosubmitOrder()
. Llama aresetOrder()
en elsharedViewModel
y luego navega aStartOrderFragment
. - Por último, en
MainActivity.kt
, configuranavController
anavController
desde elNavHostFragment
.
4. Prueba tu app
El proyecto Lunch Tray contiene un objetivo "androidTest" con varios casos de prueba: MenuContentTests
, NavigationTests
y OrderFunctionalityTests
.
Cómo realizar las pruebas
Para ejecutar las pruebas, puedes realizar una de las siguientes acciones:
Si se trata de un solo caso de prueba, abre una clase de caso de prueba y haz clic en la flecha verde que está a la izquierda de la declaración de la clase. Luego, puedes seleccionar la opción Run en el menú. Se ejecutarán todas las pruebas del caso de prueba.
Lo más probable es que quieras ejecutar una sola prueba, por ejemplo, si hay una sola prueba fallida y las otras aprueban. Puedes ejecutar una sola prueba como lo harías con todo el caso de prueba. Usa la flecha verde y selecciona la opción Run.
Si tienes varios casos de prueba, también podrás ejecutar todo el conjunto. Al igual que cuando ejecutas la app, encontrarás esta opción en el menú Run.
Ten en cuenta que Android Studio usará, de forma predeterminada, el último objetivo que hayas ejecutado (app, objetivos de prueba, etc.), por lo que, si el menú sigue indicando Run > Run 'app', podrás ejecutar el objetivo de prueba si seleccionas Run > Run.
Luego, selecciona el objetivo de prueba en el menú emergente.
5. Opcional: Envíanos tus comentarios
Nos encantaría saber qué opinas de este proyecto. Completa esta encuesta breve y haz los comentarios pertinentes. Tu aporte ayudará a guiar proyectos futuros en este curso.