Flutter问题:无法将FutureBuilder数据传入DropdownButton报错
问题分析与修复
错误原因
- 初始化时机问题:
_DropdownButtonExampleState初始化时,_MyAppState.data2还是空列表,调用.first会直接抛出Bad state: No element错误——因为网络请求是异步的,此时FutureBuilder还没完成数据加载,data2里没有任何元素。 - 数据重复添加:在
ListView.builder的itemBuilder里每次调用都会执行data2.add(data[index].title),导致data2会被重复填充多次(ListView.builder会多次重建item)。 - Dropdown选项未关联服务器数据:当前Dropdown的选项用的是固定的
list常量,根本没用到从服务器获取的data2数据。
修复方案
我们需要调整状态管理逻辑,确保Dropdown能正确获取异步加载的数据,同时避免空列表访问错误:
关键修改点
- 移除静态变量
data2,改为在_MyAppState中维护服务器返回的选项列表,数据加载完成后更新状态。 - 将选项列表通过构造参数传递给
DropdownButtonExample,而非直接访问父组件状态。 - 在Dropdown中处理数据未加载完成的状态(比如显示加载提示)。
- 一次性转换服务器数据为选项列表,避免重复添加。
修复后的完整代码
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; Future<List<Album>> fetchAlbum() async { final response = await http.get( Uri.parse('https://my-json-server.typicode.com/fluttirci/testJson/db')); Map<String, dynamic> userMap = jsonDecode(response.body); if (response.statusCode == 200) { return (userMap['employees'] as List) .map((e) => Album.fromJson(e)) .toList(); } else { throw Exception('Failed to load album'); } } class Album { final int userId; final int id; final String title; Album(this.userId, this.id, this.title); Album.fromJson(Map<String, dynamic> json) : userId = json['userId'], id = json['id'], title = json['title']; Map<String, dynamic> toJson() => { 'userId': userId, 'id': id, 'title': title, }; } void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late Future<List<Album>> _userFuture; List<String> _dropdownOptions = []; String? _selectedValue; @override void initState() { super.initState(); _userFuture = fetchAlbum().then((albums) { // 一次性转换为选项列表 setState(() { _dropdownOptions = albums.map((album) => album.title).toList(); // 默认选中第一个选项(如果列表不为空) if (_dropdownOptions.isNotEmpty) { _selectedValue = _dropdownOptions.first; } }); return albums; }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Fetch Data Example', theme: ThemeData( brightness: Brightness.dark, primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('Fetch Data Example'), ), body: Column(children: <Widget>[ Expanded( child: _dropdownOptions.isEmpty ? const Center(child: CircularProgressIndicator()) : DropdownButtonExample( options: _dropdownOptions, selectedValue: _selectedValue!, onChanged: (value) { setState(() { _selectedValue = value; }); }, ), ), Expanded( child: FutureBuilder<List<Album>>( future: _userFuture, builder: (context, snapshot) { if (snapshot.hasData) { return ListView.builder( itemBuilder: (context, index) { return Column( children: [ Text(snapshot.data![index].title), ], ); }, itemCount: snapshot.data!.length, ); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const Center(child: CircularProgressIndicator()); }, ), ) ]), ), ); } } class DropdownButtonExample extends StatelessWidget { final List<String> options; final String selectedValue; final ValueChanged<String?> onChanged; const DropdownButtonExample({ super.key, required this.options, required this.selectedValue, required this.onChanged, }); @override Widget build(BuildContext context) { return DropdownButton<String>( value: selectedValue, icon: const Icon(Icons.arrow_downward), elevation: 16, style: const TextStyle(color: Colors.deepPurple), underline: Container( height: 2, color: Colors.deepPurpleAccent, ), onChanged: onChanged, items: options.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ); } }
代码说明
- 在
_MyAppState中,我们在fetchAlbum的回调里一次性将服务器返回的Album列表转换为字符串选项列表_dropdownOptions,并设置默认选中值。 - Dropdown组件改为无状态组件,通过构造参数接收选项列表、选中值和回调函数,符合组件封装原则。
- 在数据加载完成前,Dropdown区域显示加载指示器,避免空状态错误。
- 移除了重复添加数据的逻辑,
ListView.builder直接使用快照中的数据,不再操作选项列表。
内容的提问来源于stack exchange,提问作者crag hack
相关产品推荐
相关产品推荐

