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
相关产品推荐
相关产品推荐

