0
votes

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";
      }
    });
  }
1
I had a similar one today where I had to change my List<Map<String, dynamic>> (which used to work) to List<dynamic> even though it definitely contains Maps (the error I got was that _InternalLinkedHashMap wasn't a subtype of Map, if I remember right). Where I eventually use the List, this code works fine for (Map<String, dynamic> x in y). I'm either not quite getting the nuances of strong mode, or there's a bug crept in somewhere. Could you post the code snippet that triggers this for you? - Richard Heap
Could you include part of the stacktrace which has the line where the error is thrown? - Jonah Williams
We can't really help until you post some code that might show where the problem is. If the project is on github & public, a link would be sufficient. But you should check wherever you're using futures - make sure they're all Future<String> or Future<Object> etc - if you have one that's just a Future it might have worked before but won't in strong mode. - rmtmckenzie
@rmtmckenzie I've added some code. Didn't really know where it happens because this function runs before my App Build - Charles Jr
@Charles Well the problem with that is that you have an await for something that isn't a future on the first line. that seems to be a different problem than you were describing initially though. - rmtmckenzie

1 Answers

7
votes

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).