0

I have a list of mentors in firebase realtime database. And each of them has 'numFav' which indicates how many users have checked him as favorite.

In my mentor list page, I want to show registered mentors in the order of descending 'numFav'. At the same time, I want to paginate.

So I wrote code for initial fetch and load more.

  Future<List<MentorModel>> initialMentorFuture(int num) {
    final future = _database.orderByChild('numFav').limitToLast(num).once().then((snapshot) {
      List<MentorModel> _mentorList = [];
      Map<String, dynamic>.from(snapshot.value).forEach((key, value) => _mentorList.insert(0, MentorModel.fromRTDB(key, value)));
      return _mentorList;
    });

    return future;
  }

  Future<List<MentorModel>> moreMentorFuture(int num, int startAtValue) {
    final future = _database.orderByChild('numFav').startAt(startAtValue).limitToLast(num).once().then((snapshot) {
      List<MentorModel> _mentorList = [];
      Map<String, dynamic>.from(snapshot.value).forEach((key, value) => _mentorList.insert(0, MentorModel.fromRTDB(key, value)));
      return _mentorList;
    });

    return future;
  }

initialMentorFuture works as intended. moreMentorFuture doesn't. I have two problems.

  1. I'm using descending order, but limitToLast and startAt seems to contradict each other.

  2. There could be many mentors with same 'numFav'. For example if the last mentor in the first page had numFav of 10, I can't just say startAt(10) in moreMentorFuture because there could be other mentors with numFav of 10 that has not been displayed in the first page.

I'm pretty sure I'm not the only one trying to sort a list by number of likes. Anyone know how to solve this issue?

EDITED

One more question: Does pagination really save loading time? When I call .orderByChild('numFav').limitToLast(10), how many data are actually downloaded from server? If I call orderByChild() does it download the whole data set for sorting? If so, what's the point of pagination?

Younghak Jang
  • 449
  • 3
  • 8
  • 22

1 Answers1

1

The Firebase Realtime Database always returns results in ascending order. If you want to display them in descending order, you'll need to reverse the results in your application code.


If you want the second-to-last page of results, you need to use endAt instead of startAt:

_database.orderByChild('numFav').endAt(endAtValue, key: endAtKey).limitToLast(num)

If your endAtValue is not guaranteed to be unique (as the name numFav suggests), you'll want to also keep the key of the node that you want to anchor on to disambiguate between nodes with the same numFav value.


Firebase added startAfter/endBefore methods to the Realtime Database API last year, so you might want to consider using those to exclude the first/last item of the next/previous page from the results.


A query on a named property like numFav requires that you define an index in your rules before it can be executed on the server. Once you define the index in your rules, the query will be executed on the server.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • endAt(endAtValue, endAtKey) produced too many positional arguments error. Solved with endAt(endAtValue, key: endAtKey). But the mentor with 'endAtKey' appeared twice on the list (one from initial load, another from load more) so I had to _mentorList.removeAt(0) to prevent duplication. – Younghak Jang Jan 10 '22 at 14:06
  • One more question: I think combining endAtValue with endAtKey assumes that mentors with same numFav are guaranteed to be sorted in the same order every time query is called. Since we can only orderBy one child at a time, how can I be assured that they are ordered the same way? – Younghak Jang Jan 10 '22 at 14:10
  • 1
    1) Good catch on `key` being a named argument; I updated that in my answer too. -- 2) If you use `endBefore` you won't have to use `removeAt(0)`, but aside from that it'd be the same. --- 3) Firebase orders on the property you specify and then the node key (internally, you can't rely on this for the results afaik), so it's a stable sort order. – Frank van Puffelen Jan 10 '22 at 15:09
  • Do they have ```endBefore``` in Realtime Database? I'm getting ```Error: The method 'endBefore' isn't defined for the class 'Query'.``` https://firebase.flutter.dev/docs/database/usage#querying – Younghak Jang Jan 12 '22 at 13:47
  • Yes, `endBefore` exists on the Realtime Database too these days. See its documentation for FlutterFire here: https://pub.dev/documentation/firebase_database/latest/firebase_database/Query/endBefore.html. If you don't have it on your system, you might be on a version before that was introduced, in which case you'll need to update (or stick to using `endAt` and skip the overlapping item). – Frank van Puffelen Jan 12 '22 at 15:58
  • I've began flutter coding like 4 month ago and already my firebase version is out of date :) I've updated to 9.0.5 and now I'm getting errors on every event.snapshot.value :( – Younghak Jang Jan 14 '22 at 13:57