Compila una app para Android con Firebase y Jetpack Compose

1. Introducción

Última actualización: 16/11/2022

Compila una app para Android con Firebase y Jetpack Compose

En este codelab, compilarás una app para Android llamada Make It So. La IU de esta app se compiló completamente con Jetpack Compose, el moderno kit de herramientas de Android para compilar IU nativas. Es intuitivo y requiere menos código que escribir archivos .xml y vincularlos a actividades, fragmentos o vistas.

El primer paso para comprender qué tan bien funcionan Firebase y Jetpack Compose en conjunto es comprender la arquitectura moderna de Android. Una buena arquitectura hace que el sistema sea fácil de entender, de desarrollar y de mantener, ya que deja muy claro cómo los componentes están organizados y se comunican entre sí. En el mundo de Android, la arquitectura recomendada se llama Model - View - ViewModel. El Modelo representa la capa que accede a los datos en la aplicación. El objeto View es la capa de la IU y no debería saber nada sobre la lógica empresarial. Y ViewModel es donde se aplica la lógica empresarial, lo que a veces requiere que ViewModel llame a la capa Model.

Te recomendamos que leas este artículo para comprender cómo se aplica Model - View - ViewModel a una app para Android compilada con Jetpack Compose, ya que facilitará la comprensión de la base de código y los próximos pasos.

Qué compilarás

Facilita la tarea es una aplicación simple de lista de tareas pendientes que permite al usuario agregar y editar tareas, agregar marcas, prioridades y fechas límite, y marcarlas como completadas. Las siguientes imágenes muestran las dos páginas principales de esta aplicación: la página de creación de tareas y la página principal con la lista de tareas creadas.

Pantalla Add Task Pantalla principal de Make it So

Agregarás algunas funciones que faltan en esta app:

  • Autentica usuarios con un correo electrónico y una contraseña
  • Agregar un objeto de escucha a una colección de Firestore y hacer que la IU reaccione a los cambios
  • Agrega seguimientos personalizados para supervisar el rendimiento de código específico en la app
  • Crea un botón para activar o desactivar la función con Remote Config y usa el lanzamiento en etapas para iniciarla

Qué aprenderás

  • Cómo usar Firebase Authentication, Performance Monitoring, Remote Config y Cloud Firestore en una aplicación moderna para Android
  • Cómo hacer que las APIs de Firebase se ajusten a una arquitectura MVVM
  • Cómo reflejar los cambios realizados con las APIs de Firebase en una IU de Compose

Requisitos

2. Obtén la app de ejemplo y configura Firebase

Obtén el código de la app de ejemplo

Clona el repositorio de GitHub a partir de la línea de comandos:

git clone https://github.com/FirebaseExtended/make-it-so-android.git

Configura Firebase

Lo primero que debes hacer es ir a Firebase console y crear un proyecto de Firebase haciendo clic en el botón “+ Agregar proyecto”, como puedes ver a continuación:

Firebase console

Sigue los pasos que aparecen en pantalla para completar la creación del proyecto.

Dentro de cada proyecto de Firebase, puedes crear diferentes apps: para Android, iOS, Web, Flutter y Unity. Elige la opción de Android, como se muestra aquí:

Descripción general del proyecto de Firebase

Luego, sigue estos pasos:

  1. Ingresa com.example.makeitso como el nombre del paquete y, de manera opcional, ingresa un sobrenombre. Para este codelab, no necesitas agregar el certificado de firma de depuración.
  2. Haz clic en Siguiente para registrar tu app y acceder al archivo de configuración de Firebase.
  3. Haz clic en Descargar google-services.json para descargar el archivo de configuración y guardarlo en el directorio make-it-so-android/app.
  4. Haz clic en Siguiente. Debido a que los SDK de Firebase ya están incluidos en el archivo build.gradle del proyecto de muestra, haz clic en Siguiente para pasar a Próximos pasos.
  5. Haz clic en Ir a la consola para finalizar.

Para que la app Make it So funcione correctamente, hay dos cosas que debes hacer en la consola antes de pasar al código: habilitar los proveedores de autenticación y crear la base de datos de Firestore. Primero, habilitemos Authentication para que los usuarios puedan acceder a la app:

  1. En el menú Build, selecciona Authentication y, luego, haz clic en Get Started.
  2. En la tarjeta Método de acceso, selecciona Correo electrónico/contraseña y habilítalo.
  3. Luego, haz clic en Agregar proveedor nuevo y selecciona y habilita Anónimo.

