Flutter MethodChannel, Dart Generic and Type-Casting

Cuong Nguyen (Ralph)
ITNEXT
Published in
3 min readSep 12, 2020

--

A beginner gotcha, or how type ‘_InternalLinkedHashMap<dynamic, dynamic>’ is not a subtype of type ‘Map<String, dynamic>’ leads you here.

As someone who often learns more by doing stuff (wrongly 👻) than go through the the official Dart Language Tour docs, I’ve recently stumbled across an error while trying to perform a platform-specific call using Flutter MethodChannel

The code was trying to invoke a platform-specific method that would pass a Map-like data structure (HashMap on Android, NSDictionary on iOS) back to the Flutter world. Each entry in the returned data can be accessed via a String key.

Upon searching around, to understand what the actual error was and what exactly happened, I’ve found this important piece of note

Dart generic types are reified, which means that they carry their type information around at runtime.

Note: In contrast, generics in Java use erasure, which means that generic type parameters are removed at runtime. In Java, you can test whether an object is a List, but you can’t test whether it’s a List<String>.

Suddenly, everything starts to make sense 💡, i.e. how the Platform channel data types support and codecs mentioned that Dart’s List andMap are supported but it does not mention the generic type info.
It is understandable, because when going from one platform (Android) to another (Dart/Flutter), or to be more specific, from erasure to reified the <Key, Value> type information of the Map would be discarded.

The fix for the above error is actually very simple, just remove the specific type from the generic:

Type-Casting in Dart

In Dart, type information is available at runtime, meaning we can do the following:

Notice how Map<dynamic, dynamic> is NOT Map<String, dynamic>.
Attempting to downcasting it would result in the same error that we observed when doing MethodChannel.invokeMethod

Back to the MethodChannel.invokeMethod, now that we know that we have to treat the data as Map<dynamic, dynamic>, to access the value and since we have already known its type via prior API contract, we can cast it using as :

final intValue = dataFromPlatform[‘specificKey’] as int;

However, what if your data is holding a nested Map<String, String>? Again, you will have to treat it as a Map<dynamic, dynamic> 🙃, and it would be tiresome to continue using the as operator to access the nested values.
Luckily for us, Map in Dart come with the cast method so that you can do this:

final nestedMapValue = (dataFromPlatform['anotherSpecificKey'] as Map).cast<String, String>();final stringValue = nestedMapValue['key'];
print(stringValue.length); // stringValue is known as String at runtime at this point, so we can access its `length` property 🙌

Final thought

With a heavy background in TypeScript and Java, it looks like I have been too familiar with erasure generics and take the typecasting as operator for granted in Dart!
Writing this post also reminded me of the time when I use C++ dynamic_cast, hoping that this would help someone come across this issue in the future 👍.

References

--

--