Flutter中FirebaseAuth.currentUser!报空检查运算符错误排查
问题代码
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:google_nav_bar/google_nav_bar.dart'; import 'package:projectciplified/screens/read%20data/get_user_name.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final user = FirebaseAuth.instance.currentUser!; // 存储文档ID List<String> docIDs = []; // 获取文档ID的方法 Future getDocID() async { await FirebaseFirestore.instance.collection('users').get().then( (snapshot) => snapshot.docs.forEach( (document) { print(document.reference); docIDs.add(document.reference.id); }, ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( user.email!, style: TextStyle(fontSize: 16), ), actions: [ GestureDetector( onTap: () { FirebaseAuth.instance.signOut(); }, child: Icon(Icons.logout)), ], ), bottomNavigationBar: Container( color: Colors.black, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 15, vertical: 20.0, ), child: GNav( backgroundColor: Colors.black, color: Colors.white, activeColor: Colors.white, tabBackgroundColor: Colors.blue, padding: EdgeInsets.all(10), gap: 2, onTabChange: (index) {}, tabs: [ GButton( icon: Icons.home, text: 'Home', ), GButton( icon: Icons.favorite_border, text: 'likes', ), GButton( icon: Icons.search, text: 'search', ), GButton( icon: Icons.settings, text: 'settings', ), ], ), ), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: FutureBuilder( future: getDocID(), builder: (context, snapshot) { return ListView.builder( itemCount: docIDs.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8.0), child: ListTile( tileColor: Colors.grey[200], title: GetUserName(documentID: docIDs[index]), ), ); }, ); }, )), ], ), ), ); } }
错误原因
- 空断言运算符
!的作用是强制向Dart运行时声明“该值绝对不为空”,一旦实际值为null就会直接抛出Null check operator used on a null value错误。FirebaseAuth.instance.currentUser本身就是可空类型:用户未登录、Auth模块未完成初始化、用户主动登出、本地登录凭证失效时,它的返回值都是null,直接加空断言属于不安全写法。 - 报错的直接触发点是:你在
_HomePageState类成员初始化阶段就执行FirebaseAuth.instance.currentUser!,这个时机Firebase Auth还没完成异步初始化,拿不到有效用户对象,值为null,空断言校验直接失败。 - 之前代码能正常运行,是因为当时处于热重载/热启动状态,用户登录状态已经缓存在应用内存中,类初始化时可以直接拿到非空的用户对象;本次运行属于冷启动、或者之前触发了登出逻辑、本地凭证过期,初始化阶段拿不到用户就会报错。
- 去掉
!后代码不报错只是语法层面允许可空类型存在,并不代表问题修复:后续执行到user.email!逻辑时,如果user为null依然会触发同类空崩溃,只是当前还没执行到对应步骤而已。
修复方案
- 不要在State初始化阶段直接强行读取
currentUser,Firebase Auth的状态初始化是异步过程,必须等状态就绪后再获取用户信息,推荐使用官方提供的authStateChanges流监听身份状态变化,根据状态渲染对应内容:
@override Widget build(BuildContext context) { return StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { // 身份状态加载中时显示加载指示器 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 用户未登录时直接跳转登录页 if (!snapshot.hasData || snapshot.data == null) { WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pushReplacementNamed(context, '/login'); }); return const SizedBox.shrink(); } // 到这里用户一定非空,可以安全使用 final user = snapshot.data!; // 返回原有Scaffold内容 return Scaffold( appBar: AppBar( title: Text( user.email ?? '', // 不要对email随意加空断言,为空时显示空字符串即可 style: const TextStyle(fontSize: 16), ), actions: [ GestureDetector( onTap: () { FirebaseAuth.instance.signOut(); }, child: const Icon(Icons.logout)), ], ), bottomNavigationBar: Container( color: Colors.black, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 15, vertical: 20.0, ), child: GNav( backgroundColor: Colors.black, color: Colors.white, activeColor: Colors.white, tabBackgroundColor: Colors.blue, padding: const EdgeInsets.all(10), gap: 2, onTabChange: (index) {}, tabs: const [ GButton( icon: Icons.home, text: 'Home', ), GButton( icon: Icons.favorite_border, text: 'likes', ), GButton( icon: Icons.search, text: 'search', ), GButton( icon: Icons.settings, text: 'settings', ), ], ), ), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: FutureBuilder( future: getDocID(), builder: (context, snapshot) { return ListView.builder( itemCount: docIDs.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8.0), child: ListTile( tileColor: Colors.grey[200], title: GetUserName(documentID: docIDs[index]), ), ); }, ); }, )), ], ), ), ); }, ); }
- 同步修复代码里的隐藏bug:原来的
getDocID方法获取完文档ID后没有调用setState,ListView不会自动刷新数据,修改为:
Future getDocID() async { docIDs.clear(); // 拉取前先清空旧数据避免重复 await FirebaseFirestore.instance.collection('users').get().then( (snapshot) => snapshot.docs.forEach( (document) { print(document.reference); docIDs.add(document.reference.id); }, ), ); // 页面未销毁时才触发刷新 if (mounted) { setState(() {}); } }
- 记得把类里原来写的
final user = FirebaseAuth.instance.currentUser!;这行成员变量删掉,避免初始化时再次触发空错误。
内容的提问来源于stack exchange,提问作者pika3113
相关产品推荐
相关产品推荐

