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

Time:10-19

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 ?

CodePudding user response:

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)

CodePudding user response:

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.

  • Related