A continuación, configura Firestore. Usarás Firestore para almacenar las tareas de un usuario que accedió. Cada usuario obtendrá su propio documento dentro de una colección de la base de datos.

  1. Desde el menú Compilación, selecciona Firestore y, luego, haz clic en Crear base de datos.
  2. Mantén habilitada la opción Comenzar en modo de producción y haz clic en Siguiente.
  3. Cuando se te solicite, selecciona la ubicación en la que se almacenarán los datos de Cloud Firestore. Cuando desarrolles una app de producción, querrás que esta se encuentre en una región cercana a la mayoría de los usuarios y que sea común con otros servicios de Firebase, como Functions. En este codelab, puedes conservar la región predeterminada o seleccionar la más cercana a ti.
  4. Haz clic en Habilitar para aprovisionar tu base de datos de Firestore.

Dediquemos un momento a compilar reglas de seguridad sólidas en la base de datos de Firestore. Abre el panel de Firestore y ve a la pestaña Reglas. Luego, actualiza las reglas de seguridad para que se vean de la siguiente manera:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow create: if request.auth != null;
      allow read, update, delete: if request.auth != null && resource.data.userId == request.auth.uid;
    }
  }
}

Básicamente, estas reglas indican que cualquier usuario que haya accedido a la app puede crear un documento para sí mismo dentro de cualquier colección. Luego, una vez creado, solo el usuario que creó el documento podrá verlo, actualizarlo o borrarlo.

Ejecuta la aplicación

Ya está todo listo para ejecutar la aplicación. Abre la carpeta make-it-so-android/start en Android Studio y ejecuta la app (se puede hacer con un Android Emulator o un dispositivo Android real).

3. Firebase Authentication

¿Qué función agregarás?

En el estado actual de la app de ejemplo Hacerlo así, un usuario puede comenzar a usarla sin tener que acceder primero. Para lograrlo, usa autenticación anónima. Sin embargo, las cuentas anónimas no permiten que los usuarios accedan a sus datos en otros dispositivos ni en sesiones futuras. Si bien la autenticación anónima es útil para una integración cálida, siempre debes ofrecer la opción de que los usuarios realicen una conversión a una forma de acceso diferente. Con esto en mente, en este codelab, agregarás autenticación con correo electrónico y contraseña a la app Make It So.

Llegó el momento de programar

Tan pronto como el usuario cree una cuenta, ingresando un correo electrónico y una contraseña, tienes que pedirle a la API de Firebase Authentication una credencial de correo electrónico y, luego, vincular la nueva credencial con la cuenta anónima. Abre el archivo AccountServiceImpl.kt en Android Studio y actualiza la función linkAccount para que se vea de la siguiente manera:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String) {
    val credential = EmailAuthProvider.getCredential(email, password)
    auth.currentUser!!.linkWithCredential(credential).await()
}

Ahora abre SignUpViewModel.kt y llama a la función linkAccount del servicio dentro del bloque launchCatching de la función onSignUpClick:

screens/sign_up/SignUpViewModel.kt

launchCatching {
    accountService.linkAccount(email, password)
    openAndPopUp(SETTINGS_SCREEN, SIGN_UP_SCREEN)
}

Primero intenta autenticarse y, si la llamada se realiza correctamente, pasa a la siguiente pantalla (SettingsScreen). Mientras ejecutas estas llamadas dentro de un bloque launchCatching, si se produce un error en la primera línea, se captará y controlará la excepción, y no se alcanzará la segunda línea.

Tan pronto como se vuelva a abrir SettingsScreen, tendrás que asegurarte de que las opciones Acceder y Crear cuenta hayan desaparecido, porque ahora el usuario ya se autenticó. Para ello, haremos que el elemento SettingsViewModel escuche el estado del usuario actual (disponible en AccountService.kt) para verificar si la cuenta es anónima o no. Para ello, actualiza uiState en SettingsViewModel.kt de modo que se vea de la siguiente manera:

screen/settings/SettingsViewModel.kt

val uiState = accountService.currentUser.map {
    SettingsUiState(it.isAnonymous)
}

Lo último que debes hacer es actualizar uiState en SettingsScreen.kt para recopilar los estados emitidos por SettingsViewModel:

screens/settings/SettingsScreen.kt

val uiState by viewModel.uiState.collectAsState(
    initial = SettingsUiState(false)
)

Ahora, cada vez que cambie el usuario, se recompondrá SettingsScreen para mostrar las opciones según el nuevo estado de autenticación del usuario.

Llegó el momento de hacer la prueba.

Ejecuta Make it So y navega a la configuración haciendo clic en el ícono de ajustes en la esquina superior derecha de la pantalla. Allí, haz clic en la opción Crear cuenta:

