Charles Jr
Charles Jr

Reputation: 9139

Flutter Future<dynamic> vs. Future<String> subtype error?

I just updated Flutter and successfully downloaded my original project from git. Now I'm getting a weird Future error. I see mention of it online at github, but with no definitive answer on how to fix. The project doesn't even load. It reads Future statements from my main.dart file and returns this...

[VERBOSE-2:dart_error.cc(16)] Unhandled exception: type 'Future dynamic' is not a subtype of type 'Future String' where
Future is from dart:async
Future is from dart:async
String is from dart:core

*** Not sure where error is. My dart analysis says "Await Only Futures". Here is my futures code which I run before my widgets begin to build...

Future<Null> getData() async{
    FirebaseUser user = await FirebaseAuth.instance.currentUser;
    user = FirebaseAuth.instance.currentUser;
    var userid = user.uid;
    fb.child('users/${userid}').onValue.listen((Event event) {
      if (event.snapshot.value != null) {
        name = event.snapshot.value['displayName'];
        image = event.snapshot.value['image'];
      } else {
        name = "User";
      }
    });
  }

Upvotes: 0

Views: 14241

Answers (1)

Charles Shiller
Charles Shiller

Reputation: 1048

Ok. I think I got it.

Dart 1.0, was a soft-typed language, so something like

main() async {  
  print(await getData()); 
}

Future<Null> getDatare() async{return "a";} 

would print "a"

The thing is that Dart 2.0 is a typed language, so the Future actually has a meaning. And what happens here (in short) is that Future becomes a FutureNull, and you can't get a String from a FutureNull (since it only contains a Null).

Actually, this kind of bit me once[1], but if you think about it, it makes sense. Look at the following code:

List<dynamic> l = ["a", "b", "c", "d"];
List<String> d = l;

or even more so

List<int> l = [1,2,3,4];
List<double> d = l;

Will crash in Flutter. Why? Because think about what happens here:

What is "l"?

----------------------
| int | int | int | int |
----------------------

What is "d"?

---------------------------------------------
| double | double | double | double |
---------------------------------------------

So how do you convert between "l" and "d"?

You'd have to create a new List of doubles, and then copy the value in l, cast it into a double, and then store it in "d".

But this doesn't just apply to lists. It applies to all generics.

When you have something like A<B>, it's a totally different type than A<C>, and you can't cast simply cast from one to the other, and for the same reason:

Take the following code:

class Box<T> {
  T a;
  Box(T v) {
    a=v;
  }
  T getVal() {
    return a;
  }
  }

Now convert Box to Box. Doesn't make sense, right? Can I do the following?

Box<int> i = new Box(5);
Box<String> s= new Box("a");
i + (s as Box<int>)

So what really happens here is that Box becomes BoxInt, and Box becomes BoxString (with the compiler find/replacing the identifier "T" with "int" or "T" with "String").

So you have the same thing here: You have to unbox the value from the future, and then work with that. In your case (actually, your case has an additional problem - you're not returning anything) you probably want a Future and then await that Future and get a String.


[1]. How did it bite me? Coming from Kotlin (or any other JVM language), I got used to be able to do something like:

List<A> a = ...;
List<B> b = a;

without a problem. Why? Because JVM doesn't have real generics. What it does is called "type erasure", where what really happens:

List<Object> a = ...;
List<Object> b = a;

Well, that obviously works, since all Lists hold a pointer to an Object, determined at run time.

That was because Java 1.0 (unlike Dart) never had generics and they were bolted on, so for the sake of backwards compatibility they made generics less type-safe than they could have.

So Dart (or C++ or Rust or Go arrays) treats generics as monomorphic, while Java treats them as polymorphic code (treat "T" as pointer).

Upvotes: 7

Related Questions