Flutter: How to switch to a page which already is in the Navigator stack?

Viewed 4670

I have a drawer with named routes to different search pages. Pushing a route navigates to a search page. Each item in a search page navigates to a detail page. The back button pops the detail page and I'm back on the search page. This scenario makes the proper use of the navigator stack.

But how can I switch between multiple search pages? I can only pop one search page and then push another one.

Another use case I'm looking for is to switch between detail pages. In this case, the Navigator would hold (in this order): first search page, first detail page, second search page, second detail page. I want to switch between the two detail pages.

Is there any way to peek into the Navigator stack and bring the peeked entry to the top of the stack?

Do multiple Navigators help here? Can I switch between Navigators?

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

GlobalKey<NavigatorState> navKey = GlobalKey( );

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

class MyApp extends StatelessWidget {

    final Provider<MyMenu> menuProvider = Provider(
            builder: ( context ) => MyMenu( ) );

    @override
    Widget build( BuildContext context ) {
        return MultiProvider(
            child: MaterialApp(
                title: 'My App',
                home: MyHomePage( ),
                navigatorKey: navKey,
                routes: {
                    routePage1: ( context ) => Page1( ),
                    routePage2: ( context ) => Page2( ),
                },
                theme: ThemeData( primarySwatch: Colors.blue, ),
            ),
            providers: [
                menuProvider,
            ], );
    }
}

class MyMenu extends StatelessWidget {

    @override
    Widget build( BuildContext context ) {
        String currentRoute = getCurrentRouteName( );

        return Drawer( child: ListView( children: <Widget>[
            ListTile(
                leading: Icon( Icons.home ),
                onTap: ( ) => navigateTo( context, routePage1 ),
                selected: currentRoute == routePage1,
                title: Text( "Page 1" ),
            ),
            ListTile(
                onTap: ( ) => navigateTo( context, routePage1 ),
                selected: currentRoute == routePage1,
                title: Text( "Page 1" ),
            ),
            ListTile(
                onTap: ( ) => navigateTo( context, routePage2 ),
                selected: currentRoute == routePage2,
                title: Text( "Page 2" ),
            ),
        ],
        ),
        );
    }
}

String getCurrentRouteName( ) {
    String currentRouteName;
    navKey.currentState.popUntil( ( route ) {
        currentRouteName = route.settings.name;
        return true;
    } );
    return currentRouteName;
}

void navigateTo( BuildContext context, String namedRoute, { Object arguments } ) {
    // --- Close drawer menu if open
    if ( Scaffold
            .of( context )
            .isDrawerOpen ) {
        navKey.currentState.pop( );
    }

    // --- No navigation if target page already active
    String currentRoute = getCurrentRouteName( );
    if ( currentRoute == namedRoute ) return;

    // --- Navigate to target
    if ( currentRoute == "/" ) {
        navKey.currentState.pushNamed( namedRoute, arguments: arguments );
    } else {
        navKey.currentState.popAndPushNamed( namedRoute, arguments: arguments );
    }
}

class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
    @override
    Widget build( BuildContext context ) {
        String currentRoute = getCurrentRouteName( );
        return AppBar(
            leading: MenuButton( ),
            title: Text( 'my app -> ${currentRoute}' ), );
    }

    @override
    Size get preferredSize => Size.fromHeight( kToolbarHeight );
}

class MenuButton extends StatelessWidget {
    @override
    Widget build( BuildContext context ) {
        return IconButton(
            icon: Icon( Icons.menu ),
            onPressed: ( ) {
                ScaffoldState scaffold = Scaffold.of( context );
                if ( scaffold.isDrawerOpen ) {
                    navKey.currentState.pop( );
                } else {
                    scaffold.openDrawer( );
                }
            },
        );
    }
}

class MyHomePage extends StatelessWidget {
    @override
    Widget build( BuildContext context ) {
        return Scaffold(
            appBar: MyAppBar( ),
            body: Center(
                child: Text( "This is the Homepage" ),
            ),
            drawer: Provider.of<MyMenu>( context ),
        );
    }
}

const String routePage1 = '/page1';

class Page1 extends StatelessWidget {
    @override
    Widget build( BuildContext context ) {
        return Scaffold(
            appBar: MyAppBar( ),
            body: Center(
                child: Text( "This is Page 1" ),
            ),
            drawer: Provider.of<MyMenu>( context ),
        );
    }
}

const String routePage2 = '/page2';

class Page2 extends StatelessWidget {
    @override
    Widget build( BuildContext context ) {
        return Scaffold(
            appBar: MyAppBar( ),
            body: Center(
                child: Text( "This is Page 2" ),
            ),
            drawer: Provider.of<MyMenu>( context ),
        );
    }
}
1 Answers

Create a field typed Widget and replace those widgets on menu item like below code.

 enum DrawerSelection {
  home,
  cropFamilies,
  cropCalender,
  favorites,
  settings,
  notes,
  contactUs,
  disclaimer
 }

class _MyHomePage extends State<MyHomePage> {

DrawerSelection _drawerSelection = DrawerSelection.home;
Widget _appBarTitle = Text("Home");


Widget _currentWidget = MainWidget();

ListTile(
        selected: _drawerSelection == DrawerSelection.home,
        title: Text('Home'),
        leading: Icon(Icons.home),
        onTap: () {
          _drawerSelection = DrawerSelection.home;
          Navigator.pop(context);
          _currentWidget = MainWidget();
          setState(() {
            _appBarTitle = Text("Home");
          });
        },
      ),
  ListTile(
        selected: _drawerSelection == DrawerSelection.cropFamilies,
        title: Text('Crop families فصل کا خاندان'),
        leading: Icon(Icons.nature),
        onTap: () {
          _drawerSelection = DrawerSelection.cropFamilies;
          Navigator.pop(context);
          _currentWidget = CropFamiliesWidget();
          setState(() {
            _appBarTitle = Text("Crop families فصل کا خاندان");
          });
        },
      ),

Do this in all your menu items. You can ignore drawer selection if you don't want to show selected color in drawer menu.

update If you want to navigate back in stack to a desired route you can do that with popUntil method like below.see here

  Navigator.popUntil(context, ModalRoute.withName('/login'));
Related