Flutter如何解析API响应到模型类并用FutureBuilder+ListView展示
Flutter 接口嵌套解析与FutureBuilder列表渲染方案
一、完整响应数据解析实现
你已完成基础模型方法定义,核心需要补全嵌套List<Result>的显式转换逻辑,Dart不支持泛型集合的隐式向下转型,直接强转列表会触发运行时错误。
1. 模型类参考实现
// 列表条目模型 class Result { final String stockid; final String itemname; final String unit; final num costperunit; Result({ required this.stockid, required this.itemname, required this.unit, required this.costperunit, }); factory Result.fromMap(Map<String, dynamic> map) { return Result( stockid: map['stockid']?.toString() ?? '', itemname: map['itemname']?.toString() ?? '', unit: map['unit']?.toString() ?? '', costperunit: map['costperunit'] ?? 0, ); } Map<String, dynamic> toMap() { return { 'stockid': stockid, 'itemname': itemname, 'unit': unit, 'costperunit': costperunit, }; } } // 顶层响应模型 class ResultData { final String trxid; final String datetime; final String reqid; final String id; final int responsecode; final String message; final String serverkey; final List<Result> result; ResultData({ required this.trxid, required this.datetime, required this.reqid, required this.id, required this.responsecode, required this.message, required this.serverkey, required this.result, }); factory ResultData.fromMap(Map<String, dynamic> map) { // 空值兜底,避免接口返回null时崩溃 final rawList = map['result'] as List? ?? []; // 遍历集合逐个转换为Result实例 final parsedResultList = rawList .map((item) => Result.fromMap(item as Map<String, dynamic>)) .toList(); return ResultData( trxid: map['trxid']?.toString() ?? '', datetime: map['datetime']?.toString() ?? '', reqid: map['reqid']?.toString() ?? '', id: map['id']?.toString() ?? '', responsecode: map['responsecode'] ?? -1, message: map['message']?.toString() ?? '', serverkey: map['serverkey']?.toString() ?? '', result: parsedResultList, ); } Map<String, dynamic> toMap() { return { 'trxid': trxid, 'datetime': datetime, 'reqid': reqid, 'id': id, 'responsecode': responsecode, 'message': message, 'serverkey': serverkey, 'result': result.map((e) => e.toMap()).toList(), }; } }
2. 请求方法内调用解析
修改你的searchRequest异步方法,在拿到接口响应后完成解码+转换,最终返回ResultData类型:
Future<ResultData> searchRequest() async { // 保留你已写的POST请求逻辑,拿到response对象后补充以下逻辑 final response = await http.post(/* 你的原有请求参数 */); // 先将JSON字符串解码为原生Map final responseMap = json.decode(response.body) as Map<String, dynamic>; // 调用fromMap完成全量解析,直接返回ResultData实例 return ResultData.fromMap(responseMap); }
注意:需要提前导入dart:convert库使用JSON解码能力。
二、FutureBuilder搭配ListView实现列表展示
禁止在build方法内直接调用请求方法,会导致每次页面重建都重复发起请求,需要将请求Future提前在initState中初始化。
完整页面实现参考
class StockSearchPage extends StatefulWidget { const StockSearchPage({super.key}); @override State<StockSearchPage> createState() => _StockSearchPageState(); } class _StockSearchPageState extends State<StockSearchPage> { late Future<ResultData> _searchFuture; @override void initState() { super.initState(); // 初始化时发起一次请求 _searchFuture = searchRequest(); } // 手动刷新/重新搜索时调用 void _retrySearch() { setState(() { _searchFuture = searchRequest(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('库存搜索结果')), body: FutureBuilder<ResultData>( future: _searchFuture, builder: (context, snapshot) { // 加载状态 if (snapshot.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } // 请求错误状态 if (snapshot.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('请求失败:${snapshot.error}'), const SizedBox(height: 16), ElevatedButton( onPressed: _retrySearch, child: const Text('重试'), ) ], ), ); } final data = snapshot.data!; // 接口业务状态校验,替换为你项目的成功状态码 if (data.responsecode != 200) { return Center(child: Text('业务异常:${data.message}')); } // 空数据状态 if (data.result.isEmpty) { return const Center(child: Text('暂无匹配的库存数据')); } // 列表渲染,长列表优先用ListView.builder保证性能 return ListView.builder( padding: const EdgeInsets.all(16), itemCount: data.result.length, itemBuilder: (context, index) { final item = data.result[index]; return Card( margin: const EdgeInsets.only(bottom: 12), child: ListTile( title: Text(item.itemname), subtitle: Text('库存编号:${item.stockid}'), trailing: Text('${item.costperunit} 元/${item.unit}'), ), ); }, ); }, ), ); } }
关键注意点
- FutureBuilder要指定明确的泛型
FutureBuilder<ResultData>,避免类型动态化导致的异常 - 必须覆盖加载、错误、空数据、正常渲染四种状态,不要直接假设请求一定成功
- 列表项较多时不要使用
ListView()默认构造函数直接传入子组件数组,ListView.builder会按需创建列表项,性能表现更好
内容的提问来源于stack exchange,提问作者Yuzzy
相关产品推荐
相关产品推荐

