4

So, I am learning to use the FutureBuilder class in Flutter. My code works, but I wonder if I could improve the way I access the data from the snapshot. My FutureBuilder is like that:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: FutureBuilder<List<ListCard>>(
          future: items,
          builder: (context, snapshot) {
            return RefreshIndicator(
              onRefresh: _pullRefresh,
              child: _listView(snapshot),
            );
          },
        ),
      ),
    );
  }

items is a class property of type Future<List<ListCard>>. _listView is the method where I work on the snapshot. There are 2 errors in this code:

  Widget _listView(AsyncSnapshot<List<ListCard>> snapshot) {
    if (snapshot.data != null) {
      return ListView.builder(
        itemCount: snapshot.data.length, // Error when accessing the length property
        itemBuilder: (context, index) {
          return snapshot.data[index]; // Error when accessing the index
        },
      );
    } else {
      return const Center(child: CircularProgressIndicator());
    }
  }

The two errors are actually the same. They say:

The property 'length' can't be unconditionally accessed because the receiver can be 'null'. Try making the access conditional (using '?.') or adding a null check to the target ('!').dart(unchecked_use_of_nullable_value)

I understand that snapshot.data is of type List<ListCard>?, therefore its value can either be null or a list of type List<ListCard>. In case the value is null, Flutter will display the CircularProcessIndicator. If it's not null, then the ListView.builder widget will be returned. If we reach that point, snapshot.data is always a list (it's been tested in the if statement) and it should therefore have a length property.

I also tried:

Widget _listView(AsyncSnapshot<List<ListCard>> snapshot) {
  if (snapshot.data is List<ListCard>) {
  //...
}

but it gives exactly the same error. Same story with:

Widget _listView(AsyncSnapshot<List<ListCard>> snapshot) {
  if (snapshot.hasData) {
  //...
}

However I can get rid of these errors by adding the ! operator:

//...
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
  return snapshot.data![index];
},
//...

I could also do a type assertion such as List<ListCard> data = snapshot.data as List<ListCard>; but I don't want to go that way.

Is it possible to make this work without using the ! operator?

Thanks!

deczaloth
  • 7,094
  • 4
  • 24
  • 59

2 Answers2

3

I presume the problem you are facing is because snapshot can not be promoted to non nullable.

In your case you will have to go using "!" after a null-check.

Remember

Type promotion is only applicable to local variables... Promotion of an instance variable is not sound, because it could be overridden by a getter that runs a computation and returns a different object each time it is invoked. Cf. dart-lang/language#1188 for discussions about a mechanism which is similar to type promotion but based on dynamic checks, with some links to related discussions.

For further information on this topic see this answer.

deczaloth
  • 7,094
  • 4
  • 24
  • 59
2

The way you handle your builder is wrong, try this:

builder: (context, snapshot) {
    switch (snapshot.connectionState) {
        case ConnectionState.waiting:
          return Text('Loading....');
        default:
          if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            List<ListCard> data = snapshot.data ?? [];
            return RefreshIndicator(
              onRefresh: _pullRefresh,
              child: _listView(data),
            );
          }
      }
},

here I set default value for data, if it gets null I will pass empty list.

eamirho3ein
  • 16,619
  • 2
  • 12
  • 23
  • I am not 100% sure, but i believe `data = snapshot.data ?? [];` uses `data = snapshot.data!` under the hood... – deczaloth Jan 15 '23 at 20:30
  • @deczaloth I don't think so, because when you use `!` on null value it will through exception but when you use `?? []` it will not. – eamirho3ein Jan 15 '23 at 21:06
  • you only can use `!` when your 100 % sure that your variable isn't null, but the way OP handle the the different state of `FutureBuilder`, they will get null issue. @deczaloth – eamirho3ein Jan 15 '23 at 21:08
  • You are right, but what i was trying to say is that when `??` finds out `snapshot.data` is not null, it does not simply set `data = snapshot.data`, but has to set instead `data = snapshot.data!`. – deczaloth Jan 15 '23 at 21:09
  • I understand what you said, but what I try to say is that these operators are not the same but both of the lead to same result but with different scenario, in `data = snapshot.data!` you tell the compiler that this data is not null so in the back it use `data = snapshot.data` but in `data = snapshot.data ?? []` you tell that this is the data but if it was null you take the default value, so these aren't same. @deczaloth – eamirho3ein Jan 15 '23 at 21:14
  • As i see it, `data = snapshot.data ?? [];` translates to `if (snapshot.data != null) { data = snapshot.data!; } else { data = []; }` – deczaloth Jan 15 '23 at 21:20