Flutter App构建卡顿:JSON资源加载与Riverpod使用问题
Flutter加载本地JSON数据卡在启动界面的问题修复
我是上月刚接触Flutter的新手,尝试从本地JSON文件加载Item对象并构建列表页面,但手机模拟器一直卡在启动界面,推测可能存在竞态条件或代码逻辑错误。以下是相关代码及修复方案:
原代码片段
列表构建代码
Widget _buildItemList(List<Item> currentItemList) { return ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, itemCount: currentItemList.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute( builder: (context) { return ItemDetailsPage( item: currentItemList[index]); }, )); }, child: Text(currentItemList[index].productName) ); } ); }
FutureBuilder与数据获取代码
Future<List<Item>> fetchItems() { final inventoryService = ref.watch(serviceProvider); Future<List<Item>> items = inventoryService.fetchData(); return items; } Widget _futureBuilder() { return FutureBuilder<List<Item>>( future: fetchItems(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { loading = false; final result = snapshot.data; if (result is Error) { const errorMessage = 'Problems getting data'; return const Center( child: Text( errorMessage, textAlign: TextAlign.center, style: TextStyle(fontSize: 18.0), ), ); } return _buildItemList(result!); } else { return const SliverFillRemaining( child: Center( child: CircularProgressIndicator(), ), ); } } ); }
服务层数据加载代码
Future loadItems() async { var jsonString = await rootBundle.loadString('assets/inventorylist.json'); inventoryResult = InventoryResult.fromJson(jsonDecode(jsonString)); } @override Future<List<Item>> fetchData() async { _currentItems = inventoryResultsToItems(inventoryResult) as Future<List<Item>>; return _currentItems ?? Future.error('No data found'); }
主函数代码
Future<void> main() async { final service = await JsonInventoryService.create(); //runs loadItems() runApp(ProviderScope(overrides: [ serviceProvider.overrideWithValue(service), ], child: const MyApp())); }
问题分析
- 类型转换错误:
inventoryResultsToItems(inventoryResult)返回的是List<Item>,却被强制转换为Future<List<Item>>,直接触发类型异常,导致数据加载失败。 - 错误判断逻辑错误:FutureBuilder中用
result is Error判断错误是无效的,正确做法是检查snapshot.hasError。 - Future重复执行:
FutureBuilder的future参数直接调用fetchItems(),每次页面build都会重新发起请求,导致状态混乱。 - 数据加载未等待:若
JsonInventoryService.create()中未等待loadItems()完成就返回实例,会导致fetchData()调用时inventoryResult未初始化,返回空错误。 - 布局嵌套冲突:
_futureBuilder返回SliverFillRemaining,而_buildItemList返回ListView,嵌套滚动组件可能导致布局异常。
修复后的代码
修正服务层逻辑
class JsonInventoryService { InventoryResult? inventoryResult; List<Item>? _currentItems; // 确保JSON加载完成后再返回服务实例 static Future<JsonInventoryService> create() async { final service = JsonInventoryService(); await service.loadItems(); return service; } Future<void> loadItems() async { var jsonString = await rootBundle.loadString('assets/inventorylist.json'); inventoryResult = InventoryResult.fromJson(jsonDecode(jsonString)); } Future<List<Item>> fetchData() async { if (inventoryResult == null) { return Future.error('库存数据未加载'); } // 移除错误的Future类型转换 _currentItems = inventoryResultsToItems(inventoryResult!); return _currentItems ?? Future.error('未找到数据'); } }
修正FutureBuilder逻辑
// 在State类中存储future,避免重复执行 late final Future<List<Item>> _itemsFuture; @override void initState() { super.initState(); _itemsFuture = fetchItems(); } Future<List<Item>> fetchItems() { final inventoryService = ref.watch(serviceProvider); return inventoryService.fetchData(); } Widget _futureBuilder() { return FutureBuilder<List<Item>>( future: _itemsFuture, builder: (context, snapshot) { // 等待状态显示加载指示器 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 错误状态显示错误信息 else if (snapshot.hasError) { return Center( child: Text( '加载失败: ${snapshot.error}', textAlign: TextAlign.center, style: const TextStyle(fontSize: 18.0), ), ); } // 空数据状态提示 else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center( child: Text( '暂无数据', style: TextStyle(fontSize: 18.0), ), ); } // 成功加载显示列表 else { return _buildItemList(snapshot.data!); } }, ); }
修正列表布局(避免嵌套滚动)
Widget _buildItemList(List<Item> currentItemList) { return ListView.builder( scrollDirection: Axis.vertical, itemCount: currentItemList.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute( builder: (context) => ItemDetailsPage(item: currentItemList[index]), )); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text(currentItemList[index].productName), ), ); }, ); }
内容的提问来源于stack exchange,提问作者Stooges4
相关产品推荐
相关产品推荐

