Firebase Realtime DB query returns both a list and a map when using startAfter

Viewed 32

I have the following piece of code, that is simplified for the purpose of illustration:

 List<Name> _nameList = [];

_getNameList() {
    String dbPath = “names/“;

    var startAfter = _nameList.length - 1;

    final db = FirebaseDatabase.instance
        .ref()
        .child(dbPath)
        .orderByKey()
        .startAfter(startAfter.toString())
        .limitToFirst(4);

    final nameFuture = db.once();

    nameFuture.then((event) {
      List<Name> nameList = [];

      print(event.snapshot.value);

      if (event.snapshot.value != null) {
        for (var item in (event.snapshot.value as List<Object?>)) {
          if (item != null) {
            Name d = Name.fromRTDB(item);
            nameList.add(d);
          }
        }
      }

      if (nameList.isNotEmpty) {
        setState(() {
          _nameList.addAll(nameList);
        });
      }
    });
  }

It initially reads the first 4 Name objects from Firebase Realtime DB. And whenever it is called, I expect it to read the very next 4 Name objects and attach them to the _nameList as stated in the code.

IMPORTANT to note that keys to each Name object in the Realtime DB are as 0, 1, 2, 3, 4, and so on. If I am not totally wrong, this actually should make the life easier when using startAfter.

As you may recognize, I print out the values coming from DB, as print(event.snapshot.value);.

The first read works perfectly fine, where startAfter is set to "-1". Example output from print(event.snapshot.value);:

[{name: "John"}, {name: "Johanna"}, {name: "Maria"}, {name: "Steve"}]

Problem 1 When I run the function again, where the startAfter is now 3, I see the following output:

[null, null, null, null, {name: "Michael"}, {name: "Maradona"}, {name: "Pelle"}, {name: "Messi"}]

I expect the list to have the size of 4 but it includes 8 items. And the first 4 are null. Why do I have 8 items in the returned list, and why are the first 4 null?

Problem 2 When I run the function for the 3rd time, where the startAfter is now 7, I get the following output this time:

{10: {name: "Jordan"}, 8: {name: "James"}, 9: {name: "Rambo"}, 11: {name: "Brian"}}

While the first 2 calls returned lists, the 3rd call returned a map. Why does it all of a sudden return a map in the 3rd call? And I make the call with orderByKey() but the returned keys in the map are not in order; why?

1 Answers

Problem 1: When I run the function again, where the startAfter is now 3, I see the following output:

[null, null, null, null, {name: "Michael"}, {name: "Maradona"}, {name: "Pelle"}, {name: "Messi"}]

This is the expected behavior for the Realtime Database API. When it sees a bunch of (sufficiently) sequential numeric keys, it assumed that data was originally an array and returns the data as an array. Since your array doesn't have items at indexes [0-3] those indexes show us as null.

This so-called array coercion depends very much on the data, but typically stops happening once there are too many (I don't recall the exact value for that) missing values at the start of the array, which I think is what may be causing the other problem.

The solution is to not use sequential numeric keys in your Realtime Database data, but either use the push() function to generate keys (recommended) or to prefix the numbers with a short string, e.g. "key_3", "key_4", etc.

Also see: Best Practices: Arrays in Firebase.

Related