Pantalla de configuración de Make it So Pantalla de registro de Make it So

Escribe un correo electrónico válido y una contraseña segura para crear tu cuenta. Debería funcionar. Deberías ver la página de configuración, donde verás dos opciones nuevas: salir y borrar tu cuenta. Puedes verificar la cuenta nueva que se creó en el panel de Authentication de Firebase console. Para ello, haz clic en la pestaña Usuarios.

4. Cloud Firestore

¿Qué función agregarás?

En el caso de Cloud Firestore, agregarás un objeto de escucha a la colección de Firestore que almacena los documentos que representan las tareas que se muestran en Hacerlo así. Una vez que agregues este objeto de escucha, recibirás todas las actualizaciones de esta colección.

Llegó el momento de programar

Actualiza el archivo Flow disponible en StorageServiceImpl.kt para que se vea de la siguiente manera:

model/service/impl/StorageServiceImpl.kt

override val tasks: Flow<List<Task>>
    get() =
      auth.currentUser.flatMapLatest { user ->
        firestore.collection(TASK_COLLECTION).whereEqualTo(USER_ID_FIELD, user.id).dataObjects()
      }

Este código agrega un objeto de escucha a la colección de tareas en función de user.id. Cada tarea está representada por un documento en una colección llamada tasks y cada una de ellas tiene un campo llamado userId. Ten en cuenta que se emitirá un nuevo Flow si cambia el estado de currentUser (por ejemplo, si sales de la cuenta).

Ahora, debes hacer que los Flow de TasksViewModel.kt se reflejen de la misma manera que en el servicio:

screen/tasks/TasksViewModel.kt

val tasks = storageService.tasks

Y lo último será que composable function en TasksScreens.kt, que representa la IU, conozca este flujo y lo recopile como un estado. Cada vez que cambie el estado, la función de componibilidad se recompondrá automáticamente y mostrará el estado más reciente al usuario. Agrega lo siguiente a TasksScreen composable function:

screen/tasks/TasksScreen.kt

val tasks = viewModel
    .tasks
    .collectAsStateWithLifecycle(emptyList())

Una vez que la función de componibilidad tiene acceso a estos estados, puedes actualizar LazyColumn (que es la estructura que usas para mostrar una lista en la pantalla) para que se vea de la siguiente manera:

screen/tasks/TasksScreen.kt

LazyColumn {
    items(tasks.value, key = { it.id }) { taskItem ->
        TaskItem( [...] )
    }
}

Llegó el momento de hacer la prueba.

Para probar que funcionó, agrega una tarea nueva con la app (haz clic en el botón Agregar en la esquina inferior derecha de la pantalla). Una vez que termines de crear la tarea, debería aparecer en la colección de Firestore en la consola de Firestore. Si accedes a Make it So en otros dispositivos con la misma cuenta, podrás editar tus tareas pendientes y ver cómo se actualizan en tiempo real en todos los dispositivos.

5. Performance Monitoring

¿Qué función agregarás?

Es muy importante prestar atención al rendimiento, ya que es muy probable que los usuarios dejen de usar tu app si no son buenos y tardan demasiado tiempo en completar una tarea simple usándola. Por ese motivo, a veces es útil recopilar algunas métricas sobre el recorrido específico que realiza un usuario en tu app. Y para ayudarte con eso, Firebase Performance Monitoring ofrece seguimientos personalizados. Sigue los siguientes pasos para agregar seguimientos personalizados y medir el rendimiento en diferentes fragmentos de código en Cómo hacerlo.

Llegó el momento de programar

Si abres el archivo Performance.kt, verás una función intercalada llamada seguimiento. Esta función llama a la API de Performance Monitoring para crear un seguimiento personalizado y pasa el nombre del seguimiento como parámetro. El otro parámetro que ves es el bloque de código que quieres supervisar. La métrica predeterminada que se recopila para cada seguimiento es el tiempo que tarda en ejecutarse por completo:

model/service/Performance.kt

inline fun <T> trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block)

Puedes elegir qué partes de la base de código crees que es importante medir y agregarle seguimientos personalizados. A continuación, se muestra un ejemplo para agregar un seguimiento personalizado a la función linkAccount que viste antes (en AccountServiceImpl.kt) en este codelab:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String): Unit =
  trace(LINK_ACCOUNT_TRACE) {
      val credential = EmailAuthProvider.getCredential(email, password)
      auth.currentUser!!.linkWithCredential(credential).await()
  }

Ahora, es tu turno. Agrega algunos seguimientos personalizados a la app Make it So y pasa a la siguiente sección para probar si funcionó como se esperaba.

Llegó el momento de hacer la prueba.

Cuando termines de agregar los seguimientos personalizados, ejecuta la app y asegúrate de usar varias veces las funciones que quieras medir. Luego, ve a Firebase console y ve al Panel de rendimiento. En la parte inferior de la pantalla, encontrarás tres pestañas: Solicitudes de red, Seguimientos personalizados y Renderización de pantalla.

Ve a la pestaña Seguimientos personalizados y verifica que los seguimientos que agregaste en la base de código se muestren allí y que puedas ver cuánto tiempo suele tardar en ejecutar estos fragmentos de código.

6. Remote Config

¿Qué función agregarás?

Existe una gran cantidad de casos de uso para Remote Config, desde cambiar la apariencia de la app de forma remota hasta configurar distintos comportamientos para distintos segmentos de usuarios. En este codelab, usarás Remote Config para crear un botón para activar o desactivar una función que ocultará o mostrará la nueva función de edición de tareas en la app de Configurar.

Llegó el momento de programar

Lo primero que debes hacer es crear la configuración en Firebase console. Para ello, debes navegar al panel de Remote Config y hacer clic en el botón Agregar parámetro. Completa los campos según la imagen que se muestra a continuación:

Diálogo Crear un parámetro de Remote Config

Una vez que completaste todos los campos, puedes hacer clic en el botón Guardar y, luego, en Publicar. Ahora que el parámetro se creó y está disponible para tu base de código, debes agregar el código que recuperará los valores nuevos de tu app. Abre el archivo ConfigurationServiceImpl.kt y actualiza la implementación de estas dos funciones:

model/service/impl/ConfigurationServiceImpl.kt

override suspend fun fetchConfiguration(): Boolean {
  return remoteConfig.fetchAndActivate().await()
}

override val isShowTaskEditButtonConfig: Boolean
  get() = remoteConfig[SHOW_TASK_EDIT_BUTTON_KEY].asBoolean()

La primera función recupera los valores del servidor, y se la llama en cuanto se inicia la app, en SplashViewModel.kt. Es la mejor manera de garantizar que los valores más actualizados estarán disponibles en todas las pantallas desde el principio. No será una buena experiencia del usuario si cambias la IU o el comportamiento de la app más adelante, cuando el usuario esté haciendo algo.

La segunda función muestra el valor booleano que se publicó para el parámetro que acabas de crear en la consola. Deberás recuperar esta información en TasksViewModel.kt agregando lo siguiente a la función loadTaskOptions:

screen/tasks/TasksViewModel.kt

fun loadTaskOptions() {
  val hasEditOption = configurationService.isShowTaskEditButtonConfig
  options.value = TaskActionOption.getOptions(hasEditOption)
}

Estás recuperando el valor en la primera línea y utilizándolo para cargar las opciones del menú para los elementos de la tarea en la segunda línea. Si el valor es false, significa que el menú no contendrá la opción de edición. Ahora que tienes la lista de opciones, debes hacer que la IU la muestre de forma correcta. Mientras compilas una app con Jetpack Compose, debes buscar el composable function que declara cómo debería verse la IU de TasksScreen. Abre el archivo TasksScreen.kt y actualiza LazyColum para que apunte a las opciones disponibles en TasksViewModel.kt:

screen/tasks/TasksScreen.kt

val options by viewModel.options

LazyColumn {
  items(tasks.value, key = { it.id }) { taskItem ->
    TaskItem(
      options = options,
      [...]
    )
  }
}

El TaskItem es otro composable function que declara cómo debería verse la IU de una sola tarea. Y cada tarea tiene un menú con opciones que se muestra cuando el usuario hace clic en el ícono de tres puntos al final.

Llegó el momento de hacer la prueba.

Ya está todo listo para ejecutar la app. Comprueba que el valor que publicaste con Firebase console coincida con el comportamiento de la app:

  • Si es false, solo deberías ver dos opciones al hacer clic en el ícono de tres puntos:
  • Si es true, deberías ver tres opciones al hacer clic en el ícono de tres puntos.

Intenta cambiar el valor un par de veces en la consola y reinicia la app. Así de fácil es iniciar funciones nuevas en tu app con Remote Config.

7. ¡Felicitaciones!

¡Felicitaciones! Compilaste correctamente una app para Android con Firebase y Jetpack Compose.

Agregaste Firebase Authentication, Performance Monitoring, Remote Config y Cloud Firestore a una app para Android completamente compilada con Jetpack Compose para la IU, y lograste que se adaptara a la arquitectura MVVM recomendada.

Lecturas adicionales

Documentos de referencia