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
or List<Object>
. I found a neat hack for differentiating the two from T
:
...
// 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
to Vehicle
when I do not have access to the Vehicle
class. Only an instance of it is assigned to an Entity 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 splitting LiveData
into two classes, the second one being LiveDataList
. 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 the Dao<Vehicle>
being:
class VehicleDao implements Dao<Vehicle> {
...
static LiveData<List<Vehicle>> get() {
return LiveData<List<Vehicle>>(
...
(rawData) => rawData.map((e) => Vehicle.fromMap(e)).toList(),
...
);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With