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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:35:22