Flutter:如何监听 FirebaseUser 是 Email verified 布尔值?

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

我的想法: 我想在 Flutter 中使用 Firebase Auth Plugin 来注册用户。 但在他们可以访问该应用程序之前,他们必须验证他们的电子邮件地址。 因此,我在注册后将 Firebase 用户推送到验证屏幕。这只是一个加载屏幕,告诉用户他必须验证他的电子邮件。

但是现在:如果用户的电子邮件是否经过验证,我如何继续收听并将他(如果为真)发送到主屏幕?

我是 Flutter 的新手,我不知道我是否必须使用 Streams 或 Observables 或 while Loop 或 setState() 或其他东西来进行这样的布尔检查。而且我也不知道如何设置解决方案。

这是我注册用户的基本代码:

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;
    }
  }

我试过这个:

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

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

但我没有从 isEmailVerified 中得到布尔值 true

我需要做什么?

此验证并不像您希望的那样简单。首先,存在识别用户已验证其电子邮件的问题。其次,问题是没有任何类型的通知可以自动触发应用程序的更改。

查看此线程以获取有关已验证电子邮件的信息:https://github.com/flutter/flutter/issues/20390#issuecomment-514411392

我只能在以下情况下验证用户:1) 创建他们的帐户,2) 登录,3) 然后检查以确保他们验证了他们的电子邮件。

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
        }

第 2 部分:如何让您的应用识别用户已确认其电子邮件?找到一种方法来触发检查确认的功能。一个按钮就足够简单了。如果你想让它看到 "automatic" 那么我想你可以创建一个计时器,每 10 秒左右检查一次电子邮件验证。

我找到了更新 firebase 用户配置文件并在 init() 中调用它的方法,如下面的函数。

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;
        });
      });
    });
  }

我在我的应用程序中遇到了同样的情况。我的解决方案是在战略路线的 initState 方法中创建一个周期性计时器,以在验证电子邮件之前保留应用程序。它不像使用侦听器那么优雅,但工作正常。

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
    }

}

好吧,我创建了一个流来处理这个问题。不那么优雅但有效。使用 StreamProvider.value() 来处理事件。

  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);
      }
    }
  }

由于 authOnChanged 只监听登录和退出操作,在您的登录方法中,先退出然后再尝试登录。

await _firebaseAuth.signOut();

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

return authResult.user;

onAuthChanged 中,当您控制 if user.isEmailVerified 时,它会在您注销后工作,即使您尚未登录,它也会更新用户,因为注销会即使您尚未登录,也会触发您的 onAuthChanged

这就像作弊,但我发现没有超时的唯一方法是这个。

为了让应用程序识别用户是否已经验证了他们的电子邮件,您可以通过简单的 user.reload.

来实现

为了自己测试,实现一个带有 onPressed 代码的按钮:

 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;
    }
}),

授权状态更改侦听器对我不起作用。即使用户验证了他的电子邮件,字段 isEmailVerified 仍然存在 false

我的解决方法: 从假设用户离开应用程序以验证他的电子邮件开始(这意味着应用程序 已暂停 ),并且他在验证后 returns 到应用程序(应用程序 简历).

我所做的是将 WidgetsBinding 附加到相关的有状态小部件,如果电子邮件已验证(但可以在其他地方完成),我想在其中显示。这涉及两个步骤。

第一步是附加绑定:

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

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

第二步是覆盖 didChangeAppLifecycleState 以重新加载用户。我创建了一个函数来重新加载并设置一个新的 firebaseUser 对象

  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;
  }

所以这基本上是在每次用户 returns 到应用程序时重新加载 firebase 用户对象,而其电子邮件未经过验证。我选择这个解决方案而不是设置和取消计时器,因为它避免了通过计时器设置循环操作,这对于这个特定问题来说可能是过大的。

没错。如果用户验证了他们的电子邮件,FirebaseAuth idTokenChanges()authStateChanges()userChanges() 的 None 将向您发送一个事件。我在我的应用程序中使用了多种方法的组合来获取电子邮件验证更新,它似乎运行良好。

首先,我在 initState() 方法中检查状态,如果电子邮件未验证则启动计时器

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

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

    }

我还监听应用程序 background/foreground 事件,以防用户碰巧离开应用程序以确认他们的电子邮件(如果您也这样做,请将 WidgetsBindingObserver 添加到您的 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();
      }
    } 
  }

这是 _startEmailVerificationTimer() 方法

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

别忘了处理定时器

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

我的 Firebase 用户方法,以防有人感兴趣:

  User currentUser() {
    return _firebaseAuth.currentUser;
  }

  User reloadCurrentUser() {
    User oldUser = _firebaseAuth.currentUser;
    oldUser.reload();
    User newUser = _firebaseAuth.currentUser;
    return newUser;
  }

我在使用最新版本的 firebase auth 时遇到了同样的问题。

但是我发现有一个功能可以重新加载当前登录的用户

  Future<bool> get userVerified async {
    await FirebaseAuth.instance.currentUser.reload();
    return FirebaseAuth.instance.currentUser.emailVerified;
  }

检查当前用户邮箱验证后刷新令牌

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

另请告诉我这是否是正确的做事方式。