Dart - Casting List<SuperType> to List<SubType> using generics
In Dart (and indeed in many languages) generics screws with the concept of inheritance. You would think that if Bar
inherits from Foo
, that List<Bar>
would also be castable to List<Foo>
.
This is not actually going to be the case because of how generics work. When you have a generic class, every time you use that class with a different type, that type is treated as a completely separate class. This is because when the compiler compiles those types, class MyGenericType<Foo> extends BaseClass
and class MyGenericType<Bar> extends BaseClass
are basically converted to something like class MyGenericType_Foo extends BaseClass
and class MyGenericType_Bar extends BaseClass
.
Do you see the problem? MyGenericType_Foo
and MyGenericType_Bar
are not descendants of one another. They are siblings of each other, both extending from BaseClass
. This is why when you try to convert a List<Entity>
to List<Vehicle>
, the cast doesn't work because they are sibling types, not a supertype and subtype.
With all this being said, while you cannot directly cast one generic type to another based on the relationship of the generic type parameter, in the case of List
there is a way to convert one List
type to another: the cast
method.
List<Entity> entityList = <Entity>[...];
List<Vehicle> vehicleList = entityList.cast<Vehicle>(); // This cast will work
One thing to note though, if you are casting from a supertype generic to a sub-type generic and not all the elements of the list are that new type, this cast will throw an error.
michael_vendler
Updated on December 18, 2022Comments
-
michael_vendler over 1 year
I am new to Flutter and Dart, coming from native Android.
Android has a very nice database abstraction architecture called the Room Persistence Library. As far as I am aware, no such database abstraction architecture exists for Flutter using the MVVM / MVC design patterns.
My solution was to create a Dart version of it myself. I got it pretty much done after a few headaches, but I cannot seem to get
LiveData
to work properly using generics.I set up my class like this:
class LiveData<T> { ... }
Now when I want to return some data, it can either be an
Object
orList<Object>
. I found a neat hack for differentiating the two fromT
:... // Parse response // This checks if the type is an instance of a single entity or a list. if (entity is T) { cachedData = rawData.isEmpty ? null : entity.fromMap(rawData.first) as T; } else { cachedData = rawData.map((e) => entity.fromMap(e)).toList() as T; } ...
The problem lies in the second block:
cachedData = rawData.map((e) => entity.fromMap(e)).toList() as T;
With the error:
- Unhandled Exception: type 'List<Entity>' is not a subtype of type 'List<Vehicle>' in type cast
The question then becomes: How can I cast
Entity
toVehicle
when I do not have access to theVehicle
class. Only an instance of it is assigned to anEntity entity
variable.Here's a snippet to demonstrate my access to
Vehicle
:final Entity entity; ...assign Vehicle instance to entity... print(entity is Vehicle) // True
I've tried using
.runtimeType
to no avail. I have also thought about splittingLiveData
into two classes, the second one beingLiveDataList
. Although this seems to be the easiest solution to not bug the code- it would bug me (bad pun is intentional) and break the otherwise pretty direct port of Room.
As a temporary solution, I have abstracted out the build logic into a generic function to be passed to the
LiveData
in the constructor.final T Function(List<Map<String, dynamic>> rawData) builder;
And now I call that instead of the previous code to build the
cachedData
.// Parse response cachedData = builder(rawData);
With the constructor for the
LiveData<List<Vehicle>>
called when accessing all vehicles in theDao<Vehicle>
being:class VehicleDao implements Dao<Vehicle> { ... static LiveData<List<Vehicle>> get() { return LiveData<List<Vehicle>>( ... (rawData) => rawData.map((e) => Vehicle.fromMap(e)).toList(), ... ); } }
-
michael_vendler about 4 yearsThank you for the reply! I don't think I was clear enough with my question. Because
LiveData
is generic, it lives inside another package and is designed to be independent of any subtypes. This means I do not have access toVehicle
in theLiveData
class to cast it with. -
michael_vendler about 4 yearsI found a temporary solution by abstracting out the build logic into
final T Function(List<Map<String, dynamic>> rawData) builder
that is passed into the constructor of theLiveData
allowing for any builder that returns T. This is not a solution, but more of a workaround. I will keep testing with this to see how I like it. -
Bobby almost 4 yearsIn my case, I got an error when using reduce on a List that derived (derived, derived) is not a subtype of parent (parent, parent). By using the cast approach on the list that reduce is invoked on, it works.