1
votes

For my app, I want to directly bring the user into the homepage if they're logged in, and if not, bring them to the login page. I'm sure this has been explained before, but up until now, I haven't found a good way to implement it with a splash screen.

What I have now is this: A MainPage widget, which upon afterFirstLayout, will redirect to either LoginPage or HomePage. It works well, but there's two main issues:

  1. The back button redirects to the splash screen. When I press the back button on either HomePage or LoginPage, I get sent back to the splash screen. This would be just fine, but I rely on LoginPage for logging in multiple accounts (so if they were launched in any other context, they would exit the app upon pressing the back button – something you don't want happening when you're in the settings screen, harmlessly adding a new account)
  2. The screen simply freezes until the next page is built. When it runs, I show a CircularProgressIndicator at the screen, to indicate to the user that the app is launching and that it should open soon. This is extremely helpful since if I didn't add this, a user may get impatient and simply move onto another app. The problem is, by calling on afterFirstLayout, the page only gets a split-second amount of showtime before moving on to the new page. If possible, I wanted to set a small amount of delay to at least give MainPage some amount of screen time.

Below is the code I currently have that does the routing.

    // MainPage, LoginPage, and HomePage are all StatefulWidgets.
    // AccessKeyStore is a singleton I use to handle all my access keys.
    // SlidingAnimationRoute is an extended CupertinoPageRoute

    @override
    void afterFirstLayout(BuildContext context) async {
        bool notFirstLogin = await AccessKeyStore().keyPairStored(AccessKeyStore().getLastActivePairNumber());
        var landingPage;

        if (notFirstLogin)
            landingPage = MainPage();
        else
            landingPage = LoginPage(title: 'Initial Login');

        Navigator.of(context).pop();
        Navigator.push(context, new SlidingAnimationRoute(builder: (context) => landingPage));
    }

So as of now, I've done the following, but I can't find anything that can satisfy all of the design choices I need.

  • Popped MainPage upon navigating onto the new page (as seen above.) The issue is that pressing the back button on the LoginPage or HomePage shows a plain black screen, and you have to press back again to fully exit.
  • Made the app exit upon pressing the back button on both LoginPage and HomePage. But as said before, this isn't good when called in any other context than launching the app.
  • Used Android and iOS splash screens. Works, but it barely does the decision making I need that should have redirected the user to another page.
  • Scrap the splash screen altogether and make MaterialApp's home dynamic, based on the login state. This didn't work, and I ended up getting a plain black screen that launched nothing. Not even the HomePage or the LoginPage.

So my main question is: How can a splash screen be (properly) implemented in Flutter, in a way that the next page will be dynamic depending on a condition? (because I may have overlooked a few features of Flutter.) And is there a way to add a minimum amount of time before transitioning so that the splash page gets a few seconds of screen time?

If you need an example, the Twitter app always shows the splash screen, but can redirect to either the home page if you're logged in, or the login page if you're logged out. I'm trying to achieve the same thing.

2

2 Answers

4
votes
  1. The back button redirects to the splash screen. When I press the back button on either MainPage or LoginPage, I get sent back to the splash screen. This would be just fine, but I rely on LoginPage for logging in multiple accounts (so if they were launched in any other context, they would exit the app upon pressing the back button – something you don't want happening when you're in the settings screen, harmlessly adding a new account)

I don't really understand why pop and push doesn't affect that but instead using that you can use Navigator.pushAndRemoveUntil() instead to navigate from SplashScreen

Navigator.pushAndRemoveUntil(context, new SlidingAnimationRoute(builder: (context) => landingPage), (_) => false);

the last parameter is RoutePredicate. from https://api.flutter.dev/flutter/widgets/NavigatorState/pushAndRemoveUntil.html,

To remove all the routes below the pushed route, use a RoutePredicate that always returns false (e.g. (Route route) => false).


  1. The screen simply freezes until the next page is built. When it runs, I show a CircularProgressIndicator at the screen, to indicate to the user that the app is launching and that it should open soon. This is extremely helpful since if I didn't add this, a user may get impatient and simply move onto another app. The problem is, by calling on afterFirstLayout, the page only gets a split-second amount of showtime before moving on to the new page. If possible, I wanted to set a small amount of delay to at least give MainPage some amount of screen time.

You can delay action using Future.delayed

Future.delayed(Duration(seconds: 1), () {
  Navigator.pushAndRemoveUntil(context, new SlidingAnimationRoute(builder: (context) => landingPage), (_) => false);
});

I see that you use afterFirstLayout package to get the context which is fine, but actually you can achieve that by simple using addPostFrameCallback inside initState

@override
void initState() {
  super.initState();

  SchedulerBinding.instance.addPostFrameCallback((_) {
    // you can use "context" here, for example:
    Future.delayed(Duration(seconds: 1), () {
      Navigator.pushAndRemoveUntil(context, new SlidingAnimationRoute(builder: (context) => landingPage), (_) => false);
    });
  })
}
0
votes
@override
  void initState() {
    // TODO: implement initState
    super.initState();
    startTimer();
  }

  void startTimer() {
    Timer(Duration(seconds: 3), () {
      navigateUser(); //It will redirect  after 3 seconds
    });
  }

  void navigateUser() async{
    if (FirebaseAuth.instance.currentUser != null) {
      Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Screen1()));
    } else {
      Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage()));
    }
  }