135
votes

I want to develop a logout button that will send me to the log in route and remove all other routes from the Navigator. The documentation doesn't seem to explain how to make a RoutePredicate or have any sort of removeAll function.

13

13 Answers

322
votes

I was able to accomplish this with the following code:

Navigator.of(context)
    .pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);

The secret here is using a RoutePredicate that always returns false (Route<dynamic> route) => false. In this situation it removes all of the routes except for the new /login route I pushed.

103
votes

i can done with the following code snippet :

 Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) =>
    LoginScreen()), (Route<dynamic> route) => false),

if you want to remove all the route below the pushed route, RoutePredicate always return false, e.g (Route route) => false.

37
votes

Another alternative is popUntil()

Navigator.of(context).popUntil(ModalRoute.withName('/root'));

This will pop all routes off until you are back at the named route.

24
votes

Another solution is to use pushAndRemoveUntil(). To remove all other routes use ModalRoute.withName('/')

Navigator.pushAndRemoveUntil(
    context,   
    MaterialPageRoute(builder: (BuildContext context) => Login()), 
    ModalRoute.withName('/')
);

Reference: https://api.flutter.dev/flutter/widgets/NavigatorState/pushAndRemoveUntil.html

20
votes

In case you want to go back to the particular screen and you don't use named router can use the next approach

Example:

Navigator.pushAndRemoveUntil(context,
                  MaterialPageRoute(builder: (BuildContext context) => SingleShowPage()),
                  (Route<dynamic> route) => route is HomePage
              );

With route is HomePage you check the name of your widget.

13
votes

If you are using namedRoutes, you can do this by simply :

Navigator.pushNamedAndRemoveUntil(context, "/login", (Route<dynamic> route) => false);

Where "/login" is the route you want to push on the route stack.

Note That :

This statement removes all the routes in the stack and makes the pushed one the root.

8
votes

I don't know why no one mentioned the solution using SchedularBindingInstance, A little late to the party though, I think this would be the right way to do it originally answered here

SchedulerBinding.instance.addPostFrameCallback((_) async {
   Navigator.of(context).pushNamedAndRemoveUntil(
      '/login',
      (Route<dynamic> route) => false);
});

The above code removes all the routes and naviagtes to '/login' this also make sures that all the frames are rendered before navigating to new route by scheduling a callback

6
votes

Not sure if I'm doing this right

but this suits my use-case of popping until by root widget

void popUntilRoot({Object result}) {
    if (Navigator.of(context).canPop()) {
      pop();
      popUntilRoot();
    }
}
3
votes

This is working for me. Actually, I was working with bloc but my issue was login screen bloc. It was not updating after logout. It was holding the previous model data. Even, I entered the wrong entry It was going to Home Screen.

Step 1:

Navigator.of(context).pushNamedAndRemoveUntil(
        UIData.initialRoute, (Route<dynamic> route) => false);

where, UIData.initialRoute = "/" or "/login"

Step 2:

It's working to refresh the screen. If you are working with Bloc then It will very helpful.

runApp(MyApp());

where, MyApp() is the root class.

Root class (i.e. MyApp) code

class MyApp extends StatelessWidget {

  final materialApp = Provider(
      child: MaterialApp(
          title: UIData.appName,
          theme: ThemeData(accentColor: UIColor().getAppbarColor(),
            fontFamily: UIData.quickFont,
          ),
          debugShowCheckedModeBanner: false,
          //home: SplashScreen(),
          initialRoute: UIData.initialRoute,
          routes: {
            UIData.initialRoute: (context) => SplashScreen(),
            UIData.loginRoute: (context) => LoginScreen(),
            UIData.homeRoute: (context) => HomeScreen(),
          },
          onUnknownRoute: (RouteSettings rs) => new MaterialPageRoute(
              builder: (context) => new NotFoundPage(
                appTitle: UIData.coming_soon,
                icon: FontAwesomeIcons.solidSmile,
                title: UIData.coming_soon,
                message: "Under Development",
                iconColor: Colors.green,
              )
          )));

  @override
  Widget build(BuildContext context) {
    return materialApp;
  }
}

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

Here is My Logout method,

void logout() async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    preferences.clear();

    // TODO: we can use UIData.loginRoute instead of UIData.initialRoute
    Navigator.of(context).pushNamedAndRemoveUntil(
        UIData.initialRoute, (Route<dynamic> route) => false);
    //TODO: It's working as refresh the screen
    runApp(MyApp());
  }
2
votes

First see chrislondon answer, and then know that you can also do this, if you do not have access to the (context).

navigatorKey.currentState.pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);  
2
votes

In my case this solution works:

Navigator.pushNamedAndRemoveUntil(" The Route you want to go to " , (Route route) => false);
1
votes
to clear route - 

  onTap: () {
                    //todo to clear route -
                    Navigator.of(context).pop();
                    Navigator.push(context, MaterialPageRoute(builder: (context) => UpdateEmployeeUpdateDateActivity(_token),));
                    widget.listener.onEmployeeDateClick(_day,_month, _year);
}
-1
votes

This is a good question , if you aren't aware of "pushNamedAndRemoveUntil" !

You want to remove every route that is in the Navigator stack. So, your condition must always return false. Which could be achieved like this:-

Navigator.of(context).pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);