1

I'm building a flutter app with firebase auth dealing with users. My goal is to streamline the user onboarding experience by allowing email sign up and verification without forcing the user to sign out and sign in again to use the full features. Before testing with real users, this is exactly what I had configured but the overwhelming feedback is that it interrupts and may even cause the user to abandon the authentication flow altogether; the app should simply detect this change in verification status without having to log out.

WHAT I'VE TRIED

I'm using the following in main.dart to provide auth state throughout the app:

StreamProvider < FirebaseUser > .value( value: FirebaseAuth.instance.onAuthStateChanged, ),

I extract it with user1 = Provider.of<FirebaseUser>(context, listen: true); in the individual pages and access different properties like user1.isEmailVerified in ternary expressions to determine what is displayed to the user.

I know that onAuthStateChanged doesn't get called for changes in email verification, only for user sign in and sign out as explained in the accepted answer for this question, but I still need to update the user value that determines the UI shown when a user verifies their email. I have chosen to use a button "complete email verification" which will manually trigger the check and reload of the user profile with the new value once verified. This is the code for that button:

() async {
    // 1
    FirebaseUser user =
    await _firebaseAuth.currentUser();
    print(user.isEmailVerified); // returns false
    // 2
    await user.getIdToken();
    print(user.isEmailVerified);// returns false
    // 3
    await user.reload();
    print(user.isEmailVerified);// returns false
    //4
    user = await _firebaseAuth.currentUser(); 
    print(user.isEmailVerified);// this finally returns true
}

where final _firebaseAuth = FirebaseAuth.instance;

I've noticed that calling .currentUser() twice (before and after reload()) is the only way to actually return true for the .isEmailVerified property without logging out and back in. This is an answer I found in this GitHub thread.

If I press the button again, all the print statements above return true.

THE PROBLEM

That's great, but the problem is the user1 variable that stores the FirebaseUser stream does not change or update even after this, so the UI remains the same despite the user email being verified. I know this because I've placed the following checks in the code:

print('this one is from the stream:'); print(user1.isEmailVerified);

It still returns false even when in the button function body, the user.isEmailVerified returns true.

I've tried calling set state within the button like this:

setState(() {user1 = user; });

But it seems that the value of user1 still remains the same and still returns false for the user1.isEmailVerified, even though I'm trying to set it to the new value derived from the button's function!

This is really frustrating as I feel I'm very close but I just don't know how to refresh the value of the user1 (firebase user stream) throughout the app or even just on that one screen. This should be more straightforward.

I've noticed that pressing hot reload after verifying the email and pressing the button above will automatically show the updated .isEmailVerified value throughout the app while debugging on flutter. Is there a way to programmatically trigger the same kind of rebuild that hot reload uses that will also refresh the firebase user stream data WITHOUT having to log out and sign back in? Maybe this is where the answer lies, but I don't know how to proceed with that.

Any help would be appreciated.

Marvioso
  • 359
  • 6
  • 15

5 Answers5

4

Instead of onAuthStateChanged, you can use userChanges stream which

provides events on all user changes, such as when credentials are linked, unlinked and when updates to the user profile are made. The purpose of this Stream is to for listening to realtime updates to the user without manually having to call [reload]

see https://firebase.flutter.dev/docs/auth/usage/#authentication-state

Karen
  • 1,423
  • 8
  • 16
3

I was also facing this problem and get fixed with the help of:

await user.reload();
user = await _auth.currentUser();
user.isEmailVerified => now it is true
1

I came across your question while trying to figure out why user.reload() wasn't updating the email verification status of my user. Thank you for suggesting getting currentUser again, which works.

To answer your question, I use a BehaviorSubject from rxdart to store my FirebaseUser, so I can get notifications of the user changing from anywhere in the app with a StreamBuilder:

final BehaviorSubject<FirebaseUser> firebaseUser = new BehaviorSubject<FirebaseUser>();
userListener = FirebaseAuth.instance.onAuthStateChanged.listen((user) {

  if (user == null) {
    userSignedOut();
  }
  else if (currentFirebaseUser == null || user.uid != currentFirebaseUser.uid) {
    processNewUser(user);
  }
  firebaseUser.add(user);
});
Kretin
  • 237
  • 2
  • 10
1

Auth state change listener didn't work for me. Field isEmailVerified remains false even after user verifies his email.

My workaround: Started from the assumption that user leaves the app to verify his email (which mean app is paused), and he returns to the app after verifying it (app resumes).

What I did was attach a WidgetsBinding to a relevant stateful widget where I wanted to display if email was verified (but can be done elsewhere). This involves two steps.

First step is to attach the binding:

  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

Second step is to override the didChangeAppLifecycleState to reload the user. I created a function that does the reload and sets a new firebaseUser object

  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed && !firebaseUser.isEmailVerified)
      refreshFirebaseUser().then((value) => setState(() {}));
    super.didChangeAppLifecycleState(state);
  }
  Future<void> refreshFirebaseUser() async {
    await firebaseUser.reload();
    firebaseUser = FirebaseAuth.instance.currentUser;
  }

So what this is basically doing is to reload firebase user object everytime the user returns to the app, while its email is not verified. This seems cleaner than the best workaround that I've found in SO which consisted in setting and cancelling a recurrent action through a timer.

Tiago Santos
  • 726
  • 6
  • 13
0

The FirebaseAuth Flutter plugin provides a Stream with onAuthStateChanged, which is useful for getting updates when a user signs-in or signs-out, but it fails to provide an update when the user data itself changes. A common problem is when we want to detect if the user has just verified his e-mail address, in other words get if FirebaseUser.isEmailVerified changed its value, as this is updated server-side and not pushed to the app.

FirebaseAuth.currentUser() will only detect changes made locally to the user, but if any changes are made server-side, it won't detect, unless FirebaseUser.reload() is called first and then we need to call FirebaseAuth.currentUser() again to get the reloaded user, but still this user won't be emitted by onAuthStateChanged.

This can help you: https://pub.dev/packages/firebase_user_stream

You will find a manual solution or you can use the package.