Using StreamBuilder instead of FeatureBuilder to avoid whereIn 10 Limit in Firestore

314

Solution 1

First, the "whereIn limit of 10" is a Firestore hard constraint. It means that whether you use FutureBuilder or StreamBuilder, that limit-of-10 constraint still applies.

Now, if you still want to switch to StreamBuilder

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

And you will need to adjust this to become a Stream

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

You need to change the .get( ) to .snapshots() and need to use snapshot.data.docs.map

More details in the Stream example here https://firebase.flutter.dev/docs/firestore/usage

EDIT:

Concerning the 10-items hard constraint: Depending on your specific project you can either:

  • query multiple times then merge locally in your application (while removing duplicates), or

  • another solution is to do some filtering client-side in your application (basically, bring more from FireStore and filter in flutter), or

  • another solution is to create new fields that are a combination of other fields (for example in your case, it could be "month" or "weekNumber" so instead of 7 days you have a week in 1 condition)

Solution 2

Firestore does not allow you to make an array membership query with more that 10 records. If you check this documentation you will see that:

The where() method takes three parameters: a field to filter on, a comparison operator, and a value. The ‘in’ operator out of the many comparison operators of where() method is used to combine upto 10 equality (==) clauses on the same field with a logical OR.

If you use more than 10 records, Firestore will not accept it and you will get an error. I suggest that you break the “d” array into multiple arrays with a max of 10 records to make the query, this will allow Firestore to operate the whereIn within its limits and it will work.

Also a similar stackoverflow thread suggests using chunkSizeCollection which splits the array in chunks of 10 elements.

Share:
314
ARUN BALAJI
Author by

ARUN BALAJI

An Ordinary Man With ExtrOrdinary Dreams

Updated on January 01, 2023

Comments

  • ARUN BALAJI
    ARUN BALAJI over 1 year

    I want to fetch the particular users' liked posts and show them in Staggered grid view. I can able to do this in FutureBuilder. But in FutureBuilder because of the whereIn limit of 10, I cannot fetch more. I know it can be implemented using StreamBuilder.But I am helpless and don't know how to do it.

    This is my firebase collection.

    enter image description here

    Below is the code for fetch data:

    
      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;
      }
    

    This is FutureBuilder Code:

    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),
        );
      }
    }
    
    

    I referred internet and I don't exactly know how to convert FutureBuilder into StreamBuilder which returns a list of snapshots. Is there any other way to do it ?

  • Canada2000
    Canada2000 over 2 years
    Let me know if you need something else
  • ARUN BALAJI
    ARUN BALAJI over 2 years
    Thank you for answering. I tried your suggestion. But as you said even if i use streamBuilder it throws same error 'in' filters support a maximum of 10 elements in the value Is there other way to solve this ? I want to fetch them all.
  • Canada2000
    Canada2000 over 2 years
    No, the 10 items is a hard constraint. I will add some hints now about that in my answer. Please consider "accepting" my answer if you think it responded to your question