使用 StreamBuilder 而不是 FeatureBuilder 来避免 Firestore 中的 whereIn 10 限制

Posted

技术标签:

【中文标题】使用 StreamBuilder 而不是 FeatureBuilder 来避免 Firestore 中的 whereIn 10 限制【英文标题】:Using StreamBuilder instead of FeatureBuilder to avoid whereIn 10 Limit in Firestore 【发布时间】:2021-12-05 18:08:13 【问题描述】:

我想获取特定用户喜欢的帖子并在交错网格视图中显示它们。我可以在 FutureBuilder 中做到这一点。但是在 FutureBuilder 中,由于 whereIn 的限制为 10,我无法获取更多。我知道使用StreamBuilder可以实现,但是我很无奈,不知道怎么做。

这是我的 Firebase 收藏。

下面是获取数据的代码:


  final FirebaseFirestore firestore = FirebaseFirestore.instance;
  getData() async 
    SharedPreferences sp = await SharedPreferences.getInstance();
    String _uid = sp.getString('uid');

    final DocumentReference ref = firestore.collection('users').doc(_uid);
    DocumentSnapshot snap = await ref.get();
    List d = snap['loved items'];
    List filteredData = [];
    if (d.isNotEmpty) 
      await firestore
          .collection('contents')
          .where('timestamp', whereIn: d)
          .get()
          .then((QuerySnapshot snap) 
        filteredData = snap.docs;
      );
    

    notifyListeners();
    return filteredData;
  

这是 FutureBuilder 代码:

body: sb.guestUser == true
            ? EmptyPage(
                icon: FontAwesomeIcons.heart,
                title: 'No wallpapers found.\n Sign in to access this feature',
              )
            : FutureBuilder(
                future: context.watch<BookmarkBloc>().getData(),
                builder: (BuildContext context, AsyncSnapshot snapshot) 
                  if (snapshot.hasData) 
                    if (snapshot.data.length == 0)
                      return EmptyPage(
                        icon: FontAwesomeIcons.heart,
                        title: 'No wallpapers found',
                      );
                    return _buildList(snapshot);
                   else if (snapshot.hasError) 
                    return Center(
                      child: Text(snapshot.error),
                    );
                  

                  return Center(
                    child: CupertinoActivityIndicator(),
                  );
                ,
              ),
      ),
    );
  

  Widget _buildList(snapshot) 
    return StaggeredGridView.countBuilder(
      crossAxisCount: 4,
      itemCount: snapshot.data.length,
      itemBuilder: (BuildContext context, int index) 
        List d = snapshot.data;

        return InkWell(
          child: Stack(
            children: <Widget>[
              Hero(
                  tag: 'bookmark$index',
                  child: cachedImage(d[index]['image url'])),
              Positioned(
                bottom: 15,
                left: 12,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      d[index]['category'],
                      style: TextStyle(color: Colors.white, fontSize: 18),
                    )
                  ],
                ),
              ),
              Positioned(
                right: 10,
                top: 20,
                child: Row(
                  children: [
                    Icon(Icons.favorite,
                        color: Colors.white.withOpacity(0.5), size: 25),
                    Text(
                      d[index]['loves'].toString(),
                      style: TextStyle(
                          color: Colors.white.withOpacity(0.7),
                          fontSize: 16,
                          fontWeight: FontWeight.w600),
                    ),
                  ],
                ),
              ),
            ],
          ),
          onTap: () 
            Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => DetailsPage(
                          tag: 'bookmark$index',
                          imageUrl: d[index]['image url'],
                          catagory: d[index]['category'],
                          timestamp: d[index]['timestamp'],
                        )));
          ,
        );
      ,
      staggeredTileBuilder: (int index) =>
          new StaggeredTile.count(2, index.isEven ? 4 : 3),
      mainAxisSpacing: 10,
      crossAxisSpacing: 10,
      padding: EdgeInsets.all(15),
    );
  


我参考了互联网,但我不完全知道如何将 FutureBuilder 转换为返回快照列表的 StreamBuilder。还有其他方法吗?

【问题讨论】:

【参考方案1】:

首先,“whereIn 限制为 10”是 Firestore 硬约束。这意味着无论您使用FutureBuilder 还是StreamBuilder,这个limit-of-10 约束仍然适用。

现在,如果你还想切换到StreamBuilder

StreamBuilder<QuerySnapshot>(
      stream: _yourStream,
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) 

您需要调整它以成为流

await firestore
          .collection('contents')
          .where('timestamp', whereIn: d)
          .get()
          .then((QuerySnapshot snap) 
        filteredData = snap.docs;
      );

需要将.get( )改为.snapshots()并需要使用snapshot.data.docs.map

更多详细信息请参见此处的流示例https://firebase.flutter.dev/docs/firestore/usage

编辑:

关于 10 项硬约束:根据您的具体项目,您可以:

多次查询,然后在您的应用程序中本地合并(同时删除重复项),或者

另一种解决方案是在您的应用程序中进行一些过滤客户端(基本上,从 FireStore 中获取更多内容并在 Flutter 中进行过滤),或者

另一种解决方案是创建由其他字段组合而成的新字段(例如,在您的情况下,它可能是“月”或“weekNumber”,因此您在 1 种情况下有一周而不是 7 天)

【讨论】:

如果您还需要其他东西,请告诉我 感谢您的回答。我试过你的建议。但正如你所说,即使我使用 streamBuilder 它也会抛出同样的错误'in' filters support a maximum of 10 elements in the value 有没有其他方法可以解决这个问题?我想把它们都拿来。 不,这 10 项是硬约束。我现在将在我的回答中添加一些提示。如果您认为它回答了您的问题,请考虑“接受”我的回答【参考方案2】:

Firestore 不允许您使用超过 10 条记录进行数组成员资格查询。如果您检查此documentation,您将看到:

where() 方法接受三个参数:一个要过滤的字段,一个 比较运算符和一个值。众多中的“in”运算符 where() 方法的比较运算符用于组合最多 10 个 具有逻辑 OR 的同一字段上的相等 (==) 子句。

如果您使用超过 10 条记录,Firestore 将不会接受它,您会收到错误消息。 我建议您将“d”数组分成多个数组,最多包含 10 条记录以进行查询,这将允许 Firestore 在其限制范围内操作 whereIn 并且它会起作用。

还有一个类似的 *** 线程建议使用 chunkSizeCollection 将数组拆分为 10 个元素的块。

【讨论】:

以上是关于使用 StreamBuilder 而不是 FeatureBuilder 来避免 Firestore 中的 whereIn 10 限制的主要内容,如果未能解决你的问题,请参考以下文章

使本地图像直接出现在聊天中,而不是等待网络图像(Streambuilder / Firebase)

为啥使用 InheritedWidget 而我们可以使用 Broadcast Streams | StreamBuilder 和静态变量

如何在 StreamBuilder 中更新 Flutter Cards 而无需重置状态?

使用 StreamProvider 和 StreamBuilder 时出错

在java中,Stream.Builder线程是否安全?

如何使用 StreamBuilder 从一页导航到另一页