How to filter items in flutter when getting data from firebase

Viewed 440

I am currently getting data related to Car details from firebase using FirebaseFirestore.instance.collection('cars').get(). I am saving all the Car instance in List<Cars> _cars = []; using addCarsToList method which I am executing in didChangeDependencies{}

Future<void> addCarsToList({BuildContext context, FilterBy filter}) async {
    var lastDocTracker =
        Provider.of<LastDocumentTracker>(context, listen: false);

    try {
      var dcs = filter != null
          ? await carsCollection.orderBy('dueDate').limit(5).where('readyToRepair', isEqualTo: false).get()
          : await carsCollection.orderBy('dueDate').limit(5).get();

//print(dcs.docs.last.data());

      for (int i = 0; i < dcs.docs.length; i++) {
        Cars carValue = singleCar(dcs, i);

        if (_cars.every((item) => item.carId != carValue.carId)) {
          _cars.add(carValue);
          notifyListeners();
        }
      }
      lastDocTracker.changeLastDocument(dcs.docs.last);
    } catch (e) {
      print(e);
    }
  }

here I am using lastDocumentTracer to track the last document to implement pagination. Therefore in to achieve pagination I am using scroll controller and calling the getMore method

  Future<void> getMore(
      BuildContext context, QueryDocumentSnapshot lastSnap) async {
    print('getMore called');
    var lastDocTracker =
        Provider.of<LastDocumentTracker>(context, listen: false);
    var dcs = await carsCollection
        .orderBy('dueDate')
        .startAfterDocument(lastSnap)
        .limit(5)
        .get();

    if (dcs.docs.isNotEmpty)
      for (int i = 0; i < dcs.docs.length; i++) {
        Cars carValue = singleCar(dcs, i);

        if (_cars.every((item) => item.carId != carValue.carId)) {
          _cars.add(carValue);
          notifyListeners();
        }
      }
    if (dcs.docs.isNotEmpty) lastDocTracker.changeLastDocument(dcs.docs.last);
    return;
  }

The problem that I am currently facing is I have three filters (All, Upcoming, Ready).

All means all cars from firebase without any filter.

Upcoming means the cars that are coming in future. (Saved as DateTime.millisecondsSinceEpoch in Firestore )

Ready means cars that are ready to repair (Saved as bool in firestore) Have a look into the screenshot

I am trying to filter using where((element) => isReadyToRepair) through the list of cars and use setState but unfortunately as I am limiting it to 5 items from Firebase and if there is no car with field isReadyToRepair = true then I can't scroll down and use scroll controller to call getMore and therefore it's showing empty and is not loading any data.

I need your help guys. Please help me. Please let me know if I haven't explained it well.

1 Answers

If you want to get them 5 at the time you'll need to make 3 different queries to the database and use the where method, you have the documentation here.

Your queries will look more like this:

var dcs = await carsCollection
    .orderBy('dueDate')
    .where('ready', isEqualTo: true)
    .startAfterDocument(lastSnap)
    .limit(5)
    .get();

-- Update

That was out of topic, sorry aobut it. You need to create two different references in your LastDocumentTracker. I suppose your store your last document tracked in a variable, so what I'd do is creating one for filtered documents (Or one for each filter you apply), and one for all of them. Then, when you get the documents you reach the right variable based on the filter you applied. To improve a bit the code maintance and elegance I'd use addCarsToList for both getting more and getting them the first time:

//New addCarsToList
Future<void> addCarsToList(LastDocumentTracker tracker, {FilterBy filter}) async { //Tracker as required and filter as optional
    
    bool hasFilter = filter != null;

    var lastSnap = tracker.getLastSnap(filtered: hasFilter);

    bool isFirst = lastSnap != null;

    var dcs;
        
    try {
      if(hasFilter){
            dcs = isFirst ? await carsCollection.orderBy('dueDate').limit(5).where('readyToRepair', isEqualTo: false).get() : await carsCollection.orderBy('dueDate').startAfterDocument(lastSnap).limit(5).where('readyToRepair', isEqualTo: false).get()
      } else{

        dcs = isFirst ? await carsCollection.orderBy('dueDate').limit(5).get(); : await carsCollection.orderBy('dueDate').startAfterDocument(lastSnap).limit(5).get();
      }

      for (var doc in dcs.docs) {
        Cars carValue = singleCar(doc); // Make it get a single document, what's the point on passing both a list and an index ?

        if (_cars.every((item) => item.carId != carValue.carId)) { //questionable
          _cars.add(carValue);
          notifyListeners();
        }
      }

      lastDocTracker.changeLastDocument(dcs.docs.last, filtered: hasFilter);
    } catch (e) {
      print(e);
    }
  }

I modified quite some stuff, go trough all of this and let me know if you don't understand something. There quite a few adjustments you should do after this, like:

  • Adding a variable in your tracker for filtered items;
  • Adding "filtered" parameter in changeLastDocument and modify the right variable based on this parameter;
  • Pass the tracker instead of the context to the method (When you pass an Object you pass its reference, not a copy, so if you modify it you'll modify the one that you passed ;) )
  • Modify singleCar method to accept an object instead of a list and an index
Related