Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use nested Navigator with WillPopScope in Flutter

In my application, I want to have a custom Navigation (only change part of the screen and keep an history of what I'm doing inside it). For that purpose, I am using a Navigator and it's working fine for simple navigation. However, I want to handle the back button of Android. There is a problem with it in Flutter apparently which forces me to handle the backbutton in the parent widget of the Navigator : https://github.com/flutter/flutter/issues/14083

Due to that, I need to retrieve the instance of my Navigator in the children and call pop() on it. I am trying to use a GlobalKey for this.

I am trying to make it work for a while now and made a sample project just to test this. Here is my code :

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(title: 'Navigation Basics', home: MainWidget()));
}

class MainWidget extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: WillPopScope(
            onWillPop: () => navigatorKey.currentState.maybePop(),
            child: Scaffold(
                body: Padding(
              child: Column(
                children: <Widget>[
                  Text("Toto"),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Expanded(
                          child: RaisedButton(
                        child: Text('First'),
                        onPressed: () {
                          navigatorKey.currentState.pushNamed('/first');
                          // Navigator.push(
                          //   context,
                          //   MaterialPageRoute(builder: (context) => SecondRoute()),
                          // );
                        },
                      )),
                      Expanded(
                          child: RaisedButton(
                        child: Text('Second'),
                        onPressed: () {
                          navigatorKey.currentState.pushNamed('/second');
                        },
                      ))
                    ],
                  ),
                  Expanded(
                      child: Stack(
                    children: <Widget>[
                      Container(
                        decoration: BoxDecoration(color: Colors.red),
                      ),
                      ConstrainedBox(
                          constraints: BoxConstraints.expand(),
                          child: _getNavigator()),
                    ],
                  )),
                ],
              ),
              padding: EdgeInsets.only(bottom: 50),
            ))));
  }

  Navigator _getNavigator() {
    return Navigator(
        key: navigatorKey,
        initialRoute: '/',
        onGenerateRoute: (RouteSettings settings) {
          WidgetBuilder builder;
          switch (settings.name) {
            case '/':
              builder = (BuildContext _) => FirstRoute();
              break;
            case '/second':
              builder = (BuildContext _) => SecondRoute();
              break;
            default:
              throw new Exception('Invalid route: ${settings.name}');
          }
          return new MaterialPageRoute(builder: builder, settings: settings);
        });
  }
}

class FirstRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("GO TO FRAGMENT TWO"),
            onPressed: () => Navigator.of(context).pushNamed("/second"),
          )
        ],
      ),
      decoration: BoxDecoration(color: Colors.green),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("GO TO FRAGMENT ONE"),
            onPressed: () => Navigator.of(context).pop(),
          )
        ],
      ),
      decoration: BoxDecoration(color: Colors.blue),
    );
  }
}

This is however not working as I would like. The default Navigator seem to still be used : after opening the SecondRoute and pressing the Android back button, it just leaves the app instead of just going back to the first route.

How can I achieve what I want?

like image 315
leb1755 Avatar asked Jul 04 '19 14:07

leb1755


People also ask

Where is the WillPopScope in Flutter?

The WillPopScope widget comes with the Flutter framework. It gives us control over the back button action, allowing the current page to go back to the previous one if it meets certain requirements. This is achieved using a callback, which the widget takes in as one of its parameters.


1 Answers

Following the documentation of onWillPop:

  /// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
  ///
  /// If the callback returns a Future that resolves to false, the enclosing
  /// route will not be popped.
  final WillPopCallback onWillPop;

your handler should indicate that the enclosing route should not be closed, hence returning false will resolve your issue.

changing your handler to this works:

    onWillPop: () async {
      navigatorKey.currentState.maybePop();
      return false;
    },
like image 178
MozesM Avatar answered Oct 05 '22 01:10

MozesM