'Obtaining data from Firebase and creating an object in Flutter, Dart

I would like to create an Object using data from my Firebase database. I get the data correctly, but I can't create that Objet in the way im trying. I have no idea why it doesn't work, this is my first time using Dart.

Here is my code

MaterialQR selectFromFirebase(String document) {
    MaterialQR res = MaterialQR(exercises: [], image: '', name: '', id: '');
    FirebaseFirestore.instance.collection('MaterialQR')
        .doc(document).get()
        .then((value) => res = new MaterialQR(
          exercises: value['exercises'],
          image: value['image'],
          name: value['name'],
          id: value['id']));
    return res;
  }

The objective is return an "empty" Object if the document in my database doesn't exist or return a correct Object if the document exists. When I print(value['id']) inside .then(), I got a correct id, but it doesn't work when I create the new Object.



Solution 1:[1]

This is because data is loaded from Firestore (and pretty much any modern cloud API) asynchronously, because it may take some time before it's available. Instead of blocking your code (and the user) during this time, your main code actually continues to execute. Then when the data is available, your then block executes.

This is easiest to see if you place some logging in the code, run it, and check its output.

print('Before starting to read from the database');
FirebaseFirestore.instance.collection('MaterialQR')
    .doc(document).get()
    .then((value) => {
      print('got data from the database');
    });
print('After starting to read from the database');

The output of this is:

Before starting to read from the database

After starting to read from the database

Got data from the database

This is probably not what you expected, but it explains perfectly why you don't get a result from selectFromFirebase: the return res runs before res is ever set by your then block.


There is no way to prevent this asynchronous nature of the call. Instead you'll have to accept that calling cloud APIs is asynchronous, and return a Future from the method and mark is as async:

Future<MaterialQR> selectFromFirebase(String document) async {

That also means you can use await in there, so the entire function then becomes:

Future<MaterialQR> selectFromFirebase(String document) async {
  try {
    var value = await FirebaseFirestore.instance.collection('MaterialQR')
      .doc(document).get();
    return MaterialQR(
      exercises: value['exercises'],
      image: value['image'],
      name: value['name'],
      id: value['id']);
  }
  catch {
    return  MaterialQR(exercises: [], image: '', name: '', id: '');
  }
}

See also:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Frank van Puffelen