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

Flutter中StreamBuilder始终处于ConnectionState.waiting状态,无法加载Firebase数据问题排查

我仔细看了你的代码,找出了几个导致StreamBuilder一直显示加载状态的关键问题,咱们一个个来解决:

问题分析与修复方案

1. 最核心的问题:从未触发数据加载

你创建了LabController实例,但从来没调用过loadLab()方法,这就导致_streamController根本没有往流里添加任何数据,StreamBuilder自然一直处于等待状态。

你需要在合适的时机(比如页面初始化时)调用加载方法,比如在StatefulWidget的initState里:

@override
void initState() {
  super.initState();
  ctrlLab.loadLab(submenu); // 触发数据加载
}

2. Stream初始化错误

在LabController的构造函数里,你写了:

stream = _streamController.stream..toList();

这里的toList()会把Stream转换成一个Future<List<ItemLab>>,直接破坏了原有的广播流特性,导致你的stream变成了一个只完成一次的Future,而不是能持续发射数据的Stream。

修复方式很简单,直接赋值即可:

LabController() {
  _streamController = StreamController<List<ItemLab>>.broadcast();
  stream = _streamController.stream; // 移除多余的toList()
}

3. Async*生成器与StreamController混用冲突

你的loadLab方法标记为async*(Stream生成器),但同时又调用_streamController.add(conteudo)手动发送数据,这两种方式是冲突的,会导致流的逻辑混乱。

建议统一使用StreamController来管理流,把loadLab改成普通的async方法:

// 修改为普通async方法,不再用async*和yield
Future<void> loadLab(String submenu, [int limit]) async {
  // 原有逻辑保留,把yield替换为_streamController.add(...)
}

4. Firestore Stream处理不当

在处理Firestore的snapshots()流时,你用了.forEach(...),这会导致代码一直阻塞监听该流,而且无法正确控制数据的发射时机。正确的做法是用await for来遍历快照事件:

// 替换原来的forEach写法
await for (var element in databaseReference
    .collection("lab_${pathSlashless}_url")
    .snapshots()) {
  element.docs.forEach((f) {
    // 原有的文档处理逻辑
  });
  // 每次快照更新后,把最新数据发送到流
  conteudo = checkDuplicateFolder(conteudo, submenu);
  _streamController.add(List.from(conteudo)); // 发送副本避免意外修改
}

5. 资源释放错误

当前的closeStream方法调用_streamController.stream.drain(),这只是清空流中的现有数据,但不会关闭StreamController,容易造成内存泄漏。正确的关闭方式是:

void closeStream() {
  _streamController.close(); // 直接关闭控制器
}

记得在页面销毁时调用这个方法,比如在dispose里:

@override
void dispose() {
  ctrlLab.closeStream();
  super.dispose();
}
修复后的完整LabController示例
class ItemLab {
  final String tipo;
  final Reference elemento;
  final String pastaVideo;
  final Map video;
  final String imageUrl;
  ItemLab({
    this.tipo,
    this.elemento,
    this.video,
    this.imageUrl,
    this.pastaVideo
  });
}

class LabController {
  final databaseReference = FirebaseFirestore.instance;
  final _storage = FirebaseStorage.instance;
  final picker = ImagePicker();
  bool hasMore = false;
  Stream<List<ItemLab>> stream;
  StreamController<List<ItemLab>> _streamController;

  LabController() {
    _streamController = StreamController<List<ItemLab>>.broadcast();
    stream = _streamController.stream;
  }

  void closeStream() {
    _streamController.close();
  }

  Future<void> loadLab(String submenu, [int limit]) async {
    var parts = submenu.split('/');
    var pathSlashless = parts[0].trim();
    var subPathSlashless = parts.sublist(1).join('/').trim();
    var snapshot = await _storage.ref().child("/${submenu}");
    var retorno = await snapshot.listAll();
    List<ItemLab> conteudo = [];

    if(subPathSlashless.isEmpty || subPathSlashless == null){
      retorno.prefixes.forEach((element) {
        conteudo.add(
          ItemLab(
            tipo: 'PASTA',
            elemento: element,
          ),
        );
      });
    }

    for(int i = 0; i < retorno.items.length ; i++){
      var url = await retorno.items[i].getDownloadURL();
      conteudo.add(
        ItemLab(
          tipo: 'FILE',
          elemento: retorno.items[i],
          imageUrl: url,
        ),
      );
      if(limit != null){
        if(conteudo.length > limit){
          hasMore = true;
          _streamController.add(List.from(conteudo));
        }else{
          hasMore = false;
          print("less than 9");
        }
      }
    }

    if(subPathSlashless.isNotEmpty){
      print(subPathSlashless);
      List items;
      await for (var element in databaseReference
          .collection("lab_${pathSlashless}_url")
          .snapshots()) {
        element.docs.forEach((f) {
          if(f.data()['videos'] != null){
            items == null ? items = f.data()['videos'] : items.addAll(f.data()['videos']);
          };
          print("ITEMS :::: >>> ${items}");
        });
        conteudo.clear();
        for(int i = 0; i < items.length; i ++){
          conteudo.add(
            ItemLab(
              tipo: 'VIDEO',
              elemento: null,
              video: items[i],
            ),
          );
        }
        _streamController.add(List.from(conteudo));
      }
    }else{
      await for (var element in databaseReference
          .collection("lab_${pathSlashless}_url")
          .snapshots()) {
        conteudo.clear();
        element.docs.forEach((f) {
          if(f.data().isNotEmpty){
            if(f.data().keys.contains("videos")){
              conteudo.add(
                ItemLab(
                  tipo: 'PASTA',
                  pastaVideo: findFolderName(f.reference.path)
                ),
              );
            }else{
              conteudo.add(
                ItemLab(
                  tipo: 'VIDEO',
                  elemento: null,
                  video: f.data(),
                ),
              );
            }
          }
        });
        conteudo = checkDuplicateFolder(conteudo, submenu);
        _streamController.add(List.from(conteudo));
      }
    }

    conteudo = checkDuplicateFolder(conteudo, submenu);
    _streamController.add(List.from(conteudo));
  }

  // 假设你的findFolderName和checkDuplicateFolder方法在这里实现
  String findFolderName(String path) {
    // 你的实现逻辑
    return path.split('/').last;
  }

  List<ItemLab> checkDuplicateFolder(List<ItemLab> conteudo, String submenu) {
    // 你的去重逻辑
    return conteudo;
  }
}
最后补充几个注意点
  • 发送数据到流时用List.from(conteudo)发送副本,避免后续修改原列表导致流中的数据被意外篡改;
  • Firestore的snapshots()会持续监听数据变化,所以当数据库内容更新时,StreamBuilder会自动刷新列表,正好满足你的需求;
  • 一定要在页面销毁时关闭StreamController,防止内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 08:22:33