flutter/examples/api/lib/material
Justin McCandless dedd100ebd
Predictive back support for root routes (#120385)
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 https://github.com/flutter/flutter/issues/109513
Depends on engine PR https://github.com/flutter/engine/pull/39208 ✔️ 
Design doc: https://docs.google.com/document/d/1BGCWy1_LRrXEB6qeqTAKlk-U2CZlKJ5xI97g45U7azk/edit#
Migration guide: https://github.com/flutter/website/pull/8952
2023-08-04 20:44:44 +00:00
..
about Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
action_buttons Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
action_chip Enable dangling_library_doc_comments and library_annotations lints (#117365) 2022-12-20 16:03:21 -08:00
animated_icon Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
app_bar Replaces textScaleFactor with TextScaler (#128522) 2023-07-17 17:56:07 +00:00
autocomplete Fix bug in Autocomplete example (#127219) 2023-05-22 16:55:21 +00:00
banner Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
bottom_app_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
bottom_navigation_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
bottom_sheet Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
button_style Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
card Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
checkbox Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
checkbox_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
choice_chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
color_scheme Remove dead code (#126266) 2023-05-09 15:47:16 +00:00
context_menu Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
data_table Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
date_picker Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
dialog Adaptive alert dialog (#124336) 2023-04-18 23:00:03 +00:00
divider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
drawer Add missing example links (#130521) 2023-07-17 18:24:49 +00:00
dropdown Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
dropdown_menu Update menu API docs to help developers migrate to m3 (#128351) 2023-06-07 13:21:12 +00:00
elevated_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
expansion_panel Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
expansion_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
filled_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
filter_chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
flexible_space_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
floating_action_button Revised Floating Action Button examples (#128058) 2023-06-01 22:16:48 +00:00
floating_action_button_location Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
icon_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
ink Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
ink_well Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
input_chip Normalize examples (#111223) 2022-09-09 21:17:11 +00:00
input_decorator Updated InputDecoratorExamples for M3 (#128065) 2023-06-01 15:22:03 -07:00
list_tile Fix typos in ListTile examples. (#129606) 2023-06-29 06:29:03 +00:00
material_state Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
menu_anchor Disable context menu (#128365) 2023-06-07 23:40:17 +00:00
navigation_bar Predictive back support for root routes (#120385) 2023-08-04 20:44:44 +00:00
navigation_drawer Add missing example links (#130521) 2023-07-17 18:24:49 +00:00
navigation_rail Add Badge widget to NavigationBar and NavigationRail examples (#129834) 2023-07-11 09:30:05 +00:00
outlined_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
page_transitions_theme Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
platform_menu_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
popup_menu Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
progress_indicator Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
radio Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
radio_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
range_slider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
refresh_indicator Refactor refresh_indicator.1.dart to not use shrinkwrap (#129377) 2023-07-11 20:04:17 +00:00
reorderable_list Add a ReorderableListView example with cards + cleanup existing tests (#126155) 2023-05-05 16:39:11 +00:00
scaffold Add missing example links (#130521) 2023-07-17 18:24:49 +00:00
scrollbar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
search_anchor Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
segmented_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
selectable_region Selection area right click behavior should match native (#128224) 2023-06-21 19:32:04 +00:00
selection_area Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
selection_container Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
slider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
snack_bar Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
stepper Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
switch Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
switch_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
tab_controller Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
tabs Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
text_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
text_field Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
text_form_field Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
theme Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
theme_data Updated the ThemeData API example (#130954) 2023-07-20 13:12:34 -07:00
time_picker Replaces textScaleFactor with TextScaler (#128522) 2023-07-17 17:56:07 +00:00
toggle_buttons Updated TabBar and ToggleButtons examples (#128088) 2023-06-02 01:05:31 +00:00
tooltip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00