Search code examples
flutterdartcasting

Linter tells me cast is unnecessary, but crashes without it


I came across a behaviour I am unable to work around. Basically, I am trying to fetch data, and return a Future of these data, where some of the requests can fail but we can ignore them.

This is my idea so far : for each data we want to get, create a future that returns null if it fails, and the value otherwise. Then wait all the futures, and remove null values.

Here is my code : (simple test data)

Future<List<String>> testFunc() {
  // create a result list
  List<Future<String?>> result = List.empty(growable: true);

  // put 10 future in the results
  for(int i = 0; i < 10; i++) {
    result.add(
      // create a future that may fail
      Future.delayed(Duration(seconds: i), () {
        if(i % 2 == 0) { return i; }
        else { throw Error(); }
      })
      // if the future succeed, return the value
      .then((value) {
        return "got from future : $value";
      })
      // otherwise, returns "null" as we are expecting a future returning a nullable value
      .catchError((error) {
        return null; // <========= Linter error here
      })
    );
  }

  // then, wait for all futures, and remove the ones that crashed
  return Future.wait(result).then((listOfNullable) => listOfNullable.where((element) => element != null).map((e) => e!).toList());
}

When I run this and one of the futures fails and return null, I'm having an error : The error handler of Future.catchError must return a value of the future's type which I don't understand, as null is a valid value for String??

Something that does work is with explicit casts :

Future<List<String>> testFunc() {
// create a result list
List<Future<String?>> result = List.empty(growable: true);

// put 10 future in the results
for(int i = 0; i < 10; i++) {
 result.add(
   // create a future that may fail
   Future.delayed(Duration(seconds: i), () {
     if(i % 2 == 0) { return i; }
     else { throw Error(); }
   })
   // if the future succeed, return the value
   .then((value) {
     return "got from future : $value" as String?; // <========= Linter error here
   })
   // otherwise, returns "null" as we are expecting a future returning a nullable value
   .catchError((error) {
     return null as String?; // <========= Linter error here
   })
 );
}

// then, wait for all futures, and remove the ones that crashed
return Future.wait(result).then((listOfNullable) => listOfNullable.where((element) => element != null).map((e) => e!).toList());
}

But now, the linter tells me I have an unnecessary cast, and I'm trying to remove any linter errors.

What I am missing here ?


Solution

  • The linter errors are caused by the call to then(...), which the dart linter eagerly resolves to then<String> instead of then<String?>.

    You can specify the type explicitly to work around this behavior:

    Future<List<String>> testFunc() {
      List<Future<String?>> result = List.empty(growable: true);
    
      for(int i = 0; i < 10; i++) {
        result.add(
          Future.delayed(Duration(seconds: i), () {
            if(i % 2 == 0) { return i; }
            else { throw Error(); }
          })
          .then<String?>((value) { // <- Change here!
            return "got from future : $value";
          })
          .catchError((error) {
            return null; // No more linter warning
          })
        );
      }
    
      // then, wait for all futures, and remove the ones that crashed
      return Future.wait(result).then((listOfNullable) => listOfNullable.where((element) => element != null).map((e) => e!).toList());
    }