67

While implementing persistent bottom bar, previous route need to be restored when a button in the bottom bar was clicked.

When a button in the bottom bar is clicked, its current route path (/a/b/c) is saved and previously saved route is restored according to the button click.

Conceptually user will think each button as a workspace and its state is never get lost (including back stack). User can safely switch from one workspace to another.

How to get current route path in Flutter, when the routing is rewinding to root?

Kyaw Tun
  • 11,420
  • 9
  • 51
  • 78
  • 1
    Many things were added in Navigator 2.0: https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade For new apps consider a whole different design. – Alexey Inkin Mar 22 '21 at 10:19

5 Answers5

127

This should give you the exact route name

ModalRoute.of(context).settings.name

if you use Navigator.popuntil.. check this other answer out by Rémi Rousselet https://stackoverflow.com/a/50817399/5921185

ikben
  • 3,456
  • 3
  • 19
  • 25
25

NavigatorState doesn't expose an API for getting the path of the current route, and Route doesn't expose an API for determining a route's path either. Routes can be (and often are) anonymous. You can find out if a given Route is on the top of the navigator stack right now using the isCurrent method, but this isn't very convenient for your use case.

I would recommend that you take a different approach to this problem and don't rewind to the root at all. Instead, use a different Navigator widget for each pane of the BottomNavigationBar. That way, you won't have to rewind the stack when switching between panes. You can wrap your Navigator widgets in Opacity and IgnorePointer widgets to hide them when they aren't supposed to be visible without destroying their stack.

app video

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class SecurePage extends StatelessWidget {
  final int index;

  SecurePage(this.index);

  Widget build(BuildContext context) {
    return new Material(
      color: Colors.amber,
      child: new InkWell(
        child: new Center(
          child: new Icon(
            Icons.security,
            color: Colors.white,
            size: index * 100.0 + 20.0,
          ),
        ),
        onTap: () {
          Navigator.of(context).push(
            new MaterialPageRoute(
              builder: (BuildContext context) {
                return new SecurePage(index + 1);
              },
            ),
          );
        },
      ),
    );
  }
}

class VerifiedPage extends StatelessWidget {
  final int index;

  VerifiedPage(this.index);

  Widget build(BuildContext context) {
    return new Material(
      color: Colors.green,
      child: new InkWell(
        child: new Center(
          child: new Icon(
            Icons.verified_user,
            color: Colors.white,
            size: index * 100.0 + 20.0,
          ),
        ),
        onTap: () {
          Navigator.of(context).push(
            new MaterialPageRoute(
              builder: (BuildContext context) {
                return new VerifiedPage(index + 1);
              },
            ),
          );
        },
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  int _page = 0;
  List<Widget> initialWidgets = <Widget>[
    new SecurePage(0),
    new VerifiedPage(0),
  ];

  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Stack(
        children: new List<Widget>.generate(initialWidgets.length, (int index) {
          return new IgnorePointer(
            ignoring: index != _page,
            child: new Opacity(
              opacity: _page == index ? 1.0 : 0.0,
              child: new Navigator(
                onGenerateRoute: (RouteSettings settings) {
                  return new MaterialPageRoute(
                    builder: (_) => initialWidgets[index],
                  );
                },
              ),
            ),
          );
        }),
      ),
      bottomNavigationBar: new BottomNavigationBar(
        currentIndex: _page,
        onTap: (int index) {
          setState(() {
            _page = index;
          });
        },
        items: <BottomNavigationBarItem>[
          new BottomNavigationBarItem(
            icon: new Icon(Icons.security),
            title: new Text('Secure'),
          ),
          new BottomNavigationBarItem(
            icon: new Icon(Icons.verified_user),
            title: new Text('Verified'),
          ),
        ],
      ),
    );
  }
}
alfiepoleon
  • 1,458
  • 15
  • 18
Collin Jackson
  • 97,818
  • 28
  • 207
  • 143
  • 1
    Wow! Beautify. This solution also solve other state problem I am having. – Kyaw Tun Sep 30 '17 at 04:26
  • In fact, I was tinkering around `Stack` and `Opacity`, but not able to get it work. I was not aware `Navigator` can be localised. `IgnorePointer` is another trick I was not able to figure out. Combining the two, a build-in [Hide-Show Widget](https://stackoverflow.com/questions/44489804/show-hide-widgets-on-flutter-programmatically) will be good, to make them discoverable. – Kyaw Tun Sep 30 '17 at 04:28
  • This fantastic solution has [other issue](https://stackoverflow.com/questions/46500186/flutter-keyboard-issue-when-wrap-under-stack-opacity-scrollable). – Kyaw Tun Sep 30 '17 at 06:28
  • @CollinJackson What happens if `IgnorePointer` isn't included? I read the documentation but am still unsure how it relates to the navigation stack – geg Oct 22 '20 at 23:33
13

Posting this answer mainly for archival purposes, but as @ikben mentioned, one way to get the current route, and all its properties, is ModalRoute.of(context). This returns a ModalRoute, which despite its name, applies to most Navigator.push calls, not just showDialog. Helpful properties include Route.settings, Route.navigator, and Route.isFirst.

Levi Lesches
  • 1,256
  • 1
  • 12
  • 21
13

In case if you want to get current route by use navigator key, possible to use popUntil method for it:

String? currentPath;
navigatorKey.currentState?.popUntil((route) {
  currentPath = route.settings.name;
  return true;
}); 
StasVo
  • 254
  • 3
  • 4
  • Perfect answer when using nested navigator key, well play !!! – Eng Jan 06 '22 at 08:31
  • 1
    I couldn't figure out why all my returned names are null. Then I found out that I needed to pass the 'settings' property when building the routes manually using onGenerateRoute method and returning MaterialPageRoute( builder: (context) => YourWidget(), settings: RouteSettings(name: "payment") ) – Dan Apr 04 '22 at 04:32
  • This is a sweet hack, take my +1 – Hemant_Negi May 03 '22 at 14:28
0

I found a much simpler solution. I love StatelessWidget's so I did it with a static, but you can do it with a StatefulWidget. You'll need a StatefulWidget if you have hierarchical navigation. This is a tile in my NavDrawer. (Statics get a bad rap but in a UI it's not bad, there's only one process running a single thread.)

class NavListTile extends StatelessWidget {
  static String currentRoute = 'dashboard';
  final String route;

  const NavListTile({
    Key? key,
    required this.route,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var isCurrent = currentRoute == route;
    String suffix = isCurrent ? '_green' : '';
    return ListTile(
      title: NavMenuItem(capitalizedRoute, isCurrent,
          "assets/images/" + route + suffix + ".png", context),
      onTap: () {
        currentRoute = route;
        Navigator.of(context).pushNamed('/' + route);
      },
    );
  }
}
Michael Bushe
  • 1,375
  • 14
  • 16