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

如何无需热重载更新Firebase Firestore驱动的Widget可见性?

问题描述

使用StreamBuilder从Firebase Firestore获取任务数据,展示两个列表:一个是所有未完成任务,另一个是taskStatus为true的已完成任务。当前功能正常,但需要实现已完成任务列表为空时自动隐藏整个Widget区域,且无需热重载就能实时更新可见性。之前尝试用Opacity调整透明度,但必须热重载才生效,需要优化实现方式。

解决方案

核心思路是直接在StreamBuilder的builder回调中根据数据状态返回不同组件:

  • 当已完成任务列表为空时,返回SizedBox.shrink()(完全不占用布局空间,相当于隐藏)
  • 当列表有数据时,返回完整的已完成任务列表组件

这种方式依赖StreamBuilder的数据流监听特性,Firestore数据变化时会自动触发builder重建,无需热重载即可实时更新可见性。

修改后的完整代码
body: Column(
  children: [
    // 未完成任务列表
    Expanded(
      child: StreamBuilder<List<Task>>(
          stream: DatabaseService().getTasks(orderName),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return Loading();
            }
            List<Task>? tasks = snapshot.data;
            if (snapshot.data!.isEmpty) {
              return StreamBuilder(
                stream: DatabaseService().getUniqueIDs(),
                builder: (context, snapshot) =>
                    Column(
                      children: [
                        Container(
                          alignment: Alignment.centerLeft,
                          child: Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 30, vertical: 40),
                            child: Text(
                              "Tasks",
                              style: TextStyle(
                                fontSize: 30,
                                fontWeight: FontWeight.bold,
                                color: Colors.grey[700],
                              ),
                            ),
                          ),
                        ),
                        Padding(
                          padding:
                          const EdgeInsets.symmetric(horizontal: 30),
                          child: Container(
                            alignment: Alignment.center,
                            child: const Text(
                              "Great! You don't have any remaining tasks.",
                              style: TextStyle(
                                fontSize: 20,
                                fontFamily: "Roboto",
                                color: Colors.black,
                              ),
                            ),
                          ),
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        Image(
                          image:
                          const AssetImage('assets/images/tasks.png'),
                          height: MediaQuery
                              .of(context)
                              .size
                              .height * 0.3,
                        ),
                      ],
                    ),
              );
            }
            return Column(
              children: [
                Container(
                  alignment: Alignment.centerLeft,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 30, vertical: 40),
                    child: Text(
                      "Tasks",
                      style: TextStyle(
                        fontSize: 30,
                        fontWeight: FontWeight.bold,
                        color: Colors.grey[700],
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 15),
                    child: ListView.separated(
                      separatorBuilder: (context, index) =>
                      const SizedBox(
                        height: 5,
                      ),
                      itemCount: tasks!.length,
                      itemBuilder: (context, index) {
                        return Dismissible(
                          background: Container(
                            color: Colors.red,
                            child: const Align(
                              alignment: Alignment.centerLeft,
                              child: Padding(
                                padding: EdgeInsets.all(15.0),
                                child: Icon(
                                  Icons.delete,
                                  color: Colors.white,
                                ),
                              ),
                            ),
                          ),
                          secondaryBackground: Container(
                            color: Colors.red,
                            child: const Align(
                              alignment: Alignment.centerRight,
                              child: Padding(
                                padding: EdgeInsets.all(15.0),
                                child: Icon(
                                  Icons.delete,
                                  color: Colors.white,
                                ),
                              ),
                            ),
                          ),
                          key: UniqueKey(),
                          onDismissed: (direction) {
                            DatabaseService()
                                .deleteTask(tasks[index].id);
                            AwesomeNotifications()
                                .cancelSchedule(tasks[index].id);
                          },
                          child: Item(
                            task: tasks[index],
                          ),
                        );
                      },
                    ),
                  ),
                ),
              ],
            );
          }),
    ),
    // 已完成任务列表(优化后)
    StreamBuilder<List<Task>>(
        stream: DatabaseService().getCompletedTasks(orderName),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Loading();
          }
          // 数据为空时返回空组件,不占用布局空间
          if (snapshot.data!.isEmpty) {
            return SizedBox.shrink();
          }
          List<Task>? completedTasks = snapshot.data;
          // 有数据时返回完整的列表区域
          return Expanded(
            child: Column(
              children: [
                Container(
                  alignment: Alignment.centerLeft,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 30, vertical: 20),
                    child: Text(
                      "Completed Tasks",
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.grey[700],
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 15),
                    child: ListView.separated(
                      separatorBuilder: (context, index) =>
                      const SizedBox(
                        height: 5,
                      ),
                      itemCount: completedTasks!.length,
                      itemBuilder: (context, index) {
                        return Dismissible(
                          background: Container(
                            color: Colors.red,
                            child: const Align(
                              alignment: Alignment.centerLeft,
                              child: Padding(
                                padding: EdgeInsets.all(15.0),
                                child: Icon(
                                  Icons.delete,
                                  color: Colors.white,
                                ),
                              ),
                            ),
                          ),
                          secondaryBackground: Container(
                            color: Colors.red,
                            child: const Align(
                              alignment: Alignment.centerRight,
                              child: Padding(
                                padding: EdgeInsets.all(15.0),
                                child: Icon(
                                  Icons.delete,
                                  color: Colors.white,
                                ),
                              ),
                            ),
                          ),
                          key: UniqueKey(),
                          onDismissed: (direction) {
                            DatabaseService()
                                .deleteTask(completedTasks[index].id);
                            AwesomeNotifications().cancelSchedule(
                                completedTasks[index].id);
                          },
                          child: Item(
                            task: completedTasks[index],
                          ),
                        );
                      },
                    ),
                  ),
                ),
              ],
            ),
          );
        }),
  ],
),
关键改动说明
  1. 调整了已完成任务列表的结构:将Expanded从外部移到StreamBuilder内部,仅在有数据时返回带Expanded的组件,空数据时返回SizedBox.shrink()完全不占空间
  2. 移除了Opacity组件,改为直接返回不同Widget,避免了透明但仍占用布局的问题
  3. 利用StreamBuilder的自动监听特性,Firestore数据更新时会立即重建组件,实现实时的显示/隐藏切换,无需热重载

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 15:00:54