You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Firebase登录触发FirebaseAuthException但仍登录成功,且登录函数被重复调用问题排查

分析与解决方案:FirebaseAuth重复调用导致空参数异常

从你的描述和代码来看,核心问题是**login函数被意外调用了两次**:一次传入了正确的凭证(触发HERE1),另一次传入空字符串(触发HERE2的异常)。异步操作的并发特性导致了HERE1和HERE2的打印顺序不稳定,你临时加的空判断只是治标,我们需要找到重复调用的根源。

可能的触发原因及排查方向

1. checkLocalBiometrics被重复执行

checkLocalBiometrics的调用时机可能存在重复触发的情况:

  • 如果它是在initState中调用,当页面因路由跳转、屏幕旋转或状态重建时,initState会再次执行,导致函数重复调用。
  • 如果是通过GetX的状态监听或Controller初始化触发,也可能因为状态变化多次执行。

解决方法:给函数加一个执行标记,避免重复运行:

bool _hasCheckedBiometrics = false;

void checkLocalBiometrics() async {
  // 已经执行过就直接返回
  if (_hasCheckedBiometrics) return;
  _hasCheckedBiometrics = true;

  if (await credentialsSaved) {
    // 后续原有逻辑不变
    var localAuth = LocalAuthentication();
    bool canCheckBiometrics = await localAuth.canCheckBiometrics;
    if (canCheckBiometrics) {
      var didAuthenticate = await localAuth.authenticate(
        localizedReason: "-----------------",
        biometricOnly: true,
        stickyAuth: true,
      );
      if (didAuthenticate) {
        var secureStorage = FlutterSecureStorage();
        var storedEmail = await secureStorage.read(key: "email");
        var storedPassword = await secureStorage.read(key: "password");
        login(email: storedEmail!, password: storedPassword!);
      }
    }
  }
}

2. stickyAuth: true的特性触发重复验证

你设置了stickyAuth: true,这个参数的作用是:当App被中断(比如来电、切换到后台)后回到前台时,会重新触发生物识别验证。如果用户在验证过程中切换了后台,可能会导致didAuthenticate被多次返回true,进而重复调用login。

解决方法:

  • 先尝试将stickyAuth改为false,看是否还会出现重复调用;
  • 或者在验证成功后加一个标记,避免重复执行登录逻辑:
bool _hasAuthenticated = false;

void checkLocalBiometrics() async {
  if (await credentialsSaved) {
    var localAuth = LocalAuthentication();
    bool canCheckBiometrics = await localAuth.canCheckBiometrics;
    if (canCheckBiometrics) {
      var didAuthenticate = await localAuth.authenticate(
        localizedReason: "-----------------",
        biometricOnly: true,
        stickyAuth: true,
      );
      if (didAuthenticate && !_hasAuthenticated) {
        _hasAuthenticated = true; // 标记已完成验证
        var secureStorage = FlutterSecureStorage();
        var storedEmail = await secureStorage.read(key: "email");
        var storedPassword = await secureStorage.read(key: "password");
        login(email: storedEmail!, password: storedPassword!);
      }
    }
  }
}

3. 异步竞态与非空断言的潜在风险

虽然credentialsSaved校验了邮箱非空,但你用了storedEmail!和storedPassword!的非空断言,极端情况下(比如存储的密码被意外清空)会导致空参数调用。建议在调用login前增加双重校验:

if (storedEmail != null && storedPassword != null) {
  login(email: storedEmail, password: storedPassword);
} else {
  print("存储的凭证不完整,请重新登录");
  // 这里可以跳转到登录页面,清空无效存储
  await secureStorage.deleteAll();
}

关于打印顺序混乱的解释

因为两次login调用是并发执行的异步操作:第一次正常登录的signInWithEmailAndPassword和第二次空参数的请求同时在后台处理,它们的完成顺序是不确定的,所以HERE1和HERE2的打印顺序会随机变化。

额外排查建议

在checkLocalBiometrics和login函数开头增加日志,明确调用次数和参数:

void checkLocalBiometrics() async {
  print("🔍 checkLocalBiometrics 被调用");
  // 原有逻辑...
}

void login({required String email, required String password}) async {
  print("🔑 login 被调用:email=$email, password=${password.isEmpty ? '空' : '非空'}");
  // 原有逻辑...
}

通过日志可以直观看到是不是真的被调用了两次,以及每次的参数情况,帮助定位问题。

内容的提问来源于stack exchange,提问作者Phake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 12:24:10