Flutter - Start app with different routes depending on login state

Viewed 14787

I'm searching for a way to show a different screens on app startup depending on login state. For example, I have the following routes defined:

  • /home
  • /login
  • /settings

Naturally, I would check if the user has already logged in within the main() method and then set the initialRoute of my MaterialApp to either /login or /home. After successful login, I can call Navigator.pushReplacement to navigate to /home and the login screen is removed from the stack. Unfortunately, I always have to either define a route for / or set the home property of MaterialApp. So if I set / to a blank Container(), this Container will be on the navigation stack and the user can go back to this blank screen.

Two options I came up with are:

  • Setting the home property of MaterialApp to either HomeScreen or LoginScreen
  • Return a LoginScreen within HomeScreen's build() method, if the user has not logged in yet

Both options are feasible, but then I have to come up with some reload logic and re-set the state in order to update the home property or HomeScreen.

Any ideas what is the proper way in Flutter to deal with such cases?

2 Answers

Maybe, you could do this. Imagine you have a class Auth with an async method isLogged.

class Auth {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

  Future<bool> isLogged() async {
    try {
      final FirebaseUser user = await _firebaseAuth.currentUser();
      return user != null;
    } catch (e) {
      return false;
    }
  }
}

You could use the myApp constructor to pass the initialRoute, and decide the initial route depending on the login state. Then, you can pass the instance of myApp to runApp:

Warning: In case of error, add WidgetsFlutterBinding.ensureInitialized(); just before void main() async {. Check this issue #40253.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final Auth _auth = Auth();
  final bool isLogged = await _auth.isLogged();
  final MyApp myApp = MyApp(
    initialRoute: isLogged ? '/home' : '/',
  );
  runApp(myApp);
}

After that, you have to modify the myApp class to pass the initial route in the constructor:

class MyApp extends StatelessWidget {
  final String initialRoute;

  MyApp({this.initialRoute});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic Route Demo',
      initialRoute: initialRoute,
      routes: {
        '/': (context) => LoginPage(),
        '/home': (context) => HomePage(),
        '/settings': (context) => SettingsPage(),
      },
    );
  }
}

Hope this helps.

You should do is move to the route /login (or any other your preferred with login) and set that as initialRoute. And based on that you can modify the onGenerateInitialRoutes property of the app, to make sure that the routes are actually not pushed into Navigator history. That way you override the defaultGenerateInitialRoutes which is pushing those home state on to your Navigator. Check this code snippet from Flutter's site.

https://api.flutter.dev/flutter/widgets/Navigator/defaultGenerateInitialRoutes.html

Will copy it here as well, just in case link not works in future.

defaultGenerateInitialRoutes method List defaultGenerateInitialRoutes ( NavigatorState navigator, String initialRouteName ) Turn a route name into a set of Route objects.

This is the default value of onGenerateInitialRoutes, which is used if initialRoute is not null.

If this string contains any / characters, then the string is split on those characters and substrings from the start of the string up to each such character are, in turn, used as routes to push.

For example, if the route /stocks/HOOLI was used as the initialRoute, then the Navigator would push the following routes on startup: /, /stocks, /stocks/HOOLI. This enables deep linking while allowing the application to maintain a predictable route history.

static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
  final List<Route<dynamic>> result = <Route<dynamic>>[];
  if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
    initialRouteName = initialRouteName.substring(1); // strip leading '/'
    assert(Navigator.defaultRouteName == '/');
    List<String> debugRouteNames;
    assert(() {
      debugRouteNames = <String>[ Navigator.defaultRouteName ];
      return true;
    }());
    result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null, allowNull: true));
    final List<String> routeParts = initialRouteName.split('/');
    if (initialRouteName.isNotEmpty) {
      String routeName = '';
      for (final String part in routeParts) {
        routeName += '/$part';
        assert(() {
          debugRouteNames.add(routeName);
          return true;
        }());
        result.add(navigator._routeNamed<dynamic>(routeName, arguments: null, allowNull: true));
      }
    }
    if (result.last == null) {
      assert(() {
        FlutterError.reportError(
          FlutterErrorDetails(
            exception:
              'Could not navigate to initial route.\n'
              'The requested route name was: "/$initialRouteName"\n'
              'There was no corresponding route in the app, and therefore the initial route specified will be '
              'ignored and "${Navigator.defaultRouteName}" will be used instead.'
          ),
        );
        return true;
      }());
      result.clear();
    }
  } else if (initialRouteName != Navigator.defaultRouteName) {
    // If initialRouteName wasn't '/', then we try to get it with allowNull:true, so that if that fails,
    // we fall back to '/' (without allowNull:true, see below).
    result.add(navigator._routeNamed<dynamic>(initialRouteName, arguments: null, allowNull: true));
  }
  // Null route might be a result of gap in initialRouteName
  //
  // For example, routes = ['A', 'A/B/C'], and initialRouteName = 'A/B/C'
  // This should result in result = ['A', null,'A/B/C'] where 'A/B' produces
  // the null. In this case, we want to filter out the null and return
  // result = ['A', 'A/B/C'].
  result.removeWhere((Route<dynamic> route) => route == null);
  if (result.isEmpty)
    result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null));
  return result;
}
Related