Proyecto: app de Lunch Tray

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.

20fa769d4ba93ef3.png

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.

438b61180d690b3a.png

En la siguiente pantalla, el usuario podrá agregar una guarnición.

768352680759d3e2.png

En la siguiente pantalla, el usuario podrá seleccionar un acompañamiento para su pedido.

8ee2bf41e9844614.png

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.

61c883c34d94b7f7.png

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.

acb7d7a5d9843bac.png

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.

  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.

1e4c0d2c081a8fd2.png

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

1debcf330fd04c7b.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.

d8e9dbdeafe9038a.png

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

8d1fda7396afe8e5.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 8de56cba7583251f.png 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.

a19fd8a4bc92f2fc.png

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:

  1. Si _entree no es null (es decir, el usuario ya seleccionó un plato principal pero cambió su elección), establece previousEntreePrice al precio de current _entree.
  2. Si el _subtotal no es null, resta el previousEntreePrice del subtotal.
  3. Actualiza el valor de _entree al plato principal que se pasó a la función (accede a MenuItem usando el menuItems).
  4. 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:

  1. Si _subtotal no es null, agrega itemPrice a _subtotal.
  2. De lo contrario, si _subtotal es null, configura _subtotal como itemPrice.
  3. Después de configurar (o actualizar) el _subtotal, llama a calculateTaxAndTotal() 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:

  1. Configura el _tax igual a la tasa de impuestos multiplicada por el subtotal.
  2. 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:

  1. fragment_entree_menu.xml
  2. fragment_side_menu.xml
  3. fragment_accompaniment_menu.xml
  4. 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.

  1. En fragment_entree_menu.xml, en la etiqueta <data>, agrega una variable de vinculación para el EntreeMenuFragment. Para cada uno de los botones de selección, deberás configurar el plato principal en el ViewModel cuando esté seleccionado. El texto de la vista del texto subtotal se debe actualizar según corresponda. También deberás configurar el atributo onClick para el cancel_button y next_button para cancelar el pedido o navegar a la siguiente pantalla, respectivamente.
  2. Haz lo mismo en el fragment_side_menu.xml, pero agrega una variable de vinculación para el SideMenuFragment, 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 atributo onClick para los botones Cancelar y Siguiente.
  3. De nuevo, haz lo mismo en el fragment_accompaniment_menu.xml pero con una variable de vinculación para el AccompanimentMenuFragment 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.
  4. 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 para OrderViewModel y otra para CheckoutFragment. En las vistas de texto, deberás configurar los nombres y precios del plato principal, la guarnición y el acompañamiento que seleccionaste en OrderViewModel. También deberás configurar el subtotal, los impuestos y el total del OrderViewModel. Luego, configura los onClickAttributes para cuando se confirme o cancele el pedido, con las funciones apropiadas de CheckoutFragment.

.

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

  1. EntreeMenuFragment
  2. SideMenuFragment
  3. AccompanimentMenuFragment
  4. 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.

e3381215c35c1726.png

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.

  1. Navegación de StartOrderFragment a EntreeMenuFragment
  2. Navegación de EntreeMenuFragment a SideMenuFragment
  3. Navegación de SideMenuFragment a AccompanimentMenuFragment
  4. Navegación de AccompanimentMenuFragment a CheckoutFragment
  5. Navegación de CheckoutFragment a StartOrderFragment
  6. Navegación de EntreeMenuFragment a StartOrderFragment
  7. Navegación de SideMenuFragment a StartOrderFragment
  8. Navegación de AccompanimentMenuFragment a StartOrderFragment
  9. 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.

  1. Para el método goToNextScreen() en EntreeMenuFragment, SideMenuFragment y AccompanimentMenuFragment, navega a la siguiente pantalla en la app.
  2. Para el método cancelOrder() en EntreeMenuFragment, SideMenuFragment, AccompanimentMenuFragment y CheckoutFragment, primero llama a resetOrder() en el sharedViewModel y luego navega a StartOrderFragment.
  3. En StartOrderFragment, implementa setOnClickListener() para navegar a EntreeMenuFragment.
  4. En CheckoutFragment, implementa el método submitOrder(). Llama a resetOrder() en el sharedViewModel y luego navega a StartOrderFragment.
  5. Por último, en MainActivity.kt, configura navController a navController desde el NavHostFragment.

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.

8ddcbafb8ec14f9b.png

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.

335664b7fc8b4fb5.png

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.

80312efedf6e4dd3.png

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.

95aacc8f749dee8e.png

Luego, selecciona el objetivo de prueba en el menú emergente.

8b702efbd4d21d3d.png

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.