Flutter中FutureBuilder搭配DropdownButton触发RangeError报错
报错原因
- 核心触发点是你在
build方法中直接访问空列表的[0]索引:uniquelist的生成逻辑依赖状态变量seen,第一次执行去重后所有品牌已经被加入seen集合,后续setState触发页面重建时,seen.add(brand)会全部返回false,最终uniquelist变成空列表,访问索引0直接抛出RangeError。 - 选中值
defaultValue是build方法内的局部变量,setState时变量会被重新赋值,既无法持久化选中状态,也会因为重建时的执行顺序问题拿到空列表的索引。 - 数据加载、去重逻辑全部写在
build流程里,且FutureBuilder的future参数没有缓存,每次页面重建都会重复触发json加载、重复往brands列表里追加数据,进一步放大异常。 - 访问列表索引前没有做非空判断,空列表场景直接触发索引越界。
修复步骤
- 将选中值、品牌列表全部提升为
State的成员变量,不要在build方法里声明存页面状态的临时变量 - 把json解析、数据去重逻辑抽离为单独的异步方法,在
initState阶段就初始化加载任务并缓存Future,避免build重复触发资源加载 - 去重逻辑在数据加载阶段一次性完成,不要放到每次build执行的流程里
- 访问列表首个元素设置默认值前,先判断列表是否有数据,避免空列表索引越界
- 选中值变化时通过
setState更新状态类里的选中变量,驱动下拉框正确刷新显示
修复后完整代码
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Features extends StatefulWidget { const Features({Key? key}) : super(key: key); @override State<Features> createState() => _FeaturesState(); } class _FeaturesState extends State<Features> { List<String> brands = []; String? selectedBrand; late Future<void> dataLoadFuture; @override void initState() { super.initState(); dataLoadFuture = loadBrandData(); } Future<void> loadBrandData() async { final jsonString = await rootBundle.loadString('assets/phone-model.json'); final jsonData = json.decode(jsonString) as Map; // 加载时直接完成去重 final uniqueSet = <String>{}; for (final key in jsonData.keys) { uniqueSet.add(key.toString()); } brands = uniqueSet.toList(); // 列表非空时初始化默认选中值 if (brands.isNotEmpty) { selectedBrand = brands.first; } } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: FutureBuilder( future: dataLoadFuture, builder: (BuildContext context, snapshot) { // 数据加载完成再渲染下拉组件 if (snapshot.connectionState == ConnectionState.done) { if (brands.isEmpty) { return const Text('暂无可用品牌数据'); } return DropdownButton<String>( value: selectedBrand, icon: const Icon(Icons.arrow_downward), elevation: 16, style: const TextStyle(color: Colors.deepPurple), underline: Container( height: 2, color: Colors.deepPurpleAccent, ), items: brands.map((brand) { return DropdownMenuItem( value: brand, child: Text(brand), ); }).toList(), onChanged: (value) { setState(() { selectedBrand = value; }); }, ); } // 加载过程显示进度条 return const CircularProgressIndicator(); }, ), ), ); } }
内容的提问来源于stack exchange,提问作者OraOraOra
相关产品推荐
相关产品推荐

