You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:09:17