I have written an async Flutter/Dart function which behaves unexpectedly in my opinion. Following code structure:

static Future<bool> verifySometing() async {
   try {
      await getCloudData().then((snapshot) {
         if (snapshot.exists && snapshot.hasData) {
            bool dataValid = await validateData(snapshot.data);
            if (dataValid) {
               print('Data is correct');
               return true;
            }
         }
      });
   } catch (e) {
      print('Error $e');
      return false;
   }
   print('Something went wrong');
   return false;
}

The expected result would be that the function awaits the cloud data, then awaits validation and returns true if the data is valid. In this case, the console would show the following and the function would return true:

Data is correct

What happens in practice is that the console shows the following output and the function first returns true and then false:

Data is correct
Something went wrong

This goes against anything I thought to know about funtions in Dart because I always assumed that once return is fired, the function is done. Any ideas how this happens?


Solution 1: Nisanth Reddy

The issue is with this line.

await getCloudData().then((snapshot) {

Here, instead of just awaiting, you have also attached a then callback. So in actuality, whatever you are returning is return value of the callback function ie., (snapshot) {}.

The callback function that you need to pass into the then takes that return true and gives it to us as the result of await.

So, if you would've put something like

var bool = await getCloudData().then((snapshot) { ..... });

Then this bool, would've been equal to true. That's it. No return from the your main function.

Change it to this,

var snapshot = await getCloudData();
if (snapshot.exists && snapshot.hasData) {
  bool dataValid = await validateData(snapshot.data);
  if (dataValid) {
    print('Data is correct');
    return true;
  }
}

Hope, I was able to explain clearly.


Solution 2: Abion47

There are a few of faults in your assumptions.

First, you've attached a then to the future returned by getCloudData(). This means that you aren't awaiting getCloudData(), but instead the additional future returned by getCloudData().then(...), and that future will return when the callback function passed to it completes. (And unless the first future throws an error, the callback will be called.)

Second, the callback function operates on its own scope. So this code is not doing what you think it's doing:

bool dataValid = await validateData(snapshot.data);
if (dataValid) {
  print('Data is correct');
  return true;
}

This return will affect the callback function, not the verifySomething function.

Given these, the order of operation is as follows:

  1. The validateSomething function awaits getCloudData().then(...).
  2. getCloudData() gets called.
  3. getCloudData() returns, the callback passed to then is called.
  4. (Assuming the snapshot has data) validateData is called.
  5. (Assuming data is successfully validated) "Data is correct" gets printed and the callback function returns true.
  6. validateSomething is notified that the awaited future is complete, so execution resumes.
  7. "Something went wrong" gets printed and the validateSomething function returns false.

Generally speaking, these kinds of errors are common when mixing async/await and then patterns. Unless you know what you're doing, stick with either one or the other, preferably the async/await pattern. For example, a refactor of your code to eliminate the call to then is as follows:

static Future<bool> verifySometing() async {
   try {
      final snapshot = await getCloudData();
      if (snapshot.exists && snapshot.hasData) {
        bool dataValid = await validateData(snapshot.data);
        if (dataValid) {
          print('Data is correct');
          return true;
        }
      }
   } catch (e) {
      print('Error $e');
      return false;
   }
   print('Something went wrong');
   return false;
}

Now that there isn't a pesky closure to deal with, return will return from validateSomething as expected and you don't need to deal with issues like callbacks and scope.