Tema representativo de entrevista

¿Cómo debería una aplicación de Android migrar a Predictive Back?

GeneralDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Una aplicación de Android todavía intercepta el retroceso con onBackPressed o KeyEvent.KEYCODE_BACK. Diseña una migración a Predictive Back que cubra las API de Compose y View, la restauración del estado ante una cancelación y cómo evitar consumir las animaciones del sistema.

Planteamiento y alcance

Mantienes una aplicación grande de Android con Activities, Fragments y pantallas en Compose. A partir de Android 15, los usuarios pueden ver vistas previas del retroceso del sistema, pero las pantallas heredadas todavía interceptan el retroceso con onBackPressed o KeyEvent.KEYCODE_BACK. El resultado es la pérdida de animaciones, lógica de confirmación en el momento equivocado o un estado inconsistente tras un gesto cancelado. El entrevistador busca una migración gradual y un plan de pruebas.

Asume que Android 13 y versiones posteriores deben seguir siendo compatibles y que la pila de navegación no se puede reescribir por completo de una sola vez. Android recomienda la opción retrocompatible OnBackPressedCallback, o la plataforma OnBackInvokedCallback cuando se requiera una integración directa con el sistema. Compose utiliza PredictiveBackHandler cuando se necesita el progreso del gesto.

Qué evalúa el entrevistador

  • Si modelas el predictive back como una vista previa seguida de confirmación (commit) o cancelación, y no como un clic de un solo disparo.
  • Si la interceptación heredada se migra a callbacks de AndroidX cuyo estado habilitado depende del estado de la interfaz de usuario.
  • Si separas los callbacks de animación de la interfaz de usuario de los observadores de registro (logging) o negocio para no consumir la animación del sistema.
  • Si explicas el orden de los callbacks en Compose, la restauración tras la cancelación y la compatibilidad con View/Fragment.
  • Si cubres la animación del sistema en Android 15, la exclusión voluntaria a nivel de actividad y pruebas repetibles en dispositivos.

Una respuesta débil dice "actualizar AndroidX y activar un flag". Una respuesta sólida define la responsabilidad de los callbacks, la recuperación idempotente tras una cancelación y un despliegue actividad por actividad.

Aclaraciones antes de responder

  1. ¿La pantalla utiliza Fragments/Navigation Component, Compose o una pila de retroceso personalizada de Activity? Eso determina la API de migración.
  2. ¿El retroceso debe confirmar un formulario no guardado? El callback debe seguir el estado de la UI antes del gesto, no decidir una vez completado.
  3. ¿Se necesita el progreso del gesto para una animación personalizada? Utiliza BackHandler para una interceptación final y PredictiveBackHandler solo cuando se requiera el progreso.
  4. ¿El objetivo incluye Android 15? La animación del sistema y las pruebas difieren, mientras que la capa de compatibilidad debe cubrir versiones anteriores.
  5. ¿La aplicación puede desplegarse por Activity o pantalla? Una aplicación grande puede usar el flag enableOnBackInvokedCallback a nivel de actividad.

Estas respuestas cambian el diseño: mantener un callback simple cuando el progreso sea innecesario, implementar la restauración para el estado del formulario y migrar las pantallas de AndroidX antes de eliminar toda la interceptación heredada.

Estructura de respuesta en 30 segundos

"Hago un inventario de cada interceptación de retroceso heredada y vinculo los callbacks al estado observable de la UI. Reemplazo la interceptación de onBackPressed y KEYCODE_BACK con OnBackPressedCallback; uso OnBackInvokedCallback solo para integración directa con la plataforma, y PredictiveBackHandler en Compose cuando necesito progreso. La vista previa solo modifica aspectos visuales reversibles. Un gesto confirmado navega o confirma; un gesto cancelado restaura la instantánea inicial. Los callbacks de prioridad predeterminada o superpuesta pueden suprimir la animación del sistema, por lo que la animación de UI y el registro de negocio utilizan rutas separadas. Despliego esto por Activity y realizo pruebas en Android 13, 14 y 15."

Solución paso a paso

1. Modelar el retroceso como una máquina de estados

Trata el gesto como idle → preview(progress) → committed o idle → preview(progress) → cancelled. La vista previa actualiza únicamente elementos visuales reversibles. La confirmación ejecuta la navegación, el cierre o la confirmación. La cancelación restaura una instantánea. Ninguna escritura de negocio irreversible debe ocurrir durante el período en el que el usuario aún puede soltar el gesto.

2. Migrar las API de interceptación heredadas

La ruta de compatibilidad de Android consiste en actualizar AndroidX Activity y registrar un OnBackPressedCallback con OnBackPressedDispatcher. Deja de interceptar en Activity.onBackPressed o KeyEvent.KEYCODE_BACK. Utiliza OnBackInvokedCallback cuando se requiera integración de retroceso a nivel de plataforma. KEYCODE_BACK todavía tiene casos de uso admitidos, pero ya no debería ser el punto de entrada para interceptar el retroceso.

kotlin
val confirmCallback = object : OnBackPressedCallback(false) {
    override fun handleOnBackPressed() {
        showDiscardDialog()
    }
}

onBackPressedDispatcher.addCallback(viewLifecycleOwner, confirmCallback)
formState.collect { state ->
    confirmCallback.isEnabled = state.hasUnsavedChanges
}

El estado habilitado proviene del estado observable del formulario, no de una verificación tardía después de que se active el callback. Cuando el formulario esté limpio, deshabilita el callback para que el sistema o el componente de navegación manejen el retroceso.

3. Gestionar el progreso y la cancelación en Compose

Utiliza PredictiveBackHandler para recolectar BackEventCompat cuando una pantalla de Compose necesite una animación impulsada por el progreso. Restaura la animación y el estado temporal cuando la recolección se cancele o el gesto no se confirme. Utiliza BackHandler para la interceptación final sin progreso, de modo que las pantallas simples no carguen con lógica de gestos innecesaria.

kotlin
PredictiveBackHandler(enabled = canNavigateBack) { progress ->
    try {
        progress.collect { event -> renderPreview(event.progress) }
        navigateBack()
    } catch (cancelled: CancellationException) {
        restorePreviewState()
    }
}

Los callbacks se procesan como una pila: el último callback habilitado agregado maneja el siguiente gesto. En Compose anidado, el PredictiveBackHandler o BackHandler más interno tiene prioridad, así que evita callbacks globales ambiguos.

4. No consumir la animación del sistema

La guía de Android indica que un OnBackPressedCallback o OnBackInvokedCallback en PRIORITY_DEFAULT o PRIORITY_OVERLAY impide la animación predictiva del sistema y obliga a la aplicación a manejar la animación. Los callbacks de UI deben controlar diálogos y transiciones. El registro o la observación de negocio deben utilizar una ruta de observación que no consuma el evento, en lugar de un callback de alta prioridad que bloquee el sistema.

5. Desplegar por Activity

Configura android:enableOnBackInvokedCallback a nivel de aplicación o anúlalo para una Activity individual. Una aplicación grande puede habilitarlo para las Activities migradas mientras las pantallas heredadas se excluyen temporalmente, para luego eliminar la interceptación antigua pantalla por pantalla. Desactivar el flag ignora OnBackInvokedCallback, pero el OnBackPressedCallback de AndroidX aún puede ejecutarse, así que prueba ambas rutas por separado.

6. Probar confirmación, cancelación y casos límite

Para cada pantalla prueba: soltar antes del umbral de confirmación; soltar después del umbral; formulario modificado y limpio; callbacks anidados de Fragment/Compose; regresar desde la Activity raíz al inicio del sistema; y en Android 13, 14 y 15. Valida la pila de navegación, los datos del formulario, el estado final de la animación y el estado habilitado del callback, no solo una captura de pantalla.

Respuesta de ejemplo de alta calidad

"Modelaría el retroceso como una máquina de estados cancelable e inventariaría la interceptación heredada en Activities, Fragments y Compose. La migración de compatibilidad utiliza OnBackPressedCallback; OnBackInvokedCallback es para integración directa con la plataforma; PredictiveBackHandler es solo para animaciones de progreso en Compose. El estado habilitado del callback depende de si el formulario está modificado. La vista previa modifica elementos visuales reversibles, la cancelación los restaura y la confirmación navega o confirma.

Evitaría callbacks con prioridad predeterminada o superpuesta que consuman la animación del sistema y mantendría la animación de UI separada del registro. Una aplicación grande despliega enableOnBackInvokedCallback por Activity, realiza pruebas de confirmación y cancelación en Android 13, 14 y 15, y elimina gradualmente la interceptación de onBackPressed y KEYCODE_BACK. Los criterios de aceptación verifican la navegación, la restauración del estado, el orden de los callbacks y el comportamiento de retroceso al inicio del sistema, no solo una animación aislada."

Errores comunes

  • Error: verificar si el formulario está modificado únicamente dentro de handleOnBackPressed → Por qué falla: la vista previa ya ocurrió y la cancelación no puede restaurarse correctamente → Solución: habilita o deshabilita el callback a partir del estado observable.
  • Error: registrar un callback global PRIORITY_OVERLAY en todas partes → Por qué falla: se consume la animación predictiva del sistema → Solución: consume únicamente los eventos de UI necesarios y utiliza observación sin consumo para los registros.
  • Error: llamar únicamente a navigateBack() en Compose → Por qué falla: el estado de la vista previa permanece después de la cancelación → Solución: captura la cancelación y restaura la instantánea.
  • Error: probar únicamente en un dispositivo con Android 15 → Por qué falla: las API de compatibilidad y el comportamiento en versiones anteriores quedan sin verificar → Solución: cubre Android 13, 14, 15 y los flags a nivel de actividad.

Preguntas de seguimiento y respuestas

¿Qué debería pasar con los datos del formulario cuando el usuario suelta a mitad del gesto?

La vista previa solo altera elementos visuales temporales. Mantén el modelo del formulario y los datos persistidos sin cambios, luego restaura la instantánea visual cuando el gesto se cancele. No muestres un diálogo de guardar/descartar para un gesto que nunca se confirmó.

¿Por qué no usar un único callback global para cada pantalla?

La prioridad de los callbacks depende del orden en la pila y del estado habilitado. Un callback global puede ocultar la lógica de Fragment, Navigation o Compose y no puede conocer el estado no guardado de la pantalla actual. Los callbacks con responsabilidad única a nivel de pantalla son más fáciles de verificar.

¿Cómo registrar el retroceso sin sacrificar la animación del sistema?

No utilices un callback de prioridad predeterminada o superpuesta que consuma el evento para tareas de registro. Para la salida de la Activity raíz, utiliza un observador de navegación del sistema o señales del ciclo de vida; mantén los callbacks de UI enfocados exclusivamente en el comportamiento de la interfaz.

¿Cómo despliega esta funcionalidad una aplicación grande con múltiples Activities?

Comienza con una configuración conservadora a nivel de aplicación y luego anula enableOnBackInvokedCallback=true en las Activities migradas. Expándelo solo después de que cada Activity supere las pruebas de confirmación, cancelación, callbacks anidados y versiones anteriores.

¿Qué API de Compose es la adecuada cuando solo se necesita el retroceso final?

Utiliza BackHandler. Expresa la interceptación final con una semántica de cancelación más simple. Utiliza PredictiveBackHandler solo cuando BackEventCompat.progress impulse una animación interactiva.

Referencias

  • Android Developers: Agregar compatibilidad con el gesto de retroceso predictivo.
  • Android Developers: Diseño de retroceso predictivo.
  • Android Developers: Acerca de Predictive Back para Jetpack Compose.

Fuentes públicas

Preguntas relacionadas