Flutter动态调用API触发RangeError(index)错误求助
Edamam API动态传参失败问题解决
问题描述
调用Edamam食品数据库API获取营养数据时,硬编码请求参数可成功获取数据,但动态传入用户输入参数时失败,抛出如下错误:
E/flutter (11813): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: RangeError (index): Invalid value: Valid value range is empty: 0 E/flutter (11813): #0 List.[] (dart:core-patch/growable_array.dart:264:36) E/flutter (11813): #1 new Album.fromJson (package:todoapp/models/apihandler.dart:34:32) E/flutter (11813): #2 fetchAlbum (package:todoapp/models/apihandler.dart:13:18) E/flutter (11813): <asynchronous suspension>
错误原因
- API参数未正确替换:请求URL中直接写了
&ingr=ing,未将变量ing的实际值代入,导致请求的食材是字符串"ing",而非用户输入内容,返回的parsed数组为空,访问索引0时触发RangeError。 - FutureBuilder使用错误:在
onTap的setState内创建FutureBuilder,违反Flutter组件生命周期规范,无法正确更新UI。 - 空值未处理:直接访问
json["parsed"][0],未判断数组是否为空,当API无匹配结果时直接报错。 - 变量未初始化:
inputdata未初始化,可能传入空字符串到API,导致返回无效结果。 - 营养字段映射错误:原代码中膳食纤维和碳水化合物的字段对应关系颠倒,导致数据错误。
解决方案
1. 修复API请求URL
将URL中的ingr=ing替换为ingr=${Uri.encodeComponent(ing)},确保用户输入参数正确传递并编码:
Future<Album> fetchAlbum({String ing = 'chicken'}) async { final response = await http.get( Uri.parse( "https://api.edamam.com/api/food-database/v2/parser?app_id=$api_id&app_key=$api_key&ingr=${Uri.encodeComponent(ing)}&nutrition-type=cooking"), ); if (response.statusCode == 200) { return Album.fromJson(jsonDecode(response.body)); } else { throw Exception('Failed to load album'); } }
2. 正确使用FutureBuilder
在State类中定义Future变量,onTap时更新该变量,build方法中挂载FutureBuilder到组件树:
class _NutrientFactState extends State<NutrientFact> { late String inputdata; late Future<Album>? futureAlbum; late double proteins = 0; late double carbs = 0; late double fibers = 0; late double fats = 0; @override void initState() { super.initState(); inputdata = ''; // 初始化inputdata } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Center( child: Text( 'SelfTime', style: TextStyle(fontWeight: FontWeight.bold), )), ), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: TextField( textAlign: TextAlign.center, onChanged: (value) { inputdata = value; }, )), FactEntry("Protein", proteins.toString()), FactEntry("Carbohydrates", carbs.toString()), FactEntry("Fats", fats.toString()), FactEntry("Fiber", fibers.toString()), // 加载状态/错误提示组件 if (futureAlbum != null) Expanded( child: FutureBuilder<Album>( future: futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { setState(() { proteins = snapshot.data!.protein; carbs = snapshot.data!.carb; fats = snapshot.data!.fat; fibers = snapshot.data!.fiber; }); return const SizedBox.shrink(); } else if (snapshot.hasError) { return Text('错误:${snapshot.error}'); } return const CircularProgressIndicator(); }, ), ), GestureDetector( child: Container( alignment: Alignment.center, margin: const EdgeInsets.only(top: 10), color: kBottomButtonColor, width: double.infinity, height: kBottomButtonHeight, child: const Text( "SEARCH", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 30), ), ), onTap: () { setState(() { futureAlbum = fetchAlbum(ing: inputdata); }); }, ), ], )); } }
3. 添加空值安全检查
在Album.fromJson中判断数组和字段是否存在,避免索引错误:
factory Album.fromJson(Map<String, dynamic> json) { final parsedList = json["parsed"] as List?; if (parsedList == null || parsedList.isEmpty) { throw Exception('未找到匹配的食物数据'); } final foodNutrients = parsedList[0]["food"]["nutrients"] as Map<String, dynamic>?; if (foodNutrients == null) { throw Exception('营养数据缺失'); } // 修正营养字段映射关系 return Album( protein: foodNutrients["PROCNT"]?.toDouble() ?? 0, fat: foodNutrients["FAT"]?.toDouble() ?? 0, carb: foodNutrients["CHOCDF"]?.toDouble() ?? 0, fiber: foodNutrients["FIBTG"]?.toDouble() ?? 0); }
内容的提问来源于stack exchange,提问作者Aditya Bhat
相关产品推荐
相关产品推荐

