Is checking both `snapshot.hasData` and `snapshot.data != null` redundant in Flutter's FutureBuilder?

270 views Asked by At

I'm working with Flutter's FutureBuilder and came across a pattern where both snapshot.hasData and snapshot.data != null are checked before rendering a widget:

FutureBuilder(
  future: future,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    if (snapshot.hasData && snapshot.data != null) {
      return widgetToBuild;
    } else if (snapshot.hasError) {
      return Icon(Icons.error_outline);
    } else {
      return CircularProgressIndicator();
    }
  }
)

Upon examining the source code of AsyncSnapshot, I noticed that hasData is defined as true if data is not null:

bool get hasData => data != null;

Given this implementation, is it redundant to check both snapshot.hasData and snapshot.data != null? Wouldn't snapshot.hasData suffice since it inherently checks if data is not null?

I am curious if there's a scenario where both checks might be necessary, or if this is just a common misunderstanding in the usage of FutureBuilder that developers often mistaken.

3

There are 3 answers

0
VonC On BEST ANSWER

The AsyncSnapshot#hasData() comes from commit fbd4bb9.

There was a large debate around that in issue 34545:

What if we stop conflating data == null and initial data with this discussion altogether -- and simply make the small breaking change of making the data getter throw if hasError is true?
I would also get rid of requireData and let callers check null themselves.

That does sound ok to me - with one inconvenience: The conflating of data == null and initial data is kinda baked in to the API in the hasData getter (which just checks if data is null, but null may be valid data...).
I wonder if we should get rid of hasData as well and just tell people if you want to know if data is not null, just check data?

I suspect this is why you see code like:

FutureBuilder(
  future: future,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    if (snapshot.hasData) {
      if (snapshot.data != null) {
        return widgetToBuild; // Data is available and not null
      } else {
        // Handle null data scenario, e.g., show a message
        return Text("No data available");
      }
    } else if (snapshot.hasError) {
      return Icon(Icons.error_outline); // An error occurred
    }
    return CircularProgressIndicator(); // Still waiting for data
  }
)

But in the current state, it is still redundant.

0
il_boga On

Yes, it's redundant, you are right: if you replace the AsyncSnapshot code in the first piece you posted, it would look like this

if (snapshot.data != null && snapshot.data != null) {

Regarding possible null results from a future, it happened once in some code of mine (due to an unexpected circumstance), and the FutureBuilder got stuck on the CircularProgressIndicator because the data was never detected. So, unless something changed, I think that null-returning futures are not to be used with FutureBuilder.

1
Samrat Mukherjee On

The snapshot.hasData is a handy check that does more than just see if there's data. It essentially tells you, "Hey, not only do I have data, but also my future is resolved and everything's cool!" It's a green light indicating that your future has successfully completed and there's some data to show.

Think of snapshot.hasData as your trusty guard, ensuring that everything went as planned, and snapshot.data != null as a meticulous inspector, making sure what you got is actually what you need. Depending on your situation, you might need just the guard, or both the guard and the inspector, to ensure everything is just perfect.

If your future can never return null upon successful completion, then snapshot.hasData is your go-to check. It's like having confidence in the promise that if the future is done, data is guaranteed. On the flip side, if there's a chance of getting a null even when everything goes smoothly, adding snapshot.data != null is like having that extra layer of assurance.