Flutter从Hive取数据首次运行列表为空 热重启后才显示问题
问题根因
- 代码存在明显变量名笔误:
getData()方法中await Hive.openBox('venuesBox')得到的盒子实例名为venueBox,但setState里读取数据时用的是未在该方法内定义的venuesBox,存在实例引用不一致的竞态问题。首次进入页面时读取的是未完成初始化的盒子实例,拿到空数据;热重启后全局盒子实例完成初始化,才能读到数据。这也是直接读本地json的readJson()方法能正常运行的核心原因之一——该方法不存在变量引用错误。 - 未在应用启动入口完成Hive前置初始化:如果在main函数中没有等待Hive初始化、盒子打开、自定义类适配器注册完成就直接runApp,会出现Hive操作时序问题,首次运行时异步开盒的速度慢于首帧渲染速度,导致空列表。
- 未处理首次运行时Hive盒子无数据的场景:测试时热重启能读到数据,是因为之前调试流程中已经把数据写入过Hive持久化存储,但首次冷启动安装应用时,刚创建的Hive盒子本身是空的,自然读不到数据。
修复步骤
- 先修正应用入口
main函数的Hive初始化逻辑,确保所有Hive相关前置操作在runApp之前完成,从根源避免时序竞态:
void main() async { // 绑定Flutter插件引擎,否则异步调用原生插件能力会报错 WidgetsFlutterBinding.ensureInitialized(); // 初始化Hive本地存储 await Hive.initFlutter(); // 如果Venue是自定义类,提前注册Hive TypeAdapter,否则无法正确解析存储的对象 // Hive.registerAdapter(VenueAdapter()); // 提前打开业务需要用到的盒子,不用在每个页面里重复openBox await Hive.openBox('venuesBox'); runApp(const MyApp()); }
- 修正
getData()方法的变量笔误,同时补充首次运行空盒子的初始数据写入逻辑:
Future<void> getData() async { // 直接拿全局已经初始化完成的盒子实例 final venueBox = Hive.box('venuesBox'); List<Venue> storedVenues = List<Venue>.from(venueBox.values.toList()); // 首次运行盒子为空时,从asset读取初始json数据写入Hive,后续启动直接读本地存储 if (storedVenues.isEmpty) { final String response = await rootBundle.loadString('assets/venues.json'); final data = await json.decode(response); storedVenues = List<Venue>.from(data["items"]); await venueBox.addAll(storedVenues); } setState(() { _venues = storedVenues; }); }
- 给列表增加加载状态判断,避免异步读数据时直接渲染空列表出现白屏,修改build方法中body部分逻辑:
@override Widget build(BuildContext context) { return Scaffold( body: _venues.isEmpty ? const Center(child: CircularProgressIndicator()) : GroupedListView<Venue, String>( elements: _venues, groupBy: (Venue v) => v.venueGroup, groupComparator: (value1, value2) => value2.compareTo(value1), order: GroupedListOrder.DESC, useStickyGroupSeparators: true, separator: Divider( height: 1.0, color: Colors.grey, ), groupSeparatorBuilder: (String value) => Padding( padding: const EdgeInsets.all(0.0), child: Container( padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0), color: kMidBlueColour, child: Text( value.toUpperCase(), textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.normal, fontFamily: 'SanFranciscoDisplay', color: Colors.white), ), ), ), itemBuilder: (c, Venue v) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), leading: Hero( tag: v.venueName, child: Container( child: Image.asset( 'images/venue1440/' + v.venueImage, fit: BoxFit.contain, ), ), ), title: Text( v.venueTitle.toString().toUpperCase(), style: TextStyle( color: kMidBlueColour, fontFamily: 'SanFranciscoDisplay', fontSize: 20.0, ), ), subtitle: Text( v.venueAddress, style: TextStyle( color: Color(0xFF4A4A49), fontFamily: 'SanFranciscoDisplay', fontSize: 18.0, ), ), trailing: const Icon( Icons.circle, color: Colors.grey, size: 50.0, ), visualDensity: const VisualDensity(horizontal: 4, vertical: 0), onTap: () {}, ); }, ), ); }
内容的提问来源于stack exchange,提问作者ourmanflint
相关产品推荐
相关产品推荐

