Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter-web - Avoid initialRoute from initiating when the app launched with a different route via the browser's address bar?

New to Flutter.

I'm making an app that has a splash screen that initially shows up when the user opens the app. After 3 seconds, the app will show the login or the dashboard screen, depending on the authentication state.

Here's my code.

main.dart

void main() { 
  runApp(myApp);
}

MaterialApp myApp = MaterialApp(
  initialRoute: "/",
  routes: {
    "/": (context) => SplashScreen(),
    "/signin": (context) => SignInScreen(),
    "/notes": (context) => NotesScreen(),
  },
);

splash_screen.dart

class SplashScreen extends StatefulWidget {
  @override
  _SplashScreenState createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    _goToNextScreen();
  }

  void _goToNextScreen() {
    Future.delayed(
      Duration(seconds:3),
      () async {
        AuthState authState = await Auth.getAuthState();
        String route = authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
        Navigator.pushReplacementNamed(context, route);
      }
    );
  } 

  // build() override goes here...
}

I've been debugging the app with a web-server. When the app launches with the url localhost:8000/, everything seems fine. However, if the app started with the url localhost:8000/notes, the splash screen, I think, still gets initiated. What happens is the app will show the notes screen, then after 3 seconds, the app will open another notes screen.

Any ideas?

like image 843
Jeffrey Lopez Avatar asked Aug 20 '20 15:08

Jeffrey Lopez


3 Answers

Because first render always started at root '/', it's preferable to use your own path for splash screen, like

initialRoute: '/splash'.

To hide this path in the address bar, replace routes map with route generator:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateRoute: (RouteSettings settings) {

        // print current route for clarity.
        print('>>> ${settings.name} <<<');

        switch (settings.name) {
          case '/splash':
            return MaterialPageRoute(
              builder: (context) => SplashScreen(),
              // settings omitted to hide route name
            );
          case '/signin':
            return MaterialPageRoute(
              builder: (context) => SignInScreen(),
              settings: settings,
            );
          case '/notes':
            return MaterialPageRoute(
              builder: (context) => NotesScreen(),
              settings: settings,
            );
          case '/':
            // don't generate route on start-up
            return null;
          default:
            return MaterialPageRoute(
              builder: (context) => FallbackScreen(),
            );
        }
      },
      initialRoute: '/splash',
    );
  }
}


like image 81
Spatz Avatar answered Oct 16 '22 19:10

Spatz


See since the main logic is we cannot have await in the init state so the page will build irrespective of the any logic you provide. I have a solution to this, there may be some advance or other good solutions too, so this is what I would use.

I would use a concept of future builder. What it will do is wait for my server and then build the whole app.

So process is

  1. In your main.dart use

     Future<void> main() async {
       try {
         WidgetsFlutterBinding.ensureInitialized();
    
     //await for my server code and according to the variable I get I will take action
     //I would have a global parameter lets say int InternetOff
         await checkServer();
         runApp(MyApp());
       } catch (error) {
         print(error);
         print('Locator setup has failed');
     //I can handle the error here
       }
     }
    

Now MyApp stateless Widget that will help us choose our path

class MyApp extends Stateless Widget{
Widget build(BuildContext context) {
//Using this FutureBuilder 
    return FutureBuilder<String>(
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        // AsyncSnapshot<Your object type>

// Now if InternetOff is equal to one I would make it go to home 
if(InternetOff==1) return MaterialApp(
              theme: ThemeData.light(),
              home: CheckInternet(),
              debugShowCheckedModeBanner: false,
            );
//else go to Home similarly with these if and else you can add more conditions
else {
             
              return MaterialApp(
                theme: ThemeData.dark(),
                home: UserHome(),
                debugShowCheckedModeBanner: false,
              );
            }
          }
        }
      },
    );
  }
}
like image 31
Krish Bhanushali Avatar answered Oct 16 '22 20:10

Krish Bhanushali


First of all, flutter-web like any other Single Page Application supports hash based routing. As a result if you want to access

localhost:8000/notes

you have to access it as

localhost:8000/#/notes

Cleaner way to handle auth state

Call getAuthState function before runApp() to make sure that the auth state is set before app is initialized. And pass authState to SplashScreen widget as parameter.

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  AuthState authState = await Auth.getAuthState();
  runApp(MaterialApp myApp = MaterialApp(
    initialRoute: "/",
    routes: {
      "/": (context) => SplashScreen(authState: authState),
      "/signin": (context) => SignInScreen(),
      "/notes": (context) => NotesScreen(),
    },
  ));
}

splash_screen.dart

class SplashScreen extends StatefulWidget {
  final AuthState authState;
  SplashScreen({Key key, this.authState}) : super(key: key);
  @override
  _SplashScreenState createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    _goToNextScreen();
  }

  void _goToNextScreen() {
    Future.delayed(
      Duration(seconds:3),
      () async {
        String route = widget.authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
        Navigator.pushReplacementNamed(context, route);
      }
    );
  } 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}

And if you want even more cleaner way to handle auth state, you have to use state management solution like Provider.

like image 1
tanweer anwar Avatar answered Oct 16 '22 20:10

tanweer anwar