I have a Freezed generated class with two factory constructors, both containing the same field that is a generic. Given a list of this class I would like to be able to filter based on the type that the field is.
Here is my class:
@freezed
sealed class Artifact<T extends Location> with _$Artifact<T> {
const Artifact._();
const factory Artifact.FromServer(
{required String id,
@_LocationConverter() required T location
}) = ServerArtifact<T>;
const factory Artifact.NewArtifact({
@_LocationConverter() required T location,
}) = NewArtifact<T>;
factory Artifact.fromJson(Map<String, Object?> json) =>
ServerArtifact.fromJson(json);
}
My generic type Location is a Freezed union that can either be a point or a polygon.
With this class I can create new Artifacts like:
final newArtifact = Artifact.NewArtifact(
location: Location.FromPoint(
latitude: curLocation.latitude,
longitude: curLocation.longitude,
),
);
The problem with creating artifacts like this, is when I debugged my application it looks like Dart is storing the type of newArtifact as _$NewArtifactImpl<Location>
— even if I’m passing in a Location.fromPoint()
(which returns as PointLocation
class that extends Location). So if I add newArtifact
to a list I am unable to filter using the whereType<Artifact<PointLocation>>()
. To get around this I made my own custom filter as shown below:
List<Artifact<PointLocation>> get pointArtifacts => artifacts
.where((a) => a.location is PointLocation)
.map<Artifact<PointLocation>>((a) => a as Artifact<PointLocation>)
.toList();
However, when this runs I get the error:
type ‘_$NewArtifactImpl’ is not a subtype of type ‘Artifact’ in type cast
I would not only like to filter to get artifacts with a PointLocation, but also be able to be able to narrow the type of Artifact to Artifact
2
Answers
Instead of creating an
Artifact
by using the typical Freezed factory constructor without explicitly setting generics I was able to create my artifact as follows:Which then allowed me to use the
whereType
array filtering.You have a generic Freezed class
Artifact<T>
whereT
extendsLocation
, and you are trying to filter a list ofArtifact
instances by the specific type ofLocation
they hold, e.g.,PointLocation
.Unlike Java, Dart does retain type information for generics at runtime due to its reified generics.
It means your issue with filtering and casting Freezed classes does not come from Dart’s handling of generics, but rather from how Dart’s type system handles downcasting generic types.
When you attempt to (down)cast
Artifact<Location>
toArtifact<PointLocation>
, the Dart’s type system cannot guarantee the type safety of this operation at compile time without explicit checks or conversions.See "Dart / Type system / Generic type assignment":
In the context of your
Artifact<Location>
andArtifact<PointLocation>
scenario, Dart’s type system allows you to assign a subtype to a supertype variable (covariance), mirroring theList<MaineCoon>
toList<Cat>
example from the documentation. That means you can assign a more specific list (List<MaineCoon>
) to a broader list type (List<Cat>
) without explicit casting.However, assigning a supertype to a subtype variable (the reverse direction, analogous to assigning
List<Animal>
toList<Cat>
) is not allowed by Dart’s static analysis because it introduces the possibility of runtime type errors.That is what is meant by "implicit downcast," which Dart disallows for non-dynamic types to make sure type safety. The documentation confirms that to perform such an assignment, an explicit cast is required (
myAnimals as List<Cat>
), though this cast can fail at runtime ifmyAnimals
does not actually containCat
instances.In the case of your Freezed class
Artifact<T>
, when trying to downcast fromArtifact<Location>
toArtifact<PointLocation>
, Dart’s type system treats this similarly to theList<Animal>
toList<Cat>
casting scenario. Even though Dart’s generics are reified and retain type information at runtime, allowing for runtime type checks (e.g., usingis
checks), the static type system prevents implicit downcasting to make sure type safety.That means that while you can check at runtime if an
Artifact
‘slocation
is aPointLocation
usingis
, statically assigningArtifact<Location>
toArtifact<PointLocation>
without an explicit cast is disallowed because it could lead to type errors at runtime if the actuallocation
is not aPointLocation
.You might need to focus on safely handling the instances based on their actual runtime types, as Dart allows for runtime type checking of generic types. For filtering and transforming a list of
Artifact<Location>
toArtifact<PointLocation>
, consider using runtime checks for both the artifact and its location, while avoiding unsafe downcasting: