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

Flutter ListView.builder无法显示API返回的JSON响应数据

问题根因

三个问题叠加导致列表无法渲染:

  • 核心错误:JSON解析阶段直接抛出异常,被FutureBuilder静默吞掉。你定义的Clinica模型里emailClinica是必填非空字段,但接口实际返回的JSON数据里完全不存在email_clinica键,解析时取到null值触发类型错误,Future以异常状态结束,snapshot.hasData永远为false。
  • 写法错误:你直接把fetchClinicas(miId)写在FutureBuilder的future参数内,每次页面触发rebuild都会重新发起请求、重建Future实例,导致请求永远无法稳定完成。
  • 逻辑缺失:FutureBuilder的builder方法里没有判断异常状态、加载状态,出错后直接落到无数据分支,完全看不到实际报错信息。
修复步骤

1. 修复模型类字段和接口返回不匹配问题

要么把emailClinica改成可空类型,要么给它设置默认空值,适配接口没有返回该字段的场景:

class Clinica {
  Clinica({
    required this.idClinica,
    required this.nombreClinica,
    required this.direccionClinica,
    required this.telClinica,
    this.emailClinica, // 去掉required,允许为空
    required this.codClinica,
    required this.fechaIngresoClinica,
    required this.logoClinica,
    required this.visiblePacientes,
    required this.visibleAgenda,
    required this.visibleForo,
    required this.visibleSAT
  });

  String idClinica;
  String nombreClinica;
  String direccionClinica;
  String telClinica;
  String? emailClinica; // 类型后加?,声明为可空类型
  String codClinica;
  DateTime fechaIngresoClinica;
  String logoClinica;
  String visiblePacientes;
  String visibleAgenda;
  String visibleForo;
  String visibleSAT;

  factory Clinica.fromJson(Map<String, dynamic> json) => Clinica(
    idClinica: json["id_clinica"],
    nombreClinica: json["nombre_clinica"],
    direccionClinica: json["direccion_clinica"],
    telClinica: json["tel_clinica"],
    emailClinica: json["email_clinica"], // 拿不到值会自动赋值null
    codClinica: json["cod_clinica"],
    fechaIngresoClinica: DateTime.parse(json["fecha_ingreso_clinica"]),
    logoClinica: json["logo_clinica"],
    visiblePacientes: json["visiblePacientes"],
    visibleAgenda: json["visibleAgenda"],
    visibleForo: json["visibleForo"],
    visibleSAT: json["visibleSAT"],
  );

  Map<String, dynamic> toJson() => {
    "id_clinica": idClinica,
    "nombre_clinica": nombreClinica,
    "direccion_clinica": direccionClinica,
    "tel_clinica": telClinica,
    "email_clinica": emailClinica,
    "cod_clinica": codClinica,
    "fecha_ingreso_clinica": "${fechaIngresoClinica.year.toString().padLeft(4, '0')}-${fechaIngresoClinica.month.toString().padLeft(2, '0')}-${fechaIngresoClinica.day.toString().padLeft(2, '0')}",
    "logo_clinica": logoClinica,
    "visiblePacientes": visiblePacientes,
    "visibleAgenda": visibleAgenda,
    "visibleForo": visibleForo,
    "visibleSAT": visibleSAT,
  };
}

如果不想用可空类型,也可以在fromJson里给emailClinica设置默认空字符串:
emailClinica: json["email_clinica"] ?? "",

2. 修复Future重复重建问题

不要在FutureBuilder的参数内直接调用请求方法,要在StatefulWidget的initState阶段提前初始化请求Future,存入状态变量,保证build过程中不会重复触发请求:

class _ClinicaPageState extends State<ClinicaPage> {
  // 提前存储请求Future
  late Future<List<Clinica>> _clinicaListFuture;
  late String miId;

  @override
  void initState() {
    super.initState();
    // 初始化阶段只执行一次请求
    miId = "实际业务中的用户ID";
    _clinicaListFuture = fetchClinicas(miId);
  }

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        // 给FutureBuilder指定明确泛型,避免不必要的类型强转
        child: FutureBuilder<List<Clinica>>(
          future: _clinicaListFuture,
          builder: (context, snapshot) {
            // 先判断异常状态
            if (snapshot.hasError) {
              print("列表加载错误: ${snapshot.error}, 栈信息: ${snapshot.stackTrace}");
              return const Center(child: Text("加载失败,请稍后重试"));
            }
            // 判断加载状态
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: CircularProgressIndicator());
            }
            // 数据正常返回
            if (snapshot.hasData) {
              final List<Clinica> filteredList = snapshot.data ?? [];
              // 真·空数据判断
              if (filteredList.isEmpty) {
                return Image.asset(
                  "assets/images/vacio.png",
                  fit: BoxFit.contain,
                );
              }
              return ListView.builder(
                itemCount: filteredList.length,
                shrinkWrap: true,
                itemBuilder: (BuildContext context, index) {
                  final Clinica clinica = filteredList[index];
                  return GestureDetector(
                    // 补全原有列表项点击逻辑
                  );
                },
              );
            }
            // 其余异常场景兜底
            return Image.asset(
              "assets/images/vacio.png",
              fit: BoxFit.contain,
            );
          },
        ),
      ),
    );
  }
}

3. 注意事项

后续使用FutureBuilder一定要按「判断异常 -> 判断加载状态 -> 判断数据是否为空 -> 渲染列表/空状态」的顺序写判断逻辑,不要直接上来就判断hasData,否则出现解析错误、网络错误时完全没法定位问题。


内容的提问来源于stack exchange,提问作者mvasco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:03:20