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

如何在Flutter中从Firestore获取并展示去重的商品分类列表

解决Firestore商品分类去重并生成TabBar的问题

原代码核心问题

你当前的代码在itemBuilder里每次创建新的Set,每个列表项单独处理分类,完全起不到去重作用,所以会重复显示同一分类。另外where("category")写法错误,where需要指定匹配条件,直接去掉这部分才能正常获取所有商品数据。


方案一:客户端本地去重(适合小数据量场景)

先从所有商品文档中提取分类,通过Set自动去重后转成列表,再基于这个列表生成TabBar:

StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
  stream: FirebaseFirestore.instance.collection('products').snapshots(),
  builder: (_, snapshot) {
    if (snapshot.hasError) return Text('Error = ${snapshot.error}');

    if (snapshot.hasData) {
      final docs = snapshot.data!.docs;
      // 提取所有分类并去重
      final categories = docs
          .map((doc) => doc.data()['category'] as String)
          .toSet()
          .toList();

      // 用DefaultTabController包裹TabBar和对应内容
      return DefaultTabController(
        length: categories.length,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: true, // 分类过多时允许横向滚动
              tabs: categories.map((category) => Tab(text: category)).toList(),
            ),
          ),
          body: TabBarView(
            children: categories.map((category) {
              // 每个Tab对应显示该分类下的商品列表
              return ListView.builder(
                itemCount: docs.where((doc) => doc.data()['category'] == category).length,
                itemBuilder: (_, i) {
                  final product = docs.where((doc) => doc.data()['category'] == category).elementAt(i);
                  return ListTile(title: Text(product.data()['name']));
                },
              );
            }).toList(),
          ),
        ),
      );
    }

    return Center(child: CircularProgressIndicator());
  },
)

方案二:Firestore端优化(推荐大数据量场景)

如果商品数据量较大,客户端本地去重效率低,可以用以下两种方式优化:

方式1:使用Firestore的distinct查询

部分地区的Firestore支持distinct参数,直接在查询阶段按分类去重:

StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
  stream: FirebaseFirestore.instance
      .collection('products')
      .distinct(['category']) // 按category字段去重
      .snapshots(),
  builder: (_, snapshot) {
    if (snapshot.hasError) return Text('Error = ${snapshot.error}');

    if (snapshot.hasData) {
      final categories = snapshot.data!.docs
          .map((doc) => doc.data()['category'] as String)
          .toList();

      return DefaultTabController(
        length: categories.length,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: true,
              tabs: categories.map((cat) => Tab(text: cat)).toList(),
            ),
          ),
          body: TabBarView(
            children: categories.map((cat) {
              // 单独监听对应分类的商品数据
              return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
                stream: FirebaseFirestore.instance
                    .collection('products')
                    .where('category', isEqualTo: cat)
                    .snapshots(),
                builder: (_, snap) {
                  if (snap.hasError) return Text('Error');
                  if (!snap.hasData) return CircularProgressIndicator();
                  final products = snap.data!.docs;
                  return ListView.builder(
                    itemCount: products.length,
                    itemBuilder: (_, i) => ListTile(title: Text(products[i].data()['name'])),
                  );
                },
              );
            }).toList(),
          ),
        ),
      );
    }

    return Center(child: CircularProgressIndicator());
  },
)

方式2:维护独立的categories集合

在添加商品时,自动同步维护分类集合,避免重复分类:

// 添加商品时的逻辑
Future<void> addProduct(Map<String, dynamic> product) async {
  final db = FirebaseFirestore.instance;
  // 写入商品数据
  await db.collection('products').add(product);
  // 检查分类是否已存在,不存在则写入分类集合
  final category = product['category'];
  final categoryDoc = await db.collection('categories').doc(category).get();
  if (!categoryDoc.exists) {
    await db.collection('categories').doc(category).set({'name': category});
  }
}

之后直接监听categories集合生成TabBar:

StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
  stream: FirebaseFirestore.instance.collection('categories').snapshots(),
  builder: (_, snapshot) {
    if (snapshot.hasError) return Text('Error = ${snapshot.error}');

    if (snapshot.hasData) {
      final categories = snapshot.data!.docs
          .map((doc) => doc.data()['name'] as String)
          .toList();

      return DefaultTabController(
        length: categories.length,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: true,
              tabs: categories.map((cat) => Tab(text: cat)).toList(),
            ),
          ),
          body: TabBarView(
            children: categories.map((cat) {
              return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
                stream: FirebaseFirestore.instance
                    .collection('products')
                    .where('category', isEqualTo: cat)
                    .snapshots(),
                builder: (_, snap) {
                  if (snap.hasError) return Text('Error');
                  if (!snap.hasData) return CircularProgressIndicator();
                  final products = snap.data!.docs;
                  return ListView.builder(
                    itemCount: products.length,
                    itemBuilder: (_, i) => ListTile(title: Text(products[i].data()['name'])),
                  );
                },
              );
            }).toList(),
          ),
        ),
      );
    }

    return Center(child: CircularProgressIndicator());
  },
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 01:05:19