在Widget build方法中获取新创建Firestore文档数据报错的解决方法
问题描述
在initState中调用_createUserTracking方法,该方法会在Firestore的「Tracking Progress」集合不存在用户文档时创建它。但热重启应用后,Widget build里的FutureBuilder获取文档时抛出以下错误:
The following _CastError was thrown building FutureBuilder<DocumentSnapshot<Object?>>(dirty, state: _FutureBuilderState<DocumentSnapshot<Object?>>#50c6f):
type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast
相关代码如下:
void initState() { super.initState(); _createUserTracking(); } Future<void> _createUserTracking() async { String? uid = FirebaseAuth.instance.currentUser?.uid; CollectionReference usersCollection = FirebaseFirestore.instance.collection('Tracking Progress'); DocumentSnapshot userSnapshot = await usersCollection.doc(uid).get(); if (!userSnapshot.exists) { await usersCollection.doc(uid).set({ 'intTotalProgress': 0.7, 'stringTotalProgress': '70%', 'intOralHygienePractices': 0.8, 'stringOralHygienePractices': '80%', 'intCommonOralProblems': 0.7, 'stringCommonOralProblems': '70%', 'intPreventiveMeasures': 0.6, 'stringPreventiveMeasures': '60%', 'intVRContent': 0.4, 'stringVRContent': '40%', }); } } Widget build(BuildContext context) { String? uid = FirebaseAuth.instance.currentUser?.uid; return SingleChildScrollView( child: FutureBuilder<DocumentSnapshot>( future: FirebaseFirestore.instance .collection('Tracking Progress') .doc(uid) .get(), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.exists) { Map data = snapshot.data!.data() as Map; return Column();
解决方案
1. 严谨检查数据非空后再转换
即使判断了snapshot.data!.exists,Firestore的data()方法仍可能返回null(比如文档刚创建但未同步完成、权限限制等)。需先验证data()不为null,再执行类型转换:
builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.exists) { final docData = snapshot.data!.data(); if (docData != null) { Map<String, dynamic> userData = docData as Map<String, dynamic>; // 基于userData构建你的UI return Column( // ... 你的UI代码 ); } else { // 数据为空时的占位UI return const Center(child: Text("数据加载中...")); } } else if (snapshot.hasError) { // 错误处理 return Center(child: Text("加载失败: ${snapshot.error}")); } else { // 加载中状态 return const Center(child: CircularProgressIndicator()); } }
2. 同步初始化与数据请求顺序
initState中的_createUserTracking是异步执行的,而FutureBuilder会立即发起文档请求,可能出现文档未创建完成就被请求的情况,导致获取空数据。
解决方式:将「初始化文档+获取文档」合并为一个Future,让FutureBuilder等待这个组合操作完成:
late Future<DocumentSnapshot> _userTrackingFuture; @override void initState() { super.initState(); _userTrackingFuture = _createAndGetUserTracking(); } Future<DocumentSnapshot> _createAndGetUserTracking() async { String? uid = FirebaseAuth.instance.currentUser?.uid; if (uid == null) { throw Exception("用户未登录"); } CollectionReference usersCollection = FirebaseFirestore.instance.collection('Tracking Progress'); // 先检查并创建文档(如果不存在) DocumentSnapshot userSnapshot = await usersCollection.doc(uid).get(); if (!userSnapshot.exists) { await usersCollection.doc(uid).set({ 'intTotalProgress': 0.7, 'stringTotalProgress': '70%', 'intOralHygienePractices': 0.8, 'stringOralHygienePractices': '80%', 'intCommonOralProblems': 0.7, 'stringCommonOralProblems': '70%', 'intPreventiveMeasures': 0.6, 'stringPreventiveMeasures': '60%', 'intVRContent': 0.4, 'stringVRContent': '40%', }); } // 创建完成后再获取最新文档数据 return usersCollection.doc(uid).get(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: FutureBuilder<DocumentSnapshot>( future: _userTrackingFuture, // 使用组合后的Future builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.exists) { final docData = snapshot.data!.data(); if (docData != null) { Map<String, dynamic> userData = docData as Map<String, dynamic>; return Column( // ... 你的UI代码 ); } } if (snapshot.hasError) { return Center(child: Text("错误: ${snapshot.error}")); } return const Center(child: CircularProgressIndicator()); }, ), ); }
3. 使用明确的类型转换
Firestore返回的数据类型是Map<String, dynamic>,直接转换为Map会存在类型安全隐患,明确指定类型可以避免潜在错误。
内容的提问来源于stack exchange,提问作者Ariff Haikal

