Flutter Firebase Authentication: delay on startup

Viewed 1614

I am using Provider and the stream FirebaseAuth.instance.onAuthStateChanged in the app to decide where to redirect on startup, but although the user is already logged in (from a previous startup) the app starts on the login screen and almost 1 second later redirects to the home page, from which it should have started from the first moment. This happens even in airplane mode.

I would like to know if there is any approach to solve this, even if it is not possible to show the home screen at once, I don't know how to differentiate between the not logged user (null->login screen) and loading user (null->loading screen).

Some of the code:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final DatabaseService db = DatabaseService();
  @override
  Widget build(BuildContext context) {
    return StreamProvider<FirebaseUser>.value(
      value: _auth.onAuthStateChanged,
      child: Consumer<FirebaseUser>(
        builder: (context, firebaseUser, child) {
          return MultiProvider(
            providers: [
              if (firebaseUser != null)
                ChangeNotifierProvider(create: (ctx) => CollectionState(firebaseUser)),
              StreamProvider<List<Collection>>.value(value: db.streamCollections(firebaseUser)),
            ],
            child: MaterialApp(
              title: 'My App',
              routes: {
                '/': (ctx) => LandingPage(),
                '/login': (ctx) => LoginPage(),
                '/emailSignIn': (ctx) => EmailSignInPage(),
                '/emailSignUp': (ctx) => EmailSignUpPage(),
                '/emailUnverified': (ctx) => EmailUnverifiedPage(),
                '/home': (ctx) => HomePage(),
                '/settings': (ctx) => Settings(),
              },
            ),
          );
        },
      ),
    );
  }
}

class LandingPage extends StatelessWidget {
  final DatabaseService _db = DatabaseService();
  @override
  Widget build(BuildContext context) {
    final user = Provider.of<FirebaseUser>(context);
    final userCondition =
        user == null ? 'null' : user.isEmailVerified ? 'verifiedUser' : 'unverifiedUser';

    switch (userCondition) {
      case 'null':
        return LoginPage();
        break;
      case 'unverifiedUser':
        return EmailUnverifiedPage();
        break;
      case 'verifiedUser':
        return HomePage();
        break;
    }
  }
}

The code is a bit simplified, I use a service for the authentication instance instead, just that.

2 Answers

I know I'm very late, but I've had the same problem for weeks and I finally figured it out.

@ChinkySight is right when he says it's best to use a StreamBuilder, mostly because you have access to the connectionState property.

The reason why lag exists is because the connection to the stream is not fully established. So during ConnectionState.waiting, return a widget like a splash screen or just a container.

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: FirebaseAuth.instance.onAuthStateChanged,
        builder: (_, snapshot) {
          // Added this line
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Container();
          }
          if (snapshot.data is FirebaseUser && snapshot.data != null) {
            return HomePage();
          }
          return LoginPage();
        });
  }
}

You can even give your return statements fancy animations with the Animated Switcher

return StreamBuilder(
      stream: FirebaseAuth.instance.onAuthStateChanged,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        Widget widget;

        if (snapshot.connectionState == ConnectionState.waiting) {
          return Container();
        }

        switch (snapshot.hasData) {
          case (true):
            widget = HomePage();
            break;
          case (false):
            widget = LoginPage();
        }
        return Stack(
          children: <Widget>[
            Scaffold(
              backgroundColor: Colors.grey.shade200,
            ),
            AnimatedSwitcher(
              duration: Duration(milliseconds: 700),
              child: FadeTransition(
                  opacity: animation,
                  child: widget,
                ),
               );
              },
            )
          ],
        );
      },
    );

This works for FlutterFire.

Firebase Auth enables you to subscribe in realtime to this state via a Stream. Once called, the stream provides an immediate event of the user's current authentication state, and then provides subsequent events whenever the authentication state changes. To subscribe to these changes, call the authStateChanges() method on your FirebaseAuth instance:

import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:flutter/material.dart';
import 'menu.dart';
import 'login.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:firebase_core/firebase_core.dart';

Future main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      runApp(
          MyApp()
        );
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitUp]);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'TestApp',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: 
        StreamBuilder<auth.User>(
          stream: auth.FirebaseAuth.instance.authStateChanges(),
          builder: (BuildContext context, AsyncSnapshot<auth.User> snapshot) { 
            if(snapshot.hasData) {
              print("data exists");
              return HomePage();
            }
            else {
              return LoginPage();
            }
          },
        )
    );
  }
}
Related