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

Flutter Web调整浏览器窗口时Stream持续卡在Waiting状态

问题产生原因

核心问题是Stream实例的创建位置错误,直接写在了build方法中:

  • Flutter Web调整浏览器窗口尺寸时,窗口宽高变化会触发依赖MediaQuery的所有上层节点重建,整棵子树的build方法会被反复执行
  • 每次重建时,ContactDataService.getContactById(id)都会返回一个全新的Stream实例,而非之前已经完成数据加载的旧流
  • StreamBuilder接收到新传入的Stream时,会自动重置连接状态,回到ConnectionState.waiting初始状态。加上Firebase Web SDK新建流监听时存在注册延迟,就会出现一直卡在加载组件、拿不到数据的问题
  • 多数开发者实现Firestore流查询时,每次调用snapshots()都会生成新的流订阅,没有做实例缓存,会进一步放大这个问题
修复方案

按照以下步骤调整即可解决问题:

  • 将Stream实例的创建逻辑从build方法迁移到State生命周期中,仅初始化一次,后续build直接复用已有实例,避免每次重建都生成新流。
    参考实现代码:
    class _ContactDetailPageState extends State<ContactDetailPage> {
      late Stream<Contact?> _contactStream;
    
      @override
      void initState() {
        super.initState();
        // 初始化阶段只创建一次流
        _contactStream = ContactDataService.getContactById(widget.id);
      }
    
      @override
      void didUpdateWidget(covariant ContactDetailPage oldWidget) {
        super.didUpdateWidget(oldWidget);
        // 当传入的联系人id变化时,再更新流实例
        if (oldWidget.id != widget.id) {
          _contactStream = ContactDataService.getContactById(widget.id);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return CustomScaffold(
            body: StreamBuilder<Contact?>(
                // 直接复用提前初始化的流实例,禁止在build中新建流
                stream: _contactStream,
                builder: (context, contactSnapshot) {
                  if (contactSnapshot.connectionState == ConnectionState.waiting) {
                    return LoadingWidget();
                  }
    
                  if (contactSnapshot.hasError || contactSnapshot.data == null) {
                    return Center(
                        child: SizedBox(
                      width: 600,
                      child: ErrorCard(
                          message: 'Error: ${contactSnapshot.error ?? "未查询到对应联系人数据"}'),
                    ));
                  }
    
                  Contact contact = contactSnapshot.data!;
    
                  return Column(
                    children: [
                      Expanded(
                          child: CustomSubScaffoldWrapper(
                        appActions: _contactActions(contact, context),
                        subScaffoldPages: _contactPages(contact, contactScaffoldKey),
                        intialPage: widget.selectedPage,
                        subScaffoldKey: contactScaffoldKey,
                      ))
                    ],
                  );
                }));
      }
    }
    
  • 在数据服务层增加流缓存逻辑,避免相同参数的请求反复创建新的Firestore流订阅,减少重复的监听注册开销。
    参考实现:
    class ContactDataService {
      // 缓存不同id对应的流实例
      static final Map<String, Stream<Contact?>> _contactStreamCache = {};
    
      static Stream<Contact?> getContactById(String id) {
        // 已有缓存直接返回
        if (_contactStreamCache.containsKey(id)) {
          return _contactStreamCache[id]!;
        }
        // 原有Firestore查询逻辑
        final newStream = FirebaseFirestore.instance
            .collection('contacts')
            .doc(id)
            .withConverter<Contact>(
              fromFirestore: (snapshot, _) => Contact.fromJson(snapshot.data()!, snapshot.id),
              toFirestore: (contact, _) => contact.toJson(),
            )
            .snapshots()
            .map((docSnapshot) => docSnapshot.data());
        _contactStreamCache[id] = newStream;
        return newStream;
      }
    }
    
  • 体验优化(非必须):可以给StreamBuilder传入initialData参数,使用本地缓存的联系人数据作为初始值,避免极端场景下的加载闪动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:18:43