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

Flutter获取Firebase Storage文件夹名列表重复项问题如何解决

问题根因

出现方法重复调用、ListView条目重复的核心原因有三点:

  • initState中已经调用过一次getFolders(),同时FutureBuilder的future属性直接传入getFolders()方法调用,每次组件触发重建(状态更新、父组件刷新等场景)都会重新执行一次拉取逻辑
  • getFolders()每次执行都会直接向courses列表追加元素,没有提前清空旧数据,多次执行后同一条目会被重复添加
  • 对Future<List>类型存在认知偏差:Future<List>是异步操作的包装对象,本身不是列表实例,因此无法直接调用.add()方法,不需要将存储结果的courses列表声明为Future类型
修复实现

1. 调整状态声明与初始化逻辑

单独定义一个Future类型变量存储异步请求实例,保证请求只会在初始化时执行一次;同时在每次拉取数据前清空列表旧数据,避免残留。

class _SemesterState extends State<Semester> {
  // 存储文件夹名称列表
  List<String> courses = [];
  // 存储异步请求实例,避免build过程重复触发
  late Future<List<String>> folderFuture;

  Future<List<String>> getFolders() async {
    // 拉取前清空旧数据,防止重复追加
    courses.clear();
    final storageRef = FirebaseStorage.instance.ref().child(widget.value);
    final listResult = await storageRef.listAll();
    for (var prefix in listResult.prefixes) {
      courses.add(prefix.name);
    }
    return courses;
  }

  @override
  void initState() {
    super.initState();
    // 初始化时仅执行一次拉取,将结果赋值给预定义的Future变量
    folderFuture = getFolders();
  }

2. 修改FutureBuilder绑定逻辑

FutureBuilder的future属性不要直接调用getFolders(),绑定初始化好的folderFuture即可;列表渲染优先使用snapshot返回的数据,避免全局状态的时序问题。

@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.white,
    appBar: AppBar(
      leading: kIconButton(),
      elevation: 0,
      centerTitle: true,
      title: const Text("Semester", style: kTitleStyle),
      backgroundColor: Colors.white,
    ),
    body: FutureBuilder(
      future: folderFuture,
      builder: (context, snapshot) {
        // 增加异常态处理
        if (snapshot.hasError) {
          return Center(child: Text('加载失败:${snapshot.error}'));
        }
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return const Center(
              child: spinkitLines,
            );
          default:
            return Column(
              children: [
                const SizedBox(height: 20.0),
                const Center(
                  child: Text(
                    'Tap to choose course',
                    style: kPlaceholderStyle,
                  ),
                ),
                const SizedBox(height: 30.0),
                Expanded(
                  child: ListView.builder(
                      itemCount: snapshot.data?.length ?? 0,
                      itemBuilder: (context, index) {
                        final course = snapshot.data![index];
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: GestureDetector(
                            onTap: () {
                              String passedValue = widget.value;
                              String value = "$passedValue/$course";
                              Get.to(() => CourseContent(value: value, courseChosen: course));
                            },
                            child: Container(
                              height: 80,
                              decoration: const BoxDecoration(
                                color: Colors.black,
                                borderRadius: BorderRadius.all(Radius.circular(20)),
                                boxShadow: [
                                  BoxShadow(
                                      color: Color.fromARGB(75, 0, 0, 0),
                                      blurRadius: 4,
                                      spreadRadius: 0,
                                      offset: Offset(0, 4))
                                ],
                              ),
                              child: Center(
                                child: Padding(
                                  padding: const EdgeInsets.all(5.0),
                                  child: Text(
                                    course,
                                    style: kCardTitleStyle,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        );
                      }),
                ),
              ],
            );
        }
      },
    ),
  );
}

扩展说明

如果后续需要实现手动刷新(如下拉刷新),只需要在刷新逻辑中重新调用getFolders(),将返回值赋值给folderFuture后调用setState即可,不需要修改其他渲染逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:45:48