Skip to content
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

Closed
Tracked by #132504
victor-marino opened this issue Aug 14, 2022 · 23 comments · Fixed by #120385
Closed
Tracked by #132504

How will Flutter implement Android 13's predictive back gesture? #109513

victor-marino opened this issue Aug 14, 2022 · 23 comments · Fixed by #120385
Assignees
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter customer: crowd Affects or could affect many people, though not necessarily a specific customer. e: OS-version specific Affects only some versions of the relevant operating system engine flutter/engine repository. See also e: labels. f: gestures flutter/packages/flutter/gestures repository. f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. multiteam-retriage-candidate P1 High-priority issues at the top of the work list platform-android Android applications specifically r: fixed Issue is closed as already fixed in a newer version team-android Owned by Android platform team

Comments

@victor-marino
Copy link

victor-marino commented Aug 14, 2022

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!

@danagbemava-nc danagbemava-nc added in triage Presently being triaged by the triage team c: new feature Nothing broken; request for a new capability platform-android Android applications specifically framework flutter/packages/flutter repository. See also f: labels. f: gestures flutter/packages/flutter/gestures repository. e: OS-version specific Affects only some versions of the relevant operating system c: proposal A detailed proposal for a change to Flutter and removed in triage Presently being triaged by the triage team labels Aug 15, 2022
@timsneath
Copy link
Contributor

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.

@victor-marino
Copy link
Author

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.

@danagbemava-nc danagbemava-nc added the customer: crowd Affects or could affect many people, though not necessarily a specific customer. label Aug 15, 2022
@HansMuller HansMuller added the f: material design flutter/packages/flutter/material repository. label Aug 16, 2022
@goderbauer goderbauer added engine flutter/engine repository. See also e: labels. P2 P1 High-priority issues at the top of the work list and removed P2 labels Aug 18, 2022
@goderbauer
Copy link
Member

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?

@chunhtai
Copy link
Contributor

@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.

@goderbauer
Copy link
Member

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.

@chunhtai
Copy link
Contributor

cc @HansMuller @hangyujin

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?

@HansMuller
Copy link
Contributor

There is, although support for that is farther out.

@GaryQian
Copy link
Contributor

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.

@victor-marino
Copy link
Author

victor-marino commented Aug 19, 2022

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:

  1. Even if I flip the developer switch for the predictive back gesture on my phone, my app won't show the gesture at all unless I have explicitly opted-in in the build.gradle, not even for the final gesture to go back to the homescreen. So unless something is done on Flutter/app side, the gesture will definitely not appear at all.
  2. If I opt-in in the build.gradle for my app, I do get the gesture working to back out of my app and into the homescreen. Bad news is, now EVERY back gesture takes me out of my app, even if there are still pages left to pop inside my Flutter app. I assume now every back gesture is being handled by the OS, so it just breaks Flutter's internal navigation.

So I think everything GaryQian said matches with my experience.

@stuartmorgan
Copy link
Contributor

2. now EVERY back gesture takes me out of my app, even if there are still pages left to pop inside my Flutter app. I assume now every back gesture is being handled by the OS

This will be fixed by #109558. Enabling the opt-in turns off the older back handling system.

@fullflash
Copy link

still back button detaches android app willpopscope not event catches back button event

Android 13 Samsung device.
latest flutter sdk

@deimantasa
Copy link

still back button detaches android app willpopscope not event catches back button event

Android 13 Samsung device. latest flutter sdk

We had this issue as well. Few reasons it might happen:

  1. Check your AndroidManifest.xml and remove if you've android:enableOnBackInvokedCallback="true". We had it for some reason (I guess is a leftover from the past), which caused this issue
  2. Check your MainActivity (java or kotlin). It might extend FlutterFragmentActivity. If android:enableOnBackInvokedCallback is true in your manifest and FlutterFragmentActivity is extended, it seems to not work as well. However, what we've noticed that if android:enableOnBackInvokedCallback is set to true and MainActivity extends FlutterActivity - back button works as expected

Hope it helps.

dreautall added a commit to dreautall/waterfly-iii that referenced this issue Jun 2, 2023
predictive back is not yet supported by flutter, see
flutter/flutter#109513
@Akatsukishen
Copy link

@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.

@math1man
Copy link
Contributor

FYI, flutter/engine#35678 updated FlutterActivity but not FlutterFragmentActivity. That's why the latter doesn't work.

@RedsAnDev
Copy link

FYI, flutter/engine#35678 updated FlutterActivity but not FlutterFragmentActivity. That's why the latter doesn't work.

Today this bug remains and all biometrics plugins cannot be used.

@flutter-triage-bot flutter-triage-bot bot added multiteam-retriage-candidate team-android Owned by Android platform team triaged-android Triaged by Android platform team and removed triaged-android Triaged by Android platform team labels Jul 8, 2023
@flutter-triage-bot
Copy link

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!

@justinmc
Copy link
Contributor

justinmc commented Jul 25, 2023

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.

auto-submit bot pushed a commit that referenced this issue Aug 4, 2023
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
@justinmc
Copy link
Contributor

justinmc commented Aug 4, 2023

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

  1. Run Android 33 or above.
  2. Enable the feature flag for predictive back on the device under "Developer
    options".
  3. Set android:enableOnBackInvokedCallback="true" in
    android/app/src/main/AndroidManifest.xml.
  4. Use the latest master branch of Flutter (or, later, make sure your version of Flutter is recent enough to include Predictive back support for root routes #120385).
  5. Run the app. Perform a back gesture (swipe from the left side of the
    screen).

You should see the predictive back animation and be able to commit or cancel it.

@danagbemava-nc danagbemava-nc added the r: fixed Issue is closed as already fixed in a newer version label Aug 7, 2023
@milindgoel15
Copy link

milindgoel15 commented Aug 14, 2023

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?

@justinmc
Copy link
Contributor

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.

@milindgoel15
Copy link

milindgoel15 commented Aug 16, 2023

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 off
predictive.gestures.off.mp4

This 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 on
predictive.gestures.on.mp4

I 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.

@justinmc
Copy link
Contributor

@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.

@github-actions
Copy link

github-actions bot commented Sep 4, 2023

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 flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter customer: crowd Affects or could affect many people, though not necessarily a specific customer. e: OS-version specific Affects only some versions of the relevant operating system engine flutter/engine repository. See also e: labels. f: gestures flutter/packages/flutter/gestures repository. f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. multiteam-retriage-candidate P1 High-priority issues at the top of the work list platform-android Android applications specifically r: fixed Issue is closed as already fixed in a newer version team-android Owned by Android platform team
Projects
None yet
Development

Successfully merging a pull request may close this issue.