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

Flutter使用Firestore新增数据后出现数据冗余问题求助

Firestore数据重复问题排查与解决

问题现象

使用Firestore获取数据时,新增一条数据后,新数据无法显示,旧数据反而重复出现;重启APP后数据恢复正常。当前使用最新版Firestore包,采用GetX依赖注入,通过GetxService保持实例持久化。

相关代码

数据获取逻辑

Future<QuerySnapshot<Map<String, dynamic>>> getPubs() async {
  queryPubs = await pubsDb.get();
  if (queryPubs!.docs.isEmpty) {
    log("[getPubs] Variable queryPubs is empty");
  } else {
    await pubData(queryPubs!.docs).then((value) {
      if (value.isNotEmpty) {
        isPub.value = true;
        log("[getPubs] Variable pubAll is not empty");
      } else {
        log("[getPubs] Variable pubAll is empty");
      }
    });
  }
  return queryPubs!;
}

Future<List<Map<String, dynamic>>> pubData(List<DocumentSnapshot>? docs) async {
  for (int i = 0; i < docs!.length; i++) {
    DbPublisher dbPublisher = DbPublisher.fstoreInfo(docs[i]);
    final imgUrl = await getImgUrl(dbPublisher.imgName, DataType.PUBLISHER);
    pubInfo = {
      "name": dbPublisher.name,
      "imgURL": imgUrl,
      "imgName": dbPublisher.imgName,
      "desc": dbPublisher.desc,
      "email": dbPublisher.email,
      "active": dbPublisher.active,
      "status": dbPublisher.status,
      "albums": dbPublisher.albums,
      "songs": dbPublisher.songs,
      "state": dbPublisher.state,
      "privilege": dbPublisher.privilege,
      "payout": dbPublisher.payout,
      "compensation": dbPublisher.compensation,
      "started": dbPublisher.started,
      "due": dbPublisher.due,
      "license": dbPublisher.license,
      "duration": dbPublisher.duration,
      "valid": dbPublisher.valid,
    };
    pubAll!.add(pubInfo);
  }
  return pubAll!;
}

UI渲染代码

Expanded(
  child: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
    future: dNd.getPubs(),
    builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
      if (snapshot.connectionState == ConnectionState.done) {
       if (snapshot.hasData) {
        listAlbs = snapshot.data!.docs;
        if (listAlbs.isNotEmpty) {
          totalAlbs = listAlbs.length;
          log("Load $totalAlbs albums from Firestore");
          return ListView.builder(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            physics: const ScrollPhysics(),
            controller: scrollCtrl,
            itemCount: listAlbs.length,
            itemBuilder: (context, index) {
              return albsFstore(context, dNd.pubAll![index], index);
            },
          );
        } else {
          return const Center(child: FavText("The album data is empty", 15.0));
        }
      } else if (snapshot.hasError) {
        return const Center(child: FavText("The server is error", 15.0));
      } else {
        return const Center(child: FavText("The data is empty", 15.0));
      }
    } else if (snapshot.connectionState == ConnectionState.waiting) {
     return AlignPositioned(
      dx: 0,
      dy: -(MediaQuery.of(context).size.height / 4),
      child: Container(
        width: 128,
        height: 128,
        padding: const EdgeInsets.all(8.0),
        decoration: const BoxDecoration(borderRadius:     
        BorderRadius.all(Radius.circular(16.0)), color: Color.fromARGB(88, 44, 44, 44)),
        child: const LoadingIndicator(indicatorType: Indicator.ballRotateChase, colors: rainbowColors),),);
    }
    return const SizedBox();
    },
  ),
),

问题原因

  1. pubAll列表未清空重复添加
    由于GetxService保持实例持久化,pubAll是全局复用的列表。每次调用getPubs()时,都会从Firestore拉取全量数据,然后在pubData中循环将数据追加到pubAll中,导致旧数据被重复添加。重启APP后pubAll重新初始化,数据恢复正常。

  2. FutureBuilder重复触发请求
    直接将dNd.getPubs()作为FutureBuilder的future参数,每次UI重建时都会创建新的Future实例,导致getPubs()被多次调用,进一步加剧pubAll的数据重复问题。

解决方案

1. 填充数据前清空pubAll

在pubData方法开头添加清空逻辑,避免旧数据残留:

Future<List<Map<String, dynamic>>> pubData(List<DocumentSnapshot>? docs) async {
  pubAll!.clear(); // 清空旧数据,避免重复添加
  for (int i = 0; i < docs!.length; i++) {
    // 原有代码逻辑不变
  }
  return pubAll!;
}

2. 优化FutureBuilder使用,避免重复请求

不要直接在FutureBuilder中调用getPubs(),而是在initState中初始化Future并复用:

late Future<QuerySnapshot<Map<String, dynamic>>> _pubsFuture;

@override
void initState() {
  super.initState();
  _pubsFuture = dNd.getPubs(); // 仅初始化一次
}

// UI中使用复用的Future
Expanded(
  child: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
    future: _pubsFuture,
    builder: (context, snapshot) {
      // 原有builder逻辑不变
    },
  ),
)

3. 改用GetX响应式状态管理(推荐)

利用GetX的响应式列表自动触发UI刷新,替代FutureBuilder,同时避免数据重复:

步骤1:在GetxService中定义响应式列表

final RxList<Map<String, dynamic>> pubAll = <Map<String, dynamic>>[].obs;
final RxBool isPub = false.obs;

步骤2:修改pubData方法,替换列表而非追加

Future<void> pubData(List<DocumentSnapshot>? docs) async {
  final tempList = <Map<String, dynamic>>[];
  for (int i = 0; i < docs!.length; i++) {
    DbPublisher dbPublisher = DbPublisher.fstoreInfo(docs[i]);
    final imgUrl = await getImgUrl(dbPublisher.imgName, DataType.PUBLISHER);
    final pubInfo = {
      // 原有字段不变
    };
    tempList.add(pubInfo);
  }
  pubAll.assignAll(tempList); // 替换整个列表,彻底避免重复
  isPub.value = tempList.isNotEmpty;
}

步骤3:UI用Obx监听数据变化

Expanded(
  child: Obx(() {
    if (dNd.isPub.value) {
      if (dNd.pubAll.isNotEmpty) {
        log("Load ${dNd.pubAll.length} albums from Firestore");
        return ListView.builder(
          padding: EdgeInsets.zero,
          shrinkWrap: true,
          physics: const ScrollPhysics(),
          controller: scrollCtrl,
          itemCount: dNd.pubAll.length,
          itemBuilder: (context, index) {
            return albsFstore(context, dNd.pubAll[index], index);
          },
        );
      } else {
        return const Center(child: FavText("The album data is empty", 15.0));
      }
    } else if (dNd.pubAll.isEmpty) {
      return const Center(child: FavText("The data is empty", 15.0));
    } else {
      return AlignPositioned(
        dx: 0,
        dy: -(MediaQuery.of(context).size.height / 4),
        child: Container(
          width: 128,
          height: 128,
          padding: const EdgeInsets.all(8.0),
          decoration: const BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(16.0)),
            color: Color.fromARGB(88, 44, 44, 44)
          ),
          child: const LoadingIndicator(indicatorType: Indicator.ballRotateChase, colors: rainbowColors),
        ),
      );
    }
  }),
)

4. 可选:使用Firestore实时监听

通过snapshots()监听数据变化,自动获取新增数据,无需手动调用getPubs():

// 在GetxService的onInit中监听
@override
void onInit() {
  super.onInit();
  pubsDb.snapshots().listen((snapshot) {
    pubData(snapshot.docs); // 数据变化时自动更新
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:45:37