Brian Oh
Brian Oh

Reputation: 10750

Flutter and Dart and Future. Is this a known feature of Dart Futures and null?

In the code below (Dart 2.4.0 Windows), returning null from a future that indicates a return type of String results in:

I/flutter ( 8735): The following assertion was thrown building Builder:
I/flutter ( 8735): type 'Future<dynamic>' is not a subtype of type 'Future<String>'

If I return "null as String;", VSCode shows a warning "unnecessary cast", however, the code works and I don't get the fatal error. The Code in question is below. I have commented out the line that was causing the problem and now return "null as String". I use a null String all the time in Dart without a problem, so it only appears to be a problem with Futures AFAIK. Is this just a known fact about Dart and Futures and this is the workaround ("return null as String")?

 @override
  void initState() {
    super.initState(); 
    Future<String> future = _initPreferences();
    future.then((sError) {
      if (sError != null) debugPrint("\n*** Error from initPrefs = $sError\n");
      if (sError == null) debugPrint("\n***Preferences were initialized OK\n");
    });
  }

  Future<String> _initPreferences() {
    return SharedPreferences.getInstance().then((prefs) {
      _prefs = prefs;
      //return null;
      return null as String;
    }).catchError((vError) {
      return "Unable to load categories";
    });
  }

Upvotes: 3

Views: 1391

Answers (2)

jamesdlin
jamesdlin

Reputation: 90135

You didn't specify a type when using Future.then, so it has to be inferred. Since you return null, it doesn't know what type it's supposed to be, so your call to Future.then does not return a Future<String>.

You can help the inference engine along by explicitly specifying the type via then<String>(...):

Future<String> _initPreferences() {
  return SharedPreferences.getInstance().then<String>((prefs) {
    _prefs = prefs;
    return null;
  }).catchError((vError) {
    return "Unable to load categories";
  });
}

although even better would be to just use async and await:

Future<String> _initPreferences() async {
  try {
    final prefs = await SharedPreferences.getInstance();
    _prefs = prefs;
    return null;
  } catch (vError) {
    return "Unable to load categories";
  }
}

Here is a simpler reproduction case:

Future<String> foo() {
  final future = Future(() => null);
  print('${future.runtimeType}');
  return future;
}

Future<void> main() async {
  await foo();
}

I would expect future to be a Future<Null>, but it ends up being a Future<dynamic> instead, and I'm not sure why. (Null is special and allows Future<Null> to be assigned to Future<T>.) I suspect it might be a bug since dartanalyzer does not generate any warnings about it. (And contrary to what some comments say, this does not seem to be new to Dart 2.4.0; I tried with Dart 2.3.0, 2.3.2, and 2.4.0.) I've filed an issue about it.

Upvotes: 1

Brian Oh
Brian Oh

Reputation: 10750

Someone did post what appears to be the correct answer, but then it somehow disappeared, so I can't take credit. The whole point of the exercise is that I want to use a Future (not await) The answer was/is: (then)

 Future<String> _initPreferences() {
    return SharedPreferences.getInstance().then<String>((prefs) 

Upvotes: 0

Related Questions