How to maintain Firebase Authentication after refresh with Flutter web?
Asked Answered
B

3

6

I am using the authStateChanges stream from Firebase with flutter. I have two views, one for mobile and the another one for a web application. I want to redirect the user to the SignIn screen if he is not connected, logged in or authenticated. At first it works well but then when i am logged in and refresh the browser i got the SignIn screen loaded for like 1 second and then the Web screen appears again. I checked with print what's going on and from what i saw, the authStateChanges Stream is null for that 1-2 seconds(when SignIn screen appears) and then has a value when the stream receives the connected user. Is there a way to check, or wait until this authentication is done before loading the SignIn screen when it must not load it ?

My main component contains the StreamBuilder as following:

  Widget build(BuildContext context) {
    final firebaseAuthService = Provider.of<FirebaseAuthService>(context);
    return StreamBuilder<User>(
        stream: firebaseAuthService.authStateChanges(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.active) {
            User user = snapshot.data;
            if (user == null) {
              //first time no connection
              return SignIn();
            }
            if (kIsWeb) {
              return WebMain(user: user);
            }
            // load mobile version
            return MobileMain();
          }
          return Scaffold(
            body: Center(
              child: CircularProgressIndicator(),
            ),
          );

        });
  }

Here you can find my FirebaseAuth wrapper class which contains the methods from firebase:

class FirebaseAuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  User _user;
  bool get isAuthenticated {
    return _user == null ? false : true;
  }
  User get user {
    return _user;
  }
  Future<User> signInWithEmailAndPassword(
      String userEmail, String userPassword) async {
    return _user = await _firebaseAuth
        .signInWithEmailAndPassword(email: userEmail, password: userPassword)
        .then((userCredential) => userCredential.user);
  }
  Stream<User> authStateChanges() {
    _user = _firebaseAuth.currentUser;
    return _firebaseAuth.authStateChanges();
  }
  Future<void> signOut() async {
    return _firebaseAuth.signOut();
  }
}

Brader answered 27/11, 2020 at 9:47 Comment(1)
I also encounter this issue where FirebaseAuth state is lost after a refresh. I'm using firebase_auth: ^3.0.1. There's an open ticket for this issue: github.com/FirebaseExtended/flutterfire/issues/3356Julenejulep
R
4

While I am not sure why authStateChanges does not notify when the user sign in state is changed (usually a second later), a similar function does seem to work for your use case.

Try idTokenChanges()

  FirebaseAuth.instance.idTokenChanges().listen((event) {
    print("On Data: ${event}");
  });

This event will return your Firebase User object. When refreshed, it might return 'null' initially, but within a second, returns your signed in User. You could potentially make the sign in page wait a couple of seconds to make sure a signed in user isn't being initialized.

EDIT: While there may be better solutions, this is currently working for me.

  final subscription = FirebaseAuth.instance.idTokenChanges().listen(null);
  subscription.onData((event) async {

    if(event != null) {
      print("We have a user now");
      isLoading = false;
      print(FirebaseAuth.instance.currentUser);
      subscription.cancel();

      Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => OverviewController())
      );

    } else {
      print("No user yet..");
      await Future.delayed(Duration(seconds: 2));
      if(isLoading) {
        Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => LoginController())
        );

        isLoading = false;
        subscription.cancel();
      }
    }
  });
Rivulet answered 3/1, 2021 at 1:17 Comment(2)
Basically, replacing .authStateChanges() with .idTokenChanges() seems to be a workaround for the bugAirlee
Thanks for your answer i will try it when i have timeBrader
R
0

For me, the below code seems to work fine. Although there is a warning in docs that says "You should not use this getter to determine the user's current state, instead use [authStateChanges], [idTokenChanges] or [userChanges] to subscribe to updates."

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Diary Book',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
        primarySwatch: Colors.green,
      ),
      home: (FirebaseAuth.instance.currentUser == null)
          ? LoginPage()
          : MainPage(),
    );
  }
}
   

I haven't encountered any issues using the above code. I Will let you know if do. If someone can comment any future errors this may have that would be great

Rambo answered 19/8, 2021 at 11:0 Comment(0)
J
0
FirebaseAuth.instance.authStateChanges().listen(
(event) {
  if (event == null) {
    print('----user is currently signed out');
  } else {
    print('----user is signed in ');
  }
  runApp(
      const MyApp()  
      );
    },
  );
Jozef answered 19/2, 2022 at 17:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.