-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migration guide for predictive back #8952
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some edits. Once it's certain that it's going into the release, it can be merged into the 3.13 branch. I'm also adding the appropriate label.
|
||
With predictive back, the back animation begins immediately when the | ||
user initiates the gesture and before it has been committed. There is no | ||
opportunity for the Flutter app to decide whether or not it is allowed to happen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
opportunity for the Flutter app to decide whether or not it is allowed to happen | |
opportunity for the Flutter app to decide whether it's allowed to happen |
These replace parameters that corresponded with `WillPopScope` and now are used | ||
with `PopScope` in the same was as above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fields? Properties? Methods? It seems like one is a property and the other is a method?
These replace parameters that corresponded with `WillPopScope` and now are used | |
with `PopScope` in the same was as above. | |
These fields replace parameters that corresponded with `WillPopScope` | |
and are now used with `PopScope` in the same way as above. |
These are used internally to register `PopScope` widgets, so that they are taken | ||
into consideration when the route decides whether or not it can pop. This may be | ||
used if implementing a custom `PopScope` widget. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are used internally to register `PopScope` widgets, so that they are taken | |
into consideration when the route decides whether or not it can pop. This may be | |
used if implementing a custom `PopScope` widget. | |
Use these methods to register `PopScope` widgets, | |
to be evaluated when the route decides | |
whether it can pop. This functionality might be | |
used when implementing a custom `PopScope` widget. |
`PopScope` is a direct replacement for `WillPopScope`. Instead of deciding | ||
whether or not a pop is possible at the time it occurs, this is set ahead of | ||
time with the `canPop` boolean. It's also still possible to listen to pops by | ||
using `onPopInvoked`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`PopScope` is a direct replacement for `WillPopScope`. Instead of deciding | |
whether or not a pop is possible at the time it occurs, this is set ahead of | |
time with the `canPop` boolean. It's also still possible to listen to pops by | |
using `onPopInvoked`. | |
The `PopScope` class directly replaces `WillPopScope`. | |
Instead of deciding whether a pop is possible at the time it occurs, | |
this is set ahead of time with the `canPop` boolean. | |
You can still listen to pops by using `onPopInvoked`. |
Flutter's just-in-time navigation APIs, like `WillPopScope` and | ||
`Navigator.willPop`, are being replaced with a set of ahead-of-time APIs in | ||
order to support Android 14's Predictive Back feature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flutter's just-in-time navigation APIs, like `WillPopScope` and | |
`Navigator.willPop`, are being replaced with a set of ahead-of-time APIs in | |
order to support Android 14's Predictive Back feature. | |
To support Android 14's Predictive Back feature, | |
a set of ahead-of-time APIs have replaced just-in-time | |
navigation APIs, like `WillPopScope` and `Navigator.willPop`. |
Android 14 introduced the | ||
[Predictive Back feature](https://developer.android.com/guide/navigation/predictive-back-gesture), | ||
which allows the user to peek behind the current route during a valid back | ||
gesture and decide whether or not to continue back or to cancel the gesture. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gesture and decide whether or not to continue back or to cancel the gesture. | |
gesture and decide whether to continue back or to cancel the gesture. |
`Form` used to use a `WillPopScope` under the hood and expose its `onWillPop` | ||
method. It has been replaced with a `PopScope` and has exposed its `canPop` and | ||
`onPopInvoked` methods. Migrating is identical to migrating from `WillPopScope` | ||
to `PopScope`, detailed above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`Form` used to use a `WillPopScope` under the hood and expose its `onWillPop` | |
method. It has been replaced with a `PopScope` and has exposed its `canPop` and | |
`onPopInvoked` methods. Migrating is identical to migrating from `WillPopScope` | |
to `PopScope`, detailed above. | |
Previously, `Form` used a `WillPopScope` instance under the hood | |
and exposed its `onWillPop` method. This has been replaced with a | |
`PopScope` that exposes its `canPop` and `onPopInvoked` methods. | |
Migrating is identical to migrating from `WillPopScope` | |
to `PopScope`, detailed above. |
`removeScopedWillPopCallback`. Since `WillPopScope` has been replaced by | ||
`PopScope`, these methods have been replaced by `registerPopInterface` and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`removeScopedWillPopCallback`. Since `WillPopScope` has been replaced by | |
`PopScope`, these methods have been replaced by `registerPopInterface` and | |
`removeScopedWillPopCallback`. Since `PopScope` replaces `WillPopScope`, | |
these methods have been replaced by `registerPopInterface` and |
Thanks! Changes made. I'll plan to merge this after the flutter/flutter PR goes in. |
This PR aims to support Android's predictive back gesture when popping the entire Flutter app. Predictive route transitions between routes inside of a Flutter app will come later. <img width="200" src="https://user-images.githubusercontent.com/389558/217918109-945febaa-9086-41cc-a476-1a189c7831d8.gif" /> ### Trying it out If you want to try this feature yourself, here are the necessary steps: 1. Run Android 33 or above. 1. Enable the feature flag for predictive back on the device under "Developer options". 1. Create a Flutter project, or clone [my example project](https://github.com/justinmc/flutter_predictive_back_examples). 1. Set `android:enableOnBackInvokedCallback="true"` in android/app/src/main/AndroidManifest.xml (already done in the example project). 1. Check out this branch. 1. Run the app. Perform a back gesture (swipe from the left side of the screen). You should see the predictive back animation like in the animation above and be able to commit or cancel it. ### go_router support go_router works with predictive back out of the box because it uses a Navigator internally that dispatches NavigationNotifications! ~~go_router can be supported by adding a listener to the router and updating SystemNavigator.setFrameworkHandlesBack.~~ Similar to with nested Navigators, nested go_routers is supported by using a PopScope widget. <details> <summary>Full example of nested go_routers</summary> ```dart // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:go_router/go_router.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; void main() => runApp(_MyApp()); class _MyApp extends StatelessWidget { final GoRouter router = GoRouter( routes: <RouteBase>[ GoRoute( path: '/', builder: (BuildContext context, GoRouterState state) => _HomePage(), ), GoRoute( path: '/nested_navigators', builder: (BuildContext context, GoRouterState state) => _NestedGoRoutersPage(), ), ], ); @OverRide Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, ); } } class _HomePage extends StatelessWidget { @OverRide Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Nested Navigators Example'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Home Page'), const Text('A system back gesture here will exit the app.'), const SizedBox(height: 20.0), ListTile( title: const Text('Nested go_router route'), subtitle: const Text('This route has another go_router in addition to the one used with MaterialApp above.'), onTap: () { context.push('/nested_navigators'); }, ), ], ), ), ); } } class _NestedGoRoutersPage extends StatefulWidget { @OverRide State<_NestedGoRoutersPage> createState() => _NestedGoRoutersPageState(); } class _NestedGoRoutersPageState extends State<_NestedGoRoutersPage> { late final GoRouter _router; final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>(); // If the nested navigator has routes that can be popped, then we want to // block the root navigator from handling the pop so that the nested navigator // can handle it instead. bool get _popEnabled { // canPop will throw an error if called before build. Is this the best way // to avoid that? return _nestedNavigatorKey.currentState == null ? true : !_router.canPop(); } void _onRouterChanged() { // Here the _router reports the location correctly, but canPop is still out // of date. Hence the post frame callback. SchedulerBinding.instance.addPostFrameCallback((Duration duration) { setState(() {}); }); } @OverRide void initState() { super.initState(); final BuildContext rootContext = context; _router = GoRouter( navigatorKey: _nestedNavigatorKey, routes: [ GoRoute( path: '/', builder: (BuildContext context, GoRouterState state) => _LinksPage( title: 'Nested once - home route', backgroundColor: Colors.indigo, onBack: () { rootContext.pop(); }, buttons: <Widget>[ TextButton( onPressed: () { context.push('/two'); }, child: const Text('Go to another route in this nested Navigator'), ), ], ), ), GoRoute( path: '/two', builder: (BuildContext context, GoRouterState state) => _LinksPage( backgroundColor: Colors.indigo.withBlue(255), title: 'Nested once - page two', ), ), ], ); _router.addListener(_onRouterChanged); } @OverRide void dispose() { _router.removeListener(_onRouterChanged); super.dispose(); } @OverRide Widget build(BuildContext context) { return PopScope( popEnabled: _popEnabled, onPopped: (bool success) { if (success) { return; } _router.pop(); }, child: Router<Object>.withConfig( restorationScopeId: 'router-2', config: _router, ), ); } } class _LinksPage extends StatelessWidget { const _LinksPage ({ required this.backgroundColor, this.buttons = const <Widget>[], this.onBack, required this.title, }); final Color backgroundColor; final List<Widget> buttons; final VoidCallback? onBack; final String title; @OverRide Widget build(BuildContext context) { return Scaffold( backgroundColor: backgroundColor, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(title), //const Text('A system back here will go back to Nested Navigators Page One'), ...buttons, TextButton( onPressed: onBack ?? () { context.pop(); }, child: const Text('Go back'), ), ], ), ), ); } } ``` </details> ### Resources Fixes #109513 Depends on engine PR flutter/engine#39208 ✔️ Design doc: https://docs.google.com/document/d/1BGCWy1_LRrXEB6qeqTAKlk-U2CZlKJ5xI97g45U7azk/edit# Migration guide: flutter/website#8952
The related PRs have merged so this can be merged too now if the checks pass. |
A user requested this example be added to the migration guide [on Discord](https://discord.com/channels/608014603317936148/969301283825659914/1140422179511603290). I think it's a reasonable request. Follow up on: #8952 Framework PR: flutter/flutter#132249 --------- Co-authored-by: Parker Lougheed <parlough@gmail.com>
Migration guide for Android 14's Predictive Back feature, which deprecates and replaces several navigation APIs.
Related PRs:
flutter/flutter#120385
flutter/engine#39208
TODOs
Insert the "landed in" version number.Presubmit checklist