Flutter解析TheMealDB API数据时部分分类报Null转String类型错误
问题根因
报错直接原因是类型转换不匹配:接口返回的Null值被赋值给了声明为非空String类型的变量,本质是两个开发错误导致的:
- 接口用错:当前调用的
search.php?s=关键词是按餐品名称模糊搜索的接口,不是按分类拉取餐品的接口。传分类名作为搜索词时,返回结果不稳定:Vegan分类能正常展示只是巧合——刚好返回的所有Vegan相关餐品的字段都不为空;Beef分类下返回的餐品存在字段值为null的情况,直接触发类型错误。 - 模型解析不规范:你贴出的Model代码是分类列表接口的解析逻辑,和页面中调用的餐品列表接口结构完全不匹配;且实际使用的
Meal类未做空安全处理,所有字段都声明为非空String,也没做null值兜底,只要接口返回null就会直接崩溃。另外页面中Image.network直接传入未判空的图片地址,也存在崩溃风险。
修复方案
按以下步骤调整即可解决问题:
- 替换为正确的分类餐品接口
按分类筛选餐品的官方接口为https://www.themealdb.com/api/json/v1/1/filter.php?c=分类名,该接口会稳定返回指定分类下的所有餐品,不会返回多余的空字段。 - 重写匹配接口的Model类,加入空值兜底
不要混用不同接口的解析模型,针对分类餐品接口写对应解析逻辑,所有字段加入null兜底,避免类型错误:import 'dart:convert'; MealsResponse mealsJsonFromJson(String str) => MealsResponse.fromJson(json.decode(str)); class MealsResponse { MealsResponse({required this.meals}); List<Meal> meals; factory MealsResponse.fromJson(Map<String, dynamic> json) => MealsResponse( // 无匹配结果时接口meals字段会返回null,这里直接兜底为空数组 meals: json["meals"] == null ? [] : List<Meal>.from(json["meals"].map((x) => Meal.fromJson(x))), ); } class Meal { Meal({ required this.strMeal, required this.strMealThumb, required this.idMeal, }); String strMeal; String strMealThumb; String idMeal; factory Meal.fromJson(Map<String, dynamic> json) => Meal( // 每个字段加空值兜底,null时赋值为空字符串,避免类型不匹配 strMeal: json["strMeal"] ?? "", strMealThumb: json["strMealThumb"] ?? "", idMeal: json["idMeal"] ?? "", ); } - 调整页面逻辑,增加加载态和错误兜底
class _MealScreenState extends State<MealScreen> { List<Meal> mealsIn = []; bool isLoading = true; Future<void> getMealInside() async { Uri uri = Uri.parse( 'https://www.themealdb.com/api/json/v1/1/filter.php?c=${widget.names}'); final response = await http.get(uri); if (response.statusCode == 200) { final result = mealsJsonFromJson(response.body); mealsIn = result.meals; } isLoading = false; setState(() {}); } @override void initState() { super.initState(); getMealInside(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.names), ), body: isLoading ? const Center(child: CircularProgressIndicator()) : ListView.separated( itemBuilder: (context, index) { final mealsOut = mealsIn[index]; return ListTile( title: Text(mealsOut.strMeal.isEmpty ? "暂无餐品名称" : mealsOut.strMeal), leading: mealsOut.strMealThumb.isEmpty ? const Icon(Icons.fastfood, size: 50) : Image.network( mealsOut.strMealThumb, width: 50, height: 50, fit: BoxFit.cover, // 图片加载失败兜底 errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, size:50), ), ); }, separatorBuilder: (context, index) => const Divider(), itemCount: mealsIn.length, ), ); } }
内容的提问来源于stack exchange,提问作者aalicans
相关产品推荐
相关产品推荐

