Intents y Estados en ViewModel para Cámara en Android

Clase 25 de 33Curso de Android: Integración de APIs nativas

Resumen

Trabajar de manera efectiva con la lógica de una pantalla de cámara en Android requiere definir claramente intents y estados de la interfaz de usuario (UI). En este contenido, aprenderemos cómo crear y manipular estos elementos esenciales usando Kotlin y su ViewModel, facilitando acciones como la toma de fotos, solicitud de permisos y visualización previa.

¿Qué son los intents y por qué son importantes en nuestra app de cámara?

Los intents son acciones específicas que informan a nuestra aplicación qué tarea debe ejecutarse, en este caso relacionadas con la cámara del dispositivo. Para organizarlos, utilizamos sealed interface en Kotlin. Los principales intents a considerar son:

  • FotoTomada: nos avisa cuando se ha tomado una foto y contiene su información en un ByteArray. El uso de las funciones Equals y HashCode generadas por Kotlin permite identificar si dos fotos son exactamente la misma imagen.
  • SubmitCameraPermissionInfo: indica si el permiso de acceso a la cámara fue aceptado por el usuario y si la app debe explicar el porqué (rationale). Esto se apunta a través de valores booleanos:
  • AcceptedCameraPermission: aceptación del permiso por el usuario.
  • shouldShowCameraRationally: necesidad de mostrar una justificación clara al usuario antes de pedir permiso.
  • DataObjects adicionales:
  • FotoGuardada: indica que el usuario aceptó y guardó la foto.
  • CancelarFoto: describe la acción de cancelar la visualización previa o la captura de la foto.

¿Cómo crear y administrar el estado de la interfaz de usuario?

Gestionar el estado de nuestra UI implica definir claramente en qué condición se encuentra la interfaz en cualquier momento. Definimos CameraUIState utilizando una DataClass, que incluye:

  • isInPreviewMode: estado que define si la pantalla actualmente muestra una vista previa después de tomar una foto.
  • LastSavedPhoto: referencia al archivo que representa la última foto guardada.
  • ShowCameraRationales: booleano que indica si debemos mostrar racionales de permisos al usuario.

Iniciamos estos valores con condiciones por defecto útiles:

  • isInPreviewMode: 0 (la cámara está lista para tomar una foto).
  • ShowCameraRationales: false (por defecto no mostramos racionales).

¿De qué manera implementar el ViewModel utilizando Hilt y por qué hacerlo así?

El ViewModel es un componente esencial en Android que mantiene y gestiona la lógica de la interfaz. Usamos Hilt para facilitar la inyección de dependencias, beneficiándonos así de un mejor manejo de la memoria y estructura del código. En nuestro ejemplo, el ViewModel llamado CameraViewModel tiene las siguientes características clave:

  • Se anota con @HiltViewModel y extiende del propio ViewModel.
  • Se inyectan mediante constructor dos componentes esenciales:
  • PhotoHandler: maneja la obtención y guardado de fotos.
  • LocationTracker: proporciona la localización asociada a cada foto capturada.

Dentro del ViewModel también:

  • Definimos un estado mutable expuesto mediante un MutableStateFlow y asStateFlow para la UI.
  • Exponemos la foto de vista previa PreviewPhoto en la interfaz mediante un StateFlow, usando el operador stateIn, que retiene el valor durante cinco segundos tras la cancelación de la suscripción.

¿Cómo gestionar los intents en nuestro ViewModel?

Para administrar acciones derivadas de los intents definidos, usamos una función que maneja estos eventos mediante la estructura when:

fun onIntentReceived(intent: CameraIntent) = when(intent) {
    is CameraIntent.SubmitCameraPermissionInfo -> {
        uiState.value = uiState.value.copy(
            showCameraRationales = intent.shouldShowCameraRationally,
            permissionGranted = intent.acceptedCameraPermission
        )
    }
    // Aquí añadimos más acciones según los intents disponibles
}

Esta estructura permite reaccionar fácilmente a eventos, modificando adecuadamente el estado de la interfaz según corresponda.

¿Tienes alguna pregunta específica sobre estos componentes o cómo usarlos en tus proyectos Android? ¡Déjanos tu comentario y continúa aprendiendo!