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

Flutter问题:无法将FutureBuilder数据传入DropdownButton报错

问题分析与修复

错误原因

  1. 初始化时机问题:_DropdownButtonExampleState初始化时,_MyAppState.data2还是空列表,调用.first会直接抛出Bad state: No element错误——因为网络请求是异步的,此时FutureBuilder还没完成数据加载,data2里没有任何元素。
  2. 数据重复添加:在ListView.builder的itemBuilder里每次调用都会执行data2.add(data[index].title),导致data2会被重复填充多次(ListView.builder会多次重建item)。
  3. 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(),
    );
  }
}

代码说明

  1. 在_MyAppState中,我们在fetchAlbum的回调里一次性将服务器返回的Album列表转换为字符串选项列表_dropdownOptions,并设置默认选中值。
  2. Dropdown组件改为无状态组件,通过构造参数接收选项列表、选中值和回调函数,符合组件封装原则。
  3. 在数据加载完成前,Dropdown区域显示加载指示器,避免空状态错误。
  4. 移除了重复添加数据的逻辑,ListView.builder直接使用快照中的数据,不再操作选项列表。

内容的提问来源于stack exchange,提问作者crag hack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:25:36