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
How will Flutter implement Android 13's predictive back gesture? #109513
Comments
Thanks so much for filing this issue! Just to clarify, this is a feature being introduced as part of a multi-year transition to how Android handles back gestures. While it's available to developers for the first time in Android 13 through a Developer Options setting, it's not currently accessible to users and won't be until a future Android release. So while the clock is indeed 'ticking' and we will work on enabling support for this, this is not something that Android 13 app authors need to worry about immediately. |
Thanks a lot for your quick response! That clears things up for me. I thought it would be available to regular users from the final release of Android 13 (without the need to flip the developer switch), but if that's not the case it's indeed not something to worry about for now. |
My current understanding is that for this to work correctly, the framework will have to tell the engine (or android) up front whether it is going to handle the back gesture internally or whether Android should handle it. @GaryQian I think you've been looking closer at the Android APIs. Does that match your understanding? @chunhtai Does the Navigator already tell the engine that it can handle back internally? I seem to remember that we had to add this to support back on the web? |
@goderbauer Currently flutter always tells Android it will handle the pop. If flutter then figure there is nothing else to pop, it then programmatically pop the android activity. For web, the only thing I am aware is the fake browser entry to capture the backward button. I don't think it can be reuse here. |
Thanks, so we will likely have to build something new here to tell the engine whether the framework can handle back or not upfront, if my understanding of the new Android API is correct. |
I am still not sure whether we need to implement the animation. From the screenshot, it looks like this may only apply when popping the entire app. Is there any animation change when popping the in-app page? |
There is, although support for that is farther out. |
From looking at the API, it seems that we should always be telling Android that we handle the back gesture. Regardless of whether we implement predictive back or just legacy back on the framework side, we will be doing custom handling of the back gesture via OnBackPressedCallback (which is called by the OnBackPressedDispatcher). However, we should add logic to let Android know when we run out of pages to pop. At this point, we should unregister the callback, and Android will then handle the next back as exiting the app. In any case, the opt-in is in build.gradle and needs to be preconfigured. It shouldn't be possible (or necessary) for the app to dynamically inform the build whether to enable or disable predictive backnav if it is not already set before build. We should be able to support this with a combo of Embedding API migration, framework predictive backnav implementation, and unregistering callbacks on final framework pop. This is from an initial study of the API, I'll do some experimentation to see how it actually handles. |
Hi all. Just in case it's of any help, as I had already done some limited testing on my Pixel 4 running the Android 13 beta:
So I think everything GaryQian said matches with my experience. |
This will be fixed by #109558. Enabling the opt-in turns off the older back handling system. |
still back button detaches android app willpopscope not event catches back button event Android 13 Samsung device. |
We had this issue as well. Few reasons it might happen:
Hope it helps. |
predictive back is not yet supported by flutter, see flutter/flutter#109513
@deimantasa You have done me a great favor. Thank you. When MainActivity extends FlutterFragmentActivity, the splash screen only show once, and it does not show when back to home and reopen app. Change MainActivity extends FlutterActivity, it shows every time. |
FYI, flutter/engine#35678 updated |
Today this bug remains and all biometrics plugins cannot be used. |
This issue is assigned but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks! |
Quick status update: This issue is actively being developed in #120385, which I hope to merge soon. The engine PR flutter/engine#39208 has already been merged. Predictive back will then work at the root of Flutter apps built with the latest SDK on Android devices that support predictive back. Later I also plan to implement predictive back route transitions that work with the native back arrow, but that will come in a separate PR. Bringing this to FlutterFragmentActivity is also separate, but seems doable with a quick look at the code. |
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
Predictive back now works when leaving Flutter apps. Further work on predictive back route transitions will be tracked in #131961. Getting predictive back to work in your Flutter app
You should see the predictive back animation and be able to commit or cancel it. |
Is any issue created for this to be merged into a stable branch? I have just tested this out using the master branch. It works great but it doesn't close the app as it did before. Doing the back gesture looks like it's putting the app in the background as someone would using a home gesture. The app is not terminated using a back gesture. is this intended behaviour? |
This was reverted and I'm currently struggling to reland it here: #132249 Can you elaborate on the background problem? If I open any Android app and immediately perform a back gesture, the app seems to be backgrounded and not killed. |
Yes, so if predictive back gestures are set to false in the Android manifest, the app gets terminated. When reopening the app, it starts all the processes again. Check these recordings below. This one doesn't have android:enableOnBackInvokedCallback="true" in the android manifest so the app gets terminated on using the back gesture: When predictive gestures are offpredictive.gestures.off.mp4This one has android:enableOnBackInvokedCallback="true" so the app appears in background mode so reopening the app launches in the last state: When predictive gestures are onpredictive.gestures.on.mp4I feel it is the intended behaviour of using predictive back gestures because the demo provided in the official page also doesn't kill the application. |
@milindgoel15 I confirmed what you're saying by trying this on a native Android app with and without predictive back enabled, and then trying it on a Flutter app. However, it seems to happen for me on master even though predictive back has been reverted, so I don't think it's directly related to PR #132249. I've created a new issue for this: #132692. |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Internal: b/242216228
Use case
According to this page, we should get our apps ready for the new predictive back gesture that will be introduced in Android 13.
However, with just a few weeks to go before the final release, I haven't seen any info on how this will be handled in Flutter apps?
I have tried the simple trick of just adding
android:enableOnBackInvokedCallback="true"
to my Android Manifest, which does indeed activate the new animation on my Pixel 4 running the Android 13 beta. However, because all the back gestures are now captured by the system rather than Flutter, it completely breaks Flutter's navigation system, as ALL back gestures will now send you back to your phone's home screen, rather than taking you one level up Flutter's navigation tree when appropriate.Proposal
I'm merely asking for information here, as I've searched extensively but couldn't find anything pertaining Flutter.
Will we have to do anything to adapt our apps? Or will the Flutter team implement this at some point at framework level? If it's the latter, when can we expect this to be added? Android 13 is right around the corner.
I'm assuming this should be handled at framework level, so that Flutter automatically implements this animation when navigating inside's Flutter's own tree, and hands it over to the system when the user is already at the root of the navigation stack (so they can correctly peek at their home screen before completing the gesture).
Eager to get some info on this!
The text was updated successfully, but these errors were encountered: