Flutter从Firebase取数据转DateTimeRange时栈溢出问题排查
从Firebase解析数据转换为DateTimeRange列表时栈溢出问题
问题描述
使用预订日历包时,需要从Firebase读取数据并转换为DateTimeRange列表。查询无数据的时间范围时能得到空列表,但查询有数据的范围时出现栈溢出错误,确认数据已被读取但无法解析。
代码展示
final bookings = FirebaseFirestore.instance.collection('bookings'); /// 获取数据引用并通过Firestore的[withConverter]序列化数据 /// 该函数通常放在仓库类中 CollectionReference<SportBooking> getBookingStream({required String placeId}) { return bookings.doc(placeId).collection('bookings').withConverter<SportBooking>( fromFirestore: (snapshots, _) => SportBooking.fromJson(snapshots.data()!), toFirestore: (snapshots, _) => snapshots.toJson(), ); } /// 从Firestore获取数据流(注意:原查询过滤逻辑需适配自身数据结构) /// 无数据时返回空转换列表,但有数据时触发栈溢出错误,确认数据已读取但无法解析为DateTimeRange列表 Stream<dynamic>? getBookingStreamFirebase( {required DateTime end, required DateTime start}) { return FirebaseFirestore.instance.collection('bookings').withConverter<SportBooking>( fromFirestore: (snapshots, _) => SportBooking.fromJson(snapshots.data()!), toFirestore: (snapshots, _) => snapshots.toJson(), ).where('bookingStart', isGreaterThanOrEqualTo: DateTime.now() .where('bookingStart', isLessThanOrEqualTo: DateTime.now().add(const Duration(days: 50))).snapshots(); } /// 将Firestore返回的数据转换为DateTimeRange列表 List<DateTimeRange> convertStreamResultFirebase( {required dynamic streamResult}) { /// 需要将快照转换为List<DateTimeRange> // final data = snapshot.requireData; **// 这是最关键的一行,但我没理解清楚** /// 解析streamResult并转换为[List<DateTimeRange>] /// 注意参数为dynamic,需明确结果的可用属性,本例中SportBooking包含bookingStart和bookingEnd属性 List<DateTimeRange> converted = [] for (var i = 0; i < data.size; i++) { final item = data.docs[i].data(); converted.add(DateTimeRange(start: (item.bookingStart!), end: (item.bookingEnd!))); } return converted; }
Firestore数据结构

错误原因及修复方案
1. 查询方法的语法错误(栈溢出核心原因)
getBookingStreamFirebase中where条件嵌套写法错误,把第二个where调用写在了DateTime.now()对象上,导致生成异常的查询逻辑,有数据返回时触发栈溢出。正确写法应为链式调用where:
Stream<QuerySnapshot<SportBooking>> getBookingStreamFirebase({required DateTime end, required DateTime start}) { return FirebaseFirestore.instance .collection('bookings') .withConverter<SportBooking>( fromFirestore: (snapshots, _) => SportBooking.fromJson(snapshots.data()!), toFirestore: (snapshots, _) => snapshots.toJson(), ) // 链式调用where,而非嵌套在DateTime对象后 .where('bookingStart', isGreaterThanOrEqualTo: start) .where('bookingStart', isLessThanOrEqualTo: end) .snapshots(); }
2. 转换方法的变量未定义问题
convertStreamResultFirebase中直接使用未定义的data变量,且参数类型模糊。需明确参数类型并正确提取数据:
List<DateTimeRange> convertStreamResultFirebase({required QuerySnapshot<SportBooking> snapshot}) { List<DateTimeRange> converted = []; for (var doc in snapshot.docs) { final item = doc.data(); converted.add(DateTimeRange(start: item.bookingStart!, end: item.bookingEnd!)); } return converted; }
更简洁的写法:
List<DateTimeRange> convertStreamResultFirebase({required QuerySnapshot<SportBooking> snapshot}) { return snapshot.docs.map((doc) { final booking = doc.data(); return DateTimeRange(start: booking.bookingStart!, end: booking.bookingEnd!); }).toList(); }
3. 类型安全优化
避免使用dynamic类型,明确指定Stream的类型为Stream<QuerySnapshot<SportBooking>>,编译期即可发现错误,避免运行时异常。
4. Timestamp与DateTime的转换验证
从Firestore截图看,bookingStart和bookingEnd为Timestamp类型,需确保SportBooking的fromJson方法正确转换:
class SportBooking { final DateTime? bookingStart; final DateTime? bookingEnd; SportBooking({this.bookingStart, this.bookingEnd}); factory SportBooking.fromJson(Map<String, dynamic> json) { return SportBooking( // 正确将Timestamp转换为DateTime bookingStart: json['bookingStart'] is Timestamp ? (json['bookingStart'] as Timestamp).toDate() : null, bookingEnd: json['bookingEnd'] is Timestamp ? (json['bookingEnd'] as Timestamp).toDate() : null, ); } Map<String, dynamic> toJson() { return { 'bookingStart': bookingStart != null ? Timestamp.fromDate(bookingStart!) : null, 'bookingEnd': bookingEnd != null ? Timestamp.fromDate(bookingEnd!) : null, }; } }
转换逻辑错误会导致访问bookingStart/bookingEnd时触发异常,进而引发栈溢出。
内容的提问来源于stack exchange,提问作者godopetza
相关产品推荐
相关产品推荐

