Flutter解析API返回JSON渲染ListView时提示plans字段未初始化
问题原因
一共4个核心问题导致报错:
- JSON字段映射不匹配:
Datum类的fromJson方法中,读取operator_id、circle_id字段时错误使用了operatorid、circleid作为key,和接口返回的JSON key名不一致,解析时会触发空值错误,导致plans对象赋值失败。 - 异步流程处理缺失:API请求是异步操作,你用
late修饰plans字段承诺一定会初始化,但没有在组件初始化阶段触发请求、等待数据返回,首次build执行时直接访问未赋值的plans,直接触发LateInitializationError。 - 组件语法错误:
Scaffold的body属性下同时写了Text和ListView两个组件,没有父容器包裹属于语法错误;同时ListView中错误使用widget.plans.data.length访问字段,plans是State内部变量,不属于Widget属性,访问路径错误。 - 异常处理缺失:接口请求逻辑没有判断HTTP响应状态码,一旦接口返回401/403/500等错误状态,直接解析响应体会抛出异常,导致
plans永远无法被赋值。
修复方案
1. 修正模型类字段映射
对齐接口返回的JSON key,同时增加类型兼容处理,修正后的Datum.fromJson代码如下:
factory Datum.fromJson(Map<String, dynamic> json) => Datum( id: json["id"].toString(), operatorid: json["operator_id"].toString(), circleid: json["circle_id"].toString(), rechargeAmount: json["recharge_amount"].toString(), rechargeTalktime: json["recharge_talktime"].toString(), rechargeValidity: json["recharge_validity"].toString(), rechargeShortdesc: json["recharge_short_desc"].toString(), rechargeLongdesc: json["recharge_long_desc"].toString(), rechargeType: json["recharge_type"].toString(), updatedAt: json["updated_at"].toString(), );
2. 修正接口请求逻辑
增加HTTP状态码判断,避免错误响应直接触发解析异常:
Future<Plans> getPlanDetails( String operator, String circle, String rechargeType) async { var planUrl = Uri.parse( 'https://api.datayuge.com/v6/rechargeplans/?apikey=$datayugeApiKey&operator_id=$operator&circle_id=$circle&recharge_type=$rechargeType&limit=50&order_by=recharge_amount'); debugPrint(planUrl.toString()); final response = await http.get(planUrl); debugPrint(response.body); if (response.statusCode == 200) { String jsonString = response.body; return plansFromJson(jsonString); } else { throw Exception('请求失败,状态码:${response.statusCode}'); } }
3. 重写页面组件逻辑
去掉不安全的late修饰,增加加载、错误状态处理,在组件初始化阶段触发数据请求,修正后的页面代码:
class RechargePlansView extends StatefulWidget { final String operator; final String circle; final String rechargeType; const RechargePlansView({ Key? key, required this.operator, required this.circle, required this.rechargeType }) : super(key: key); @override State<RechargePlansView> createState() => _RechargePlansViewState(); } class _RechargePlansViewState extends State<RechargePlansView> { List<Datum> planList = []; bool isLoading = true; String? errorMsg; @override void initState() { super.initState(); fetchPlanData(); } Future<void> fetchPlanData() async { try { final res = await getPlanDetails( widget.operator, widget.circle, widget.rechargeType ); setState(() { planList = res.data; isLoading = false; }); } catch (e) { setState(() { errorMsg = e.toString(); isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Recharge Plans')), body: isLoading ? const Center(child: CircularProgressIndicator()) : errorMsg != null ? Center(child: Text('加载失败:$errorMsg')) : ListView.builder( itemCount: planList.length, itemBuilder: (context, index) { final currentItem = planList[index]; return ListTile( leading: CircleAvatar( radius: 28, child: Center( child: Text('\u{20B9}${currentItem.rechargeAmount}'), ), ), title: Text('Talktime: ${currentItem.rechargeTalktime} Validity: ${currentItem.rechargeValidity}'), subtitle: Text(currentItem.rechargeShortdesc), ); } ), ); } }
注意事项
- 所有异步加载的数据不要用
late强制标记非空,必须预留空状态、加载状态、错误状态的UI逻辑,避免组件首次渲染时访问未初始化的字段。 - JSON序列化时必须严格对齐接口返回的key名,建议通过代码生成工具自动生成序列化逻辑,减少手写key的错误。
- Flutter组件的布局属性(如Scaffold的body、Container的child)只能接收单个组件,多组件布局需要用Column、Row、Stack等容器类组件包裹。
内容的提问来源于stack exchange,提问作者Rithik Sandron
相关产品推荐
相关产品推荐

