Atrás predictivo de Android
La capacidad de controlar la navegación hacia atrás en el momento en que se recibe un gesto de retroceso ha sido reemplazada por una API de navegación anticipada (ahead-of-time) para admitir la función Predictive Back de Android 14.
Resumen
#Para admitir la función Predictive Back de Android 14,
un conjunto de APIs de tipo ahead-of-time han reemplazado a las APIs de navegación just-in-time,
como WillPopScope y Navigator.willPop.
Contexto
#Android 14 introdujo la característica Predictive Back, que permite al usuario echar un vistazo detrás de la ruta actual durante un gesto de retroceso válido y decidir si continuar hacia atrás o cancelar el gesto. Esto era incompatible con las APIs de navegación de Flutter que permiten al desarrollador cancelar un gesto de retroceso después de recibirlo.
Con el atrás predictivo, la animación de retroceso comienza inmediatamente cuando el usuario inicia el gesto y antes de que se haya confirmado. No hay oportunidad para que la aplicación de Flutter decida si se permite que suceda en ese momento. Debe saberse de antemano.
Por esta razón, todas las APIs que permiten a un desarrollador de aplicaciones de Flutter cancelar una navegación hacia atrás al momento en que se recibe un gesto de retroceso ahora están obsoletas. Han sido reemplazadas por APIs equivalentes que mantienen un State booleano en todo momento que dicta si la navegación hacia atrás es posible o no. Cuando lo es, la animación de retroceso predictivo ocurre como de costumbre. De lo contrario, la navegación se detiene. En ambos casos, se informa al desarrollador de la aplicación que se intentó un retroceso y si tuvo éxito.
PopScope
#La clase PopScope reemplaza directamente a WillPopScope para habilitar el
atrás predictivo. En lugar de decidir si un pop es posible en el momento en que
ocurre, esto se establece de antemano con el booleano canPop. Aún puedes
escuchar los pops utilizando onPopInvoked.
PopScope(
canPop: _myPopDisableEnableLogic(),
onPopInvoked: (bool didPop) {
// Handle the pop. If `didPop` is false, it was blocked.
},
)
Form.canPop y Form.onPopInvoked
#Estos dos nuevos parámetros se basan en PopScope y reemplazan el parámetro obsoleto
Form.onWillPop. Se utilizan con PopScope de la misma manera que la descrita
anteriormente.
Form(
canPop: _myPopDisableEnableLogic(),
onPopInvoked: (bool didPop) {
// Handle the pop. If `didPop` is false, it was blocked.
},
)
Route.popDisposition
#Este getter devuelve de forma síncrona el RoutePopDisposition
para la ruta, que describe cómo se comportarán los pops.
if (myRoute.popDisposition == RoutePopDisposition.doNotPop) {
// Back gestures are disabled.
}
ModalRoute.registerPopEntry y ModalRoute.unregisterPopEntry
#Usa estos métodos para registrar Widgets PopScope,
que se evaluarán cuando la ruta decida si puede hacer pop.
Esta funcionalidad podría usarse al implementar un
Widget PopScope personalizado.
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
if (nextRoute != _route) {
_route?.unregisterPopEntry(this);
_route = nextRoute;
_route?.registerPopEntry(this);
}
}
Guía de migración
#Migración de WillPopScope a PopScope
#
El reemplazo directo del Widget WillPopScope es el Widget PopScope.
En muchos casos, la lógica que se ejecutaba en el momento del gesto de retroceso en
onWillPop se puede realizar en tiempo de compilación y establecer en canPop.
Código antes de la migración:
WillPopScope(
onWillPop: () async {
return _myCondition;
},
child: ...
),
Código después de la migración:
PopScope(
canPop: _myCondition,
child: ...
),
Para los casos en los que sea necesario ser notificado de que se
intentó un pop, el método onPopInvoked se puede
utilizar de manera similar a onWillPop. Ten en cuenta
que mientras onWillPop se llamaba antes de que se
manejara el pop y tenía la capacidad de cancelarlo,
onPopInvoked se llama después de que el pop ha terminado de manejarse.
Código antes de la migración:
WillPopScope(
onWillPop: () async {
_myHandleOnPopMethod();
return true;
},
child: ...
),
Código después de la migración:
PopScope(
canPop: true,
onPopInvoked: (bool didPop) {
_myHandleOnPopMethod();
},
child: ...
),
Migración de WillPopScope a NavigatorPopHandler para Navigators anidados
#Un caso de uso muy común de WillPopScope era manejar adecuadamente los
gestos de retroceso al usar Widgets Navigator anidados.
También es posible hacer esto usando PopScope,
pero ahora hay un Widget contenedor que lo hace aún más fácil:
NavigatorPopHandler.
Código antes de la migración:
WillPopScope(
onWillPop: () async => !(await _nestedNavigatorKey.currentState!.maybePop()),
child: Navigator(
key: _nestedNavigatorKey,
…
),
)
Código después de la migración:
NavigatorPopHandler(
onPop: () => _nestedNavigatorKey.currentState!.pop(),
child: Navigator(
key: _nestedNavigatorKey,
…
),
)
Migración de Form.onWillPop a Form.canPop y Form.onPopInvoked
#Anteriormente, Form utilizaba una instancia de WillPopScope internamente
y exponía su método onWillPop.
Esto ha sido reemplazado por un PopScope que expone sus
métodos canPop y onPopInvoked.
La migración es idéntica a la migración de
WillPopScope a PopScope, detallada anteriormente.
Migración de Route.willPop a Route.popDisposition
#El método willPop de Route devolvía un
Future<RoutePopDisposition> para adaptarse al hecho
de que los pops podían cancelarse. Ahora que eso ya no es así,
esta lógica se ha simplificado a un getter síncrono.
Código antes de la migración:
if (await myRoute.willPop() == RoutePopDisposition.doNotPop) {
...
}
Código después de la migración:
if (myRoute.popDisposition == RoutePopDisposition.doNotPop) {
...
}
Migración de ModalRoute.add/removeScopedWillPopCallback a ModalRoute.(un)registerPopEntry
#Internamente, ModalRoute realizaba un seguimiento de la existencia de
WillPopScopes en su subárbol de Widgets registrándolos
con addScopedWillPopCallback y
removeScopedWillPopCallback.
Dado que PopScope reemplaza a WillPopScope,
estos métodos han sido reemplazados por registerPopEntry y
unregisterPopEntry, respectivamente.
PopEntry es implementado por PopScope con el fin de exponer solo la información
mínima necesaria a ModalRoute. Cualquiera que escriba su propio PopScope
debería implementar PopEntry y registrar y desregistrar su Widget con
su ModalRoute contenedor.
Código antes de la migración:
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.onWillPop != null) {
_route?.removeScopedWillPopCallback(widget.onWillPop!);
}
_route = ModalRoute.of(context);
if (widget.onWillPop != null) {
_route?.addScopedWillPopCallback(widget.onWillPop!);
}
}
Código después de la migración:
@override
void didChangeDependencies() {
super.didChangeDependencies();
_route?.unregisterPopEntry(this);
_route = ModalRoute.of(context);
_route?.registerPopEntry(this);
}
Migración de ModalRoute.hasScopedWillPopCallback a ModalRoute.popDisposition
#Este método se utilizaba anteriormente para un caso de uso
muy similar a Predictive Back pero en la librería de Cupertino,
donde ciertas transiciones de retroceso permitían cancelar
la navegación. La transición de ruta se deshabilitaba
cuando existía incluso la posibilidad de que un Widget WillPopScope
cancelara el pop.
Ahora que la API requiere que esto se decida de antemano,
esto ya no necesita basarse especulativamente en la
existencia de Widgets PopScope. La lógica
definitiva de si un ModalRoute tiene el pop bloqueado
por un Widget PopScope está integrada en ModalRoute.popDisposition.
Código antes de la migración:
if (_route.hasScopedWillPopCallback) {
// Disable predictive route transitions.
}
Código después de la migración:
if (_route.popDisposition == RoutePopDisposition.doNotPop) {
// Disable predictive route transitions.
}
Migración de un diálogo de confirmación de retroceso
#A veces se utilizaba WillPopScope para mostrar un diálogo de confirmación
cuando se recibía un gesto de retroceso.
Esto todavía se puede hacer con PopScope con un patrón similar.
Código antes de la migración:
WillPopScope(
onWillPop: () async {
final bool? shouldPop = await _showBackDialog();
return shouldPop ?? false;
},
child: child,
)
Código después de la migración:
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) async {
if (didPop) {
return;
}
final NavigatorState navigator = Navigator.of(context);
final bool? shouldPop = await _showBackDialog();
if (shouldPop ?? false) {
navigator.pop();
}
},
child: child,
)
Soporte para el atrás predictivo
#- Ejecutar Android 14 (nivel de API 34) o superior.
- Habilita la flag de función para retroceso predictivo en el dispositivo bajo "Opciones para desarrolladores". Esto no será necesario en versiones futuras de Android.
- Establece
android:enableOnBackInvokedCallback="true"enandroid/app/src/main/AndroidManifest.xml. Si es necesario, consulta la guía completa de Android. para migrar aplicaciones de Android y admitir el retroceso predictivo. - Asegúrate de estar utilizando la versión
3.14.0-7.0.prede Flutter o superior. - Asegúrate de que tu aplicación de Flutter no utilice el
Widget
WillPopScope. Usarlo deshabilita el retroceso predictivo. Si es necesario, usaPopScopeen su lugar. - Ejecuta la aplicación y realiza un gesto de retroceso (desliza desde el lado izquierdo de la pantalla).
Timeline
#Introducido en la versión: 3.14.0-7.0.pre
En la versión estable: 3.16
Referencias
#Documentación de la API:
PopScope-
NavigatorPopHandler PopEntryForm.canPop-
Form.onPopInvoked -
Route.popDisposition -
ModalRoute.registerPopEntry -
ModalRoute.unregisterPopEntry
Issues relevantes:
PRs relevantes:
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.