Search code examples
flutterflutter-navigation

Flutter Navigation doesn't pop Navigator stack on back press


I'm using Navigator for named routes to switch pages on screen with a BottomNavigationBar. On Page 3, I can navigate to Page 4 by NavigationKey.currentState.pushNamed(Page4Route).

On Page 4, I'm able to navigate back to Page 3 by calling NavigationKey.currentState.pop, but pressing the back button of the device closes the app instead. Any idea why the back button doesn't pop the current screen on the Navigation stack? How can I handle this better?

Widget build(BuildContext context) {
  return Scaffold(
    body: Navigator(
      key: navigationKey,
      initialRoute: Pages.home,
      onGenerateRoute: (RouteSettings settings) {
        WidgetBuilder builder;
        // Manage your route names here
        switch (settings.name) {
          case Pages.home:
            builder = (BuildContext context) => _page1();
            break;
          case Pages.page1:
            builder = (BuildContext context) => _page1();
            break;
          case Pages.page2:
            builder = (BuildContext context) => _page2();
            break;
          case Pages.page3:
            builder = (BuildContext context) => _page3();
            break;
          case Pages.page4:
            builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
            break;
          default:
            throw Exception('Invalid route: ${settings.name}');
        }
        return MaterialPageRoute(
          builder: builder,
          settings: settings,
       );
      },
    ),
    bottomNavigationBar: BottomNavigationBar(),
    ...
  );
}

Demo

Demo

Minimal Repro

import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nested Routing Demo',
      home: HomePage(),
    );
  }
}

class Pages{
  static const home = '/';
  static const page1 = '/page1';
  static const page2 = '/page2';
  static const page3 = '/page3';
  static const page4 = '/page4';
}

class HomePage extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<HomePage> {
  final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();
  var _currentPage = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Navigator(
        key: navigationKey,
        initialRoute: '/',
        onGenerateRoute: (RouteSettings settings) {
          WidgetBuilder builder;
          // Manage your route names here
          switch (settings.name) {
            case Pages.home:
              builder = (BuildContext context) => _page1();
              break;
            case Pages.page1:
              builder = (BuildContext context) => _page1();
              break;
            case Pages.page2:
              builder = (BuildContext context) => _page2();
              break;
            case Pages.page3:
              builder = (BuildContext context) => _page3();
              break;
            case Pages.page4:
              builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
              break;
            default:
              throw Exception('Invalid route: ${settings.name}');
          }
          // You can also return a PageRouteBuilder and
          // define custom transitions between pages
          return MaterialPageRoute(
            builder: builder,
            settings: settings,
          );
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.mood),
            label: 'Page 1',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.connect_without_contact),
            label: 'Page 2',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: 'Page 3',
          ),
        ],
        currentIndex: _currentPage,
        // selectedItemColor: Colors.amber[800],
        onTap: (value) {
          /// Update page if a different tab from the current was clicked
          if (value != _currentPage)
            setState(() {
              _currentPage = value;
              switch (value) {
                case 0:
                  navigationKey.currentState!
                      .pushReplacementNamed(Pages.page1);
                  break;
                case 1:
                  navigationKey.currentState!
                      .pushReplacementNamed(Pages.page2);
                  break;
                case 2:
                  navigationKey.currentState!
                      .pushReplacementNamed(Pages.page3);
                  break;
                default:

                /// TODO Error 404 page
                  throw Exception('Invalid route: $value');
              }
            });
        },
      ),
    );
  }

  Widget _page1() {
    return Scaffold(
      body: Container(
        color: Colors.lightBlueAccent,
        child: Center(
          child: Text('Page 1'),
        ),
      ),
    );
  }

  Widget _page2() {
    return Scaffold(
      body: Container(
        color: Colors.orangeAccent,
        child: Center(
          child: Text('Page2'),
        ),
      ),
    );
  }

  Widget _page3() {
    return Scaffold(
      body: Container(
        color: Colors.lightGreenAccent,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Page 3'),
              ElevatedButton(onPressed: (){
                navigationKey.currentState!.pushNamed(Pages.page4);
              }, child: Text('Page 4')),
            ],
          ),
        ),
      ),
    );
  }
}

class Page4Screen extends StatefulWidget {
  Page4Screen({Key? key, required GlobalKey<NavigatorState> navigatorKey}) : _navigatorKey = navigatorKey, super(key: key);
  final GlobalKey<NavigatorState> _navigatorKey;
  @override
  createState() => Page4State();
}

class Page4State extends State<Page4Screen>{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // appBar: AppBar(
      //   title: Text(AppLocalizations.of(context)!.txtTitleMood),
      //   automaticallyImplyLeading: false,
      // ),
      body: Container(
        color: Colors.redAccent,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Page 4'),ElevatedButton(
                // Within the SecondScreen widget
                onPressed: () {
                  // Navigate back to the first screen by popping the current route
                  // off the stack.
                  widget._navigatorKey.currentState!.pop();
                },
                child: Text('Go Back!'),
              ),
            ],
          ),

        ),
      ),
    );
  }
}

Solution

  • The Navigator widget does not handle back buttons by default and that's your job to do it if you have defined a Navigator widget. You can catch back press by WillPopScope widget. It takes a Future<bool> Function() which will be called whenever user wants to go back. If it returns false then your default Navigator which lies in MaterialApp will not pop the current route which simply is showing your HomePage in this case. So if your nested navigator has something to pop (like Page4) then it will pop that and will prevent your main Navigator from popping your HomePage.

    class _HomeState extends State<HomePage> {
      ...
    
      @override
      Widget build(BuildContext context) {
        return WillPopScope(
          onWillPop: () async => !(await navigationKey.currentState!.maybePop()),
          child: Scaffold(
            ...
          ),
        );
      }
    }