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