Firebase RTDB v9中await查询异常问题咨询及解决方案
问题现象
当前使用Firebase RTDB 8.2版本,升级至9.1版本后,应用中使用await的查询出现严重异常:await不再等待完整数据加载完成,仅获取第一条数据就继续执行后续逻辑,导致计算结果错误。
测试代码中的核心逻辑是点击按钮触发_submit()方法,从数据库加载商品列表并计算数量总和:
Future<void> _submit() async { List<ItemModel> itemsList = []; itemsList = await loadItems(); int _sumQty = itemsList.fold(0, (a, b) => a + b.quantity!); print('$_sumQty was the sum of quantities'); }
v8.2.0版本(Web端)预期结果
{item: iPhone, quantity: 5} {item: Samsung, quantity: 10} {item: LG, quantity: 15} {item: NTC, quantity: 20} {item: Zenith, quantity: 25} {item: Sony, quantity: 30} {item: JVC, quantity: 40}
7 Items were loaded
145 was the sum of quantities
v9.1.4版本(Web端)错误结果
{item: iPhone, quantity: 5}
1 Items were loaded
5 was the sum of quantities
{item: Samsung, quantity: 10} {item: LG, quantity: 15} {item: NTC, quantity: 20} {item: Zenith, quantity: 25} {item: Sony, quantity: 30} {item: JVC, quantity: 40}
该问题Web端100%复现,移动端(Android)偶发。
核心疑问
- Firebase新版本中await行为变更的根源是什么?
- 若迁移至最新版本,需对代码做出哪些修改以避免此类查询异常?
附件:Firebase 8.2.0完整测试代码
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/ui/firebase_list.dart'; import 'package:flutter/material.dart'; class Prueba extends StatefulWidget { const Prueba({Key? key}) : super(key: key); @override State<Prueba> createState() => _PruebaState(); } class ItemModel { String? item; int? quantity; ItemModel({ this.item, this.quantity, }); } class _PruebaState extends State<Prueba> { @override void dispose() { super.dispose(); } @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Async InitState Test'), ), body: createButton('Query'), ); } Widget createButton(String texto) { return Container( width: double.infinity, padding: const EdgeInsets.only(top:30.0, left:10.0, right:10.0), child: ElevatedButton( onPressed: _submit, child: Text(texto, style: Theme.of(context).textTheme.headline6), ), ); } Future<void> _submit() async { List<ItemModel> itemsList = []; itemsList = await loadItems(); int _sumQty = itemsList.fold(0, (a, b) => a + b.quantity!); print('$_sumQty was the sum of quantities'); } Future<List<ItemModel>> loadItems() async { final List<ItemModel> items = []; String path = '/items'; Query resp = FirebaseDatabase.instance.reference().child(path); FirebaseList( query: resp, onChildAdded: (i, element) { print(element.value); ItemModel temp = ItemModel() ..item = element.value["item"] ..quantity = element.value["quantity"]; items.add(temp); }, onError: (e) => print(e.message) ); await resp.once().then((snapshot) { print("${items.length} Items were loaded"); }); return items; } }
适配Firebase 9.1.4的原始修改(存在问题)
Future<List<ItemModel>> loadItems() async { final List<ItemModel> items = []; String path = '/empresas/-ME9qZY5k8RxpCymZlV2/items'; Query resp = FirebaseDatabase.instance.ref().child(path); FirebaseList( query: resp, onChildAdded: (i, element) { print(element.value); Map<dynamic, dynamic> map = element.value as dynamic; ItemModel temp = ItemModel() ..item = map["item"] ..quantity = map["quantity"]; items.add(temp); }, onError: (e) => print(e.message) ); await resp.once().then((snapshot) { print("${items.length} Items were loaded"); }); return items; }
问题解答
1. 行为变更的根源
Firebase RTDB v9+采用模块化API重构,同时调整了一次性读取(once/get)与实时监听(onChildAdded)的执行逻辑绑定关系:
- 在v8版本中,
once()方法会隐式等待FirebaseList的所有onChildAdded回调执行完毕后,才完成Future; - v9+版本中,
once()(或替代的get())仅保证获取到数据快照本身,不再等待FirebaseList的异步回调触发——FirebaseList作为UI组件,其回调是独立的异步流程,和一次性读取的Future完成时机不再绑定。
这就导致v9+中await resp.once()完成时,onChildAdded可能只触发了第一条数据的回调,后续数据还在异步加载,最终返回的列表不完整。
2. 代码修改方案
你的核心问题是混用了UI组件FirebaseList和一次性数据读取逻辑,正确的做法是直接通过数据快照解析完整数据,而非依赖FirebaseList的回调:
修改后的loadItems方法
Future<List<ItemModel>> loadItems() async { final List<ItemModel> items = []; String path = '/empresas/-ME9qZY5k8RxpCymZlV2/items'; // 使用v9+推荐的get()方法替代once() final snapshot = await FirebaseDatabase.instance.ref().child(path).get(); if (snapshot.exists) { // 遍历快照所有子节点,一次性解析完整数据 snapshot.children.forEach((child) { Map<dynamic, dynamic> map = child.value as dynamic; print(map); ItemModel temp = ItemModel() ..item = map["item"] ..quantity = map["quantity"]; items.add(temp); }); } print("${items.length} Items were loaded"); return items; }
修改说明
- 移除
FirebaseList:它是用于UI实时渲染的组件,不适合在纯数据获取的异步方法中使用; - 替换
once()为get():这是v9+模块化API的标准一次性读取方法,功能一致但更符合新API规范; - 直接遍历快照
children:从完整数据快照中一次性解析所有子节点,确保返回的列表是完整的,避免异步回调的时序问题。
如果需要实时监听数据更新(比如UI需要同步数据库变化),应在Widget层使用FirebaseList或StreamBuilder,而非在异步数据方法中依赖其回调收集数据。
内容的提问来源于stack exchange,提问作者David L

