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

如何将Future转为Stream以获取Firebase实时数据更新

实现Firestore数据实时更新(从Future转为Stream)

1. 编写Stream版本的数据获取方法

替换原来的一次性Future方法,改用Firestore的snapshots()监听集合实时变化:

Stream<List<Medicine>> getAllMedRecordStream(String id) {
  return _db.collection("ShareDate").doc(id).collection("medicineReminder")
      .snapshots()
      .map((snapshot) => snapshot.docs.map((doc) => Medicine.fromSnapshot(doc)).toList());
}

2. 替换FutureBuilder为StreamBuilder

将原来的FutureBuilder换成StreamBuilder,监听上述Stream实现UI自动刷新:

StreamBuilder<List<Medicine>>(
  stream: controller.getAllMedRecordStream(currentUser!),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      if (snapshot.hasData) {
        final medicineList = snapshot.data!;
        return ListView.separated(
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          separatorBuilder: (_, __) => const SizedBox(height: 25),
          itemCount: medicineList.length,
          itemBuilder: (_, index) {
            final medicine = medicineList[index];
            return GestureDetector(
              onTap: () {
                Get.to(() => MedicineDetailScreen(medicine: medicine));
              },
              child: Container(
                height: medicine.status! == "Pending"? 300 : 230,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(16),
                  color: tPrimaryColor,
                ),
                child: specialBackground(
                  top1: -140,
                  top2: 20,
                  height: 80,
                  child: Container(
                    padding: EdgeInsets.all(15),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          "Medicine Reminder",
                          style: Theme.of(context)
                              .textTheme
                              .headlineLarge!
                              .apply(color: tDarkModeColor),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                        Text(
                          "Start Time: ${medicine.startTime!}",
                          style: Theme.of(context)
                              .textTheme
                              .headlineMedium!
                              .apply(color: tDarkModeColor),
                        ),
                        Divider(color: tDarkModeColor),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.start,
                          children: [
                            Image(
                              image: NetworkImage(medicine.image!),
                              height: 90,
                              width: 90,
                            ),
                            SizedBox(width: 30),
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Name: ${medicine.medicineName!}",
                                  style: Theme.of(context)
                                      .textTheme
                                      .headlineMedium!
                                      .apply(color: tDarkModeColor),
                                  maxLines: 1,
                                  overflow: TextOverflow.ellipsis,
                                ),
                                Text(
                                  "Type: ${medicine.medicineType!}",
                                  style: Theme.of(context)
                                      .textTheme
                                      .headlineMedium!
                                      .apply(color: tDarkModeColor),
                                  maxLines: 2,
                                  overflow: TextOverflow.ellipsis,
                                ),
                                Text(
                                  "Status: ${medicine.status!}",
                                  style: Theme.of(context)
                                      .textTheme
                                      .headlineMedium!
                                      .apply(
                                        color: medicine.status! == "Took" ? Colors.green : 
                                               medicine.status! == "Pending"? Colors.grey : Colors.red 
                                      ),
                                ),
                              ],
                            ),
                          ],
                        ),
                        medicine.status! == "Pending"?
                        Column(
                          children: [
                            SizedBox(height: 20),
                            Row(
                              children: [
                                Expanded(
                                  child: ElevatedButton(
                                    style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                                    onPressed: () {
                                      controller.markStatusAsTook(medicine.date!);
                                    },
                                    child: Text(
                                      "Took".toUpperCase(),
                                      style: Theme.of(context)
                                          .textTheme
                                          .headlineMedium!
                                          .apply(color: tWhiteColor),
                                    ),
                                  ),
                                ),
                                SizedBox(width: 20),
                                Expanded(
                                  child: ElevatedButton(
                                    style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                                    onPressed: () {
                                      controller.markStatusAsForget(medicine.date!);
                                    },
                                    child: Text(
                                      "Forgot".toUpperCase(),
                                      style: Theme.of(context)
                                          .textTheme
                                          .headlineMedium!
                                          .apply(color: tWhiteColor),
                                    ),
                                  ),
                                )
                              ],
                            ),
                          ],
                        ) : Container()
                      ],
                    ),
                  ),
                ),
              ),
            );
          },
        );
      } else if (snapshot.hasError) {
        return Center(child: Text(snapshot.error.toString()));
      } else {
        return const Center(child: Text('暂无数据'));
      }
    } else {
      return Center(child: CircularProgressIndicator());
    }
  },
)

核心改动说明

  • snapshots()替代get():该方法会持续监听集合数据变化,每次数据增删改都会推送最新快照
  • StreamBuilder状态判断:改用ConnectionState.active,因为Stream会保持活跃状态,而非Future的一次性完成
  • 复用原有Medicine.fromSnapshot:无需修改即可将文档快照转换为实体对象

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:14:52