Flutter: How to listen to the FirebaseUser is Email verified boolean?
Asked Answered
U

11

29

My Idea: I want to use the Firebase Auth Plugin in Flutter to register the users. But before they can access the App, they have to verify their Email address. Therefor I push the Firebase users after registration to a verification screen. This is just a loading screen which tells the user that he has to verify his email.

But now: How can I continuously listen, if the users email is verified or not and send him (when true) to the Homescreen?

I'm new to Flutter and I don't know if I have to use a Streams or Observables or a while Loop or setState() or something else for such a boolean check. And I also don't know how to setup a solution.

This is my basic code for register a user:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final Firestore _db = Firestore.instance;

  Future<FirebaseUser> get getUser => _auth.currentUser();

  Stream<FirebaseUser> get user => _auth.onAuthStateChanged;

  Future<FirebaseUser> edubslogin(String email, String password) async {
    try {
      final FirebaseUser user = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
     
      await user.sendEmailVerification();
      
      //email verification somewhere here
    
      updateUserData(user);
      return user;
    } catch (error) {
      print(error);
      return null;
    }
  }

I've tried this:

     if (user.isEmailVerified == true) {
        
        //go to Homescreen
        return true; 
      } else {

        //show verification screen(loading spinner)
        return false;
      }

But I don't get a boolean value true out of isEmailVerified.

What do I have to do?

Utoaztecan answered 25/7, 2019 at 0:1 Comment(0)
C
12

This verification isn't as straightforward as you'd hope. First, there is the problem of recognizing that the user has verified their email. Second, there is the issue that there isn't any sort of a notification you can listen to that will automatically trigger a change in your app.

Check this thread for info about emailVerified: https://github.com/flutter/flutter/issues/20390#issuecomment-514411392

I was only able to verify the user if I 1) Created their account, 2) Signed them in, 3) Then checked to make sure they verified their email.

final FirebaseAuth _auth = FirebaseAuth.instance;

var _authenticatedUser = await _auth.signInWithEmailAndPassword(email: _email, password: _password); 

//where _email and _password were simply what the user typed in the textfields.



if (_authenticatedUser.isEmailVerified) {
        //Verified
      } else {
        //Not verified
        }

Part 2: How do you get your app to recognize that the user has confirmed their email? Find a way to trigger the function that checks confirmation. A button would be easy enough. If you want it to see "automatic" then I guess you could create a timer that checks for email verification every 10 seconds or so.

Cove answered 25/7, 2019 at 1:22 Comment(3)
The key here is that the app doesn't automatically get notified when the user verified their email address, so you'll need to check that yourself from within the app. To do this you'd force a reload of the user profile, so that you get the latest values from the server. With those you can then recheck the value of isEmailVerified.Professed
"This verification isn't as straightforward as you'd hope" It's as straightforward as straightforward can get.Dena
I totally agree with @OjonugwaJudeOchalifu. Can improved with Alisson's answer: Timer.periodic(fiveSec, (Timer t){ _auth.currentUser!..reload(); if(_auth.currentUser!.emailVerified){ //TODO: Proceed to specific page (route) }else{ //TODO: Toast something every five sec } });Gebhardt
S
35

I faced the same situation in my app. My solution was to create a periodic timer into the initState method of a strategic route to hold the app until the e-mail is verified. It is not so elegant as using a listener but works fine.

import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';

class _AccountConfirmationState extends State<AccountConfirmation> {

    late Timer _timer;

    @override
    void initState() {
      super.initState();
      
      _timer = Timer.periodic(const Duration(seconds: 5), (timer) async {
      await FirebaseAuth.instance.currentUser?.reload();
      final user = FirebaseAuth.instance.currentUser;
        if (user?.emailVerified ?? false) {
          timer.cancel();
          Navigator.pop(context, true);
        }
      });
    }

    @override
    void dispose() {
      super.dispose();
      _timer.cancel();
    }

    @override
    Widget build(BuildContext context) {
      //TODO: Implement your amazing waiting screen here
    }

}
Shantung answered 1/3, 2020 at 3:37 Comment(3)
This is brilliant! Thank you!Spier
import 'dart:async';Ankus
Simple and works! A few updates for firebase_auth: ^0.18.3: I had to initiate these variables with values: bool _isUserEmailVerified=false;\n Timer _timer=Timer(Duration(seconds: 5), (){}); AND some of the functions have changed: await FirebaseAuth.instance.currentUser..reload(); //.instance.currentUser()..reload(); var user = await FirebaseAuth.instance.currentUser; if (user.emailVerified) { setState((){ _isUserEmailVerified = user.emailVerified; });Seaworthy
C
12

This verification isn't as straightforward as you'd hope. First, there is the problem of recognizing that the user has verified their email. Second, there is the issue that there isn't any sort of a notification you can listen to that will automatically trigger a change in your app.

Check this thread for info about emailVerified: https://github.com/flutter/flutter/issues/20390#issuecomment-514411392

I was only able to verify the user if I 1) Created their account, 2) Signed them in, 3) Then checked to make sure they verified their email.

final FirebaseAuth _auth = FirebaseAuth.instance;

var _authenticatedUser = await _auth.signInWithEmailAndPassword(email: _email, password: _password); 

//where _email and _password were simply what the user typed in the textfields.



if (_authenticatedUser.isEmailVerified) {
        //Verified
      } else {
        //Not verified
        }

Part 2: How do you get your app to recognize that the user has confirmed their email? Find a way to trigger the function that checks confirmation. A button would be easy enough. If you want it to see "automatic" then I guess you could create a timer that checks for email verification every 10 seconds or so.

Cove answered 25/7, 2019 at 1:22 Comment(3)
The key here is that the app doesn't automatically get notified when the user verified their email address, so you'll need to check that yourself from within the app. To do this you'd force a reload of the user profile, so that you get the latest values from the server. With those you can then recheck the value of isEmailVerified.Professed
"This verification isn't as straightforward as you'd hope" It's as straightforward as straightforward can get.Dena
I totally agree with @OjonugwaJudeOchalifu. Can improved with Alisson's answer: Timer.periodic(fiveSec, (Timer t){ _auth.currentUser!..reload(); if(_auth.currentUser!.emailVerified){ //TODO: Proceed to specific page (route) }else{ //TODO: Toast something every five sec } });Gebhardt
S
4

In order for the app to recognise if the user has verified their email you can achieve this with a simple user.reload.

In order to test it yourself implement a button with onPressed code:

 FlatButton(
    child: Text("check"),
    textColor: Colors.white,
    onPressed: () async {
    try {
      FirebaseUser user = await _firebaseAuth.currentUser();
      await user.reload();
      user = await _firebaseAuth.currentUser();
        print( user.isEmailVerified); 
        

     } catch (e) {
      return e.message;
    }
}),
Swinger answered 31/7, 2020 at 13:37 Comment(0)
N
3

Well I created a stream to handle this. Not so elegant but works. Use a StreamProvider.value() to handle events.

  Stream<userVerificationStatus> checkUserVerified() async* {
    bool verified = false;
    yield userVerificationStatus(status: Status.LOADING); 
    while (!verified) {
      await Future.delayed(Duration(seconds: 5));
      FirebaseUser user = await _auth.currentUser();
      if(user!=null)await user.reload();
      if (user == null) {
        yield userVerificationStatus(status: Status.NULL);
      } else {
        print("isemailverified ${user.isEmailVerified}");
        await user.reload();
        verified = user.isEmailVerified;
        if(verified)
        yield userVerificationStatus(status: Status.VERIFIED);
        else
        yield userVerificationStatus(status: Status.NOT_VERIFIED);
      }
    }
  }
Nailbrush answered 21/4, 2020 at 11:35 Comment(1)
_auth should be the FirebaseAuth.Instance and userVerificationStatus is just a custom enum classNailbrush
G
3

True. None of the FirebaseAuth idTokenChanges() , authStateChanges() or userChanges() will send you an event if the user verifies their email. I'm using a combination of the methods to get an email verification update in my app and it seems to be working well.

First I check the status in the initState() method and start a timer if email is not verified

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

    //Get Authenticated user
    user = context.read<AuthenticationService>().currentUser();
    _isEmailVerified = user.emailVerified;
    if (!_isEmailVerified) _startEmailVerificationTimer();

    }

I also listen for app background/foreground events in case the user happens to leave the app to confirm their email ( If you also do this, add WidgetsBindingObserver to your class)

 @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      user = context.read<AuthenticationService>().reloadCurrentUser();
      if (user.emailVerified) {
        setState(() {
          _isEmailVerified = user.emailVerified;
        });
        timer?.cancel();
      } else {
        if (!timer.isActive) _startEmailVerificationTimer();
      }
    } 
  }

This is the _startEmailVerificationTimer() method

 _startEmailVerificationTimer() {
    timer = Timer.periodic(Duration(seconds: 5), (Timer _) {
      user = context.read<AuthenticationService>().reloadCurrentUser();
      if (user.emailVerified) {
        setState(() {
          _isEmailVerified = user.emailVerified;
        });
        timer.cancel();
      }
    });
  }

Don't forget to dispose the timer

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

My Firebase User methods in case anyone is interested:

  User currentUser() {
    return _firebaseAuth.currentUser;
  }

  User reloadCurrentUser() {
    User oldUser = _firebaseAuth.currentUser;
    oldUser.reload();
    User newUser = _firebaseAuth.currentUser;
    return newUser;
  }
Gulp answered 18/12, 2020 at 9:32 Comment(0)
A
2

I had the same problem with the latest version of firebase auth.

But I found out there is a function for reloading the current user which signed in

  Future<bool> get userVerified async {
    await FirebaseAuth.instance.currentUser.reload();
    return FirebaseAuth.instance.currentUser.emailVerified;
  }
Appreciative answered 25/4, 2021 at 6:0 Comment(1)
Yes, worked like a charm. Only difference is I did not use a Future, just a bool function.Profanity
V
2

referesh token after checking current user emailVerified is true

        var user = FirebaseAuth.instance.currentUser;
        await user?.reload();
        if (user?.emailVerified == true) {
          await FirebaseAuth.instance.currentUser?.getIdToken(true);
          //rest code..
        }

also please let me know if this a correct way of doing things.

Viral answered 3/9, 2021 at 13:43 Comment(1)
I can verify that your solution, in particular user.reload( ), is the correct way. I am using GetX to keep track of updating the widget. Worked like a charm. Although I definitely should make the function a future. Good work.Profanity
P
1

I have found a way by updating firebase user profile and calling it in init() like below function.

void _checkEmailVerification() async {
    await widget.auth.getCurrentUser().then((user) {
      UserUpdateInfo userUpdateInfo = new UserUpdateInfo();
      userUpdateInfo.displayName = user.displayName;
      user.updateProfile(userUpdateInfo).then((onValue) {
        setState(() {
          _isEmailVerified = user.isEmailVerified;
        });
      });
    });
  }
Palenque answered 21/9, 2019 at 10:18 Comment(0)
K
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. I chose this solution over setting and cancelling a timer as it avoided setting a recurrent action through a timer which could be overkill for this particular problem.

Kalvin answered 16/11, 2020 at 13:34 Comment(0)
A
0

Since authOnChanged only listens for sign in and sign out actions, in your sign in method, first sign out then try to sign in.

await _firebaseAuth.signOut();

authResult = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password);

return authResult.user;

In the onAuthChanged, when you control if user.isEmailVerified, it will work since you have signed out and it will update the user even if you haven't signed in yet because sign out will trigger your onAuthChanged even if you haven't signed in.

It is like cheating but the only way that I have found without timeout is this.

Asoka answered 26/7, 2020 at 22:48 Comment(0)
A
0

Based on previously answers, I thing I got the most simple one.

Inside your method:

//await _user!.reload(); //i often tryed to reload the already stored user
await _auth.currentUser!.reload(); //reload one from Firebase instead
_user = _auth.currentUser; //rebase your _user local with the new one.

Voi lá, its working now.

Adenosine answered 6/5, 2023 at 22:34 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.