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

Flutter集成Facebook登录异常及Firestore权限报错问题求助

Flutter Facebook 登录 + Firestore 权限异常排查

所有问题核心原因是异步执行顺序错误+认证状态监听缺失,你当前配置的Firebase安全规则(登录后可读写)本身没有问题。


问题1:首次登录报权限错误、第二次尝试才成功

根因

signInWithFacebook方法中调用FirebaseAuth.instance.signInWithCredential(facebookAuthCredential)时未加await,Firebase认证流程是异步的,你没等认证状态生效就发起Firestore查询,此时请求属于未登录态,自然触发PERMISSION_DENIED错误。异常抛出后页面栈混乱退回登录页,第二次点击时前一次的登录状态已经在本地生效,所以能正常进入。
另外你没有判断Facebook登录结果状态,用户取消登录时会直接触发空指针崩溃。

修复代码

Future<void> signInWithFacebook() async {
  final LoginResult loginResult =
      await FacebookAuth.instance.login(permissions: ['email']);
  // 新增:判断登录是否成功,避免用户取消时崩溃
  if (loginResult.status != LoginStatus.success || loginResult.accessToken == null) {
    return;
  }

  final OAuthCredential facebookAuthCredential =
      FacebookAuthProvider.credential(loginResult.accessToken!.token);

  final userData = await FacebookAuth.instance.getUserData();
  // 修复:加await等待认证完成,再执行后续逻辑
  final UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(facebookAuthCredential);
  if (userCredential.user == null) return;

  var snapshot = await FirebaseFirestore.instance
      .collection('users')
      .where('Email', isEqualTo: userData['email'])
      .get();

  if (snapshot.size == 0) {
    await FirebaseFirestore.instance
        .collection('users')
        .add({'Email': userData["email"], 'Name': userData['name']});
  }
  UserModel user = UserModel.fromJson(userData);
  _currentUser = user;
  if(mounted) {
    setState(() {
      loading = true;
    });
  }
  if(mounted) {
    Navigator.pushNamed(context, '/home');
  }
}

问题2:退出登录仍抛出权限错误

根因

退出逻辑执行顺序写反了:你先跳转登录页,再执行登出操作。路由切换过程中首页未完全销毁,残留的Firestore请求在登出完成后才发出,此时已经是未登录态,就会报权限错误。另外你把getName()这种异步数据请求直接写在build方法里,每次页面重建都会重复发请求,进一步放大了这个问题。

修复代码

  1. 调整退出逻辑顺序,先登出再跳页:
onPressed: () async {
  // 先执行登出,再跳转路由
  await signOut();
  if(mounted) {
    Navigator.of(context).pushReplacementNamed('/login');
  }
},
  1. 把getName()从build方法移到initState中,只在页面初始化时拉取一次数据,避免重复请求。

问题3:冷启动偶发直接进首页、用户数据为空

根因

Wrapper是无状态组件,仅在首次build时一次性读取currentUser,没有监听认证状态变化:冷启动时Firebase Auth还在加载本地持久化的登录凭证,此时currentUser为null,等凭证加载完成后组件不会自动刷新;部分场景下本地缓存的旧登录态被先读取,直接进入首页,但认证态未完全生效,Firestore请求失败就会显示空数据。
另外getName()方法没有做空安全判断,查询结果为空时直接取docs[0]会触发数组越界崩溃。

修复代码

  1. 改造Wrapper,通过authStateChanges流实时监听认证状态:
class Wrapper extends StatelessWidget {
  const Wrapper({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        // 等待认证状态初始化完成,避免判断错误
        if(snapshot.connectionState == ConnectionState.waiting) {
          return const Scaffold(body: Center(child: CircularProgressIndicator()));
        }
        if(snapshot.data == null) {
          return const LoginPage();
        }
        return const HomePage();
      },
    );
  }
}
  1. 给getName()加空判断和异常捕获:
Future<void> getName() async {
  User? user = auth.currentUser;
  if(user == null) return;
  await user.reload();
  user = auth.currentUser;
  String? temp = user?.email;
  if(temp == null) return;

  try {
    var querySnapshot = await FirebaseFirestore.instance
        .collection('users')
        .where('Email', isEqualTo: temp)
        .limit(1)
        .get();
    if(querySnapshot.docs.isEmpty) return;
    var snapshot = querySnapshot.docs.first;
    Map<String, dynamic> data = snapshot.data();
    if(mounted) {
      setState(() {
        name = data['Name'] ?? '';
      });
    }
  } catch(e) {
    debugPrint('获取用户信息失败: $e');
  }
}

其他优化建议

  • 所有异步UI操作前必须判断mounted属性,避免页面销毁后调用setState触发异常
  • 所有Firestore请求建议包裹try-catch,避免未捕获异常导致应用崩溃
  • 功能验证完成后建议收窄Firestore安全规则,配置为用户仅能读写自身对应的数据,避免越权访问

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 08:57:21