How to use generics with freezed sealed union objects? - flutter

I have a Flutter class that uses Freezed to create a sealed union that represents either data or an error:
#freezed
class DataOrError<T, E> with _$DataOrError {
const factory DataOrError.loading() = Loading;
const factory DataOrError.data(T data) = DataOrE<T, E>;
const factory DataOrError.error(E error) = DOrError<T, E>;
static DataOrError<T, E> fromEither<T, E>(Either<E, T> val) {
final result = val.fold(
(l) => DataOrError<T, E>.error(l), (r) => DataOrError<T, E>.data(r));
return result;
}
}
I use riverpod so I have a riverpod StateNotifier that looks like:
class RolesNotifier
extends StateNotifier<DataOrError<List<Role>, RoleFailure>> {
final Ref _ref;
StreamSubscription? sub;
RolesNotifier(Ref ref)
: _ref = ref,
super(const DataOrError.loading());
/// Will run the fetch
void fetch() {
// fetch roles
state = const DataOrError.loading();
sub = _ref.read(firebaseRoleService).getRoles().listen((event) {
state = DataOrError.fromEither<List<Role>, RoleFailure>(event);
});
}
// ... this class has been shortened for simplicity.
}
final rolesProvider = StateNotifierProvider.autoDispose<RolesNotifier,
DataOrError<List<Role>, RoleFailure>>((ref) {
return RolesNotifier(ref);
});
When I consume this provider; however, the types for DataOrError are gone:
ref
.read(rolesProvider)
.when(loading: (){}, data: (d) {
// d is dynamic type not List<Role>
}, error: (e){});
For some reason both d and e are dynamic types and not List<Role> & RoleFailure respectively. Everything appears to be typed correctly so why is this not working? I'm not sure if the error is with Freezed or Riverpod. I would like to avoid type casting (i.e. d as List<Role>) because that defeats the purpose of the generics.

The mixin has to be defined with the same generic types Like
class DataOrError<T, E> with _$DataOrError<T,E>
Although if you did not explicitly define the mixin generics ,the runner will mimic the original class types but in that case there will be no relation between them. in our case it will be a different set of T and E that is why it will be dynamic , bottom line is that you have to tell the mixin that it is indeed the same set of types.

Your code seems to be setup correctly, my intuition is that the types are not being inferred because you are using ref.read. This may be intentional in riverpod for some reason.
Finally, using ref.read inside the build method is an anti-pattern and your UI will not change whenever the state notifier updates.
Replace ref.read(rolesProvider) with:
ref.watch(rolesProvider).when(
loading: (){},
data: (d) {}, // Types should be inferred correctly now
error: (e){},
);

Related

[DART]: Telling dart generics that an attribute will exist when used

Not sure if I'm using generics properly but is there a way I can let <T> know that it has (or will have) a certain attribute when it's used? This wouldn't be a problem if it weren't generics but since it's not I keep getting the error The getter 'id' isn't defined for the type 'T & Object'.
class Foo<T> {
List<T> items = [];
removeById(int id) {
items.removeWhere((T element) => element.id! == id); // Error
}
}
You can have either static (compile-time) checks or runtime checks. For compile-time checks, you would need to parameterize your generic on some base interface. For example:
abstract class HasId {
int get id;
}
class MyClass implements HasId {
#override
final int id;
MyClass(this.id);
}
class Foo<T extends HasId> {
...
}
If you really want duck-typing, that inherently requires using dynamic to disable static type-checking and relying on runtime checks:
class Foo<T> {
List<T> items = [];
void removeById(int id) {
items.removeWhere((element) => (element as dynamic).id! == id);
}
}
If there's a possibility that instances of T might not have an id member, you will need to catch a potential NoSuchMethodError yourself.

How to recreate singleton instance if different params are passed to the constructor in dart

I gathered the following understanding for creating a singleton in dart with params
class Foo extends ChangeNotifier {
late String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel')
}
static final Foo _instance = Foo._internal();
Foo._internal() {
instanceMemberFunction();
}
factory Foo({
required String channel
}) {
_instance.channel = channel;
return _instance;
}
}
and I am calling the instance like so
Foo({channel: "bar"})
Now I want to have some working that if I use
Foo({channel: "baz"})
Then a new instance is created and it's okay in that case to destroy the old one. How can I achieve this in dart?
It seems like you've copied some existing example for creating a singleton without fully understanding what it's doing and why. The core parts are:
The single instance is stored in a global or static variable.
The class has one or more public factory constructors that returns that global/static variable, initializing it if necessary.
All other constructors for the class are private to force consumers to go through the factory constructors.
Therefore, if you want your factory constructor to replace its singleton based on its argument, you need to:
Make your factory constructor check if the argument is appropriate for the existing instance. If it is, return the existing instance. If not (or if there is no existing instance), create and return a new instance.
Since you need to check if the existing instance is initialized, make it nullable. (You alternatively could initialize it to a non-null sentinel value, e.g. Foo._internal(channel: '').
Pass the argument along to the private constructor.
class Foo extends ChangeNotifier {
final String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel');
}
static Foo? _instance;
Foo._internal({required this.channel}) {
instanceMemberFunction();
}
factory Foo({required String channel}) {
if (channel != _instance?.channel) {
_instance = Foo._internal(channel: channel);
}
return _instance!;
}
}
Note that this implementation will create a new object if the constructor argument changes, which isn't very singleton-like. Depending on what you want to do, you could:
Return a new object (which could allow multiple simultaneous instances).
Return the existing object.
Return the existing object, but mutate it with the constructor argument.

what does the child class of Equatable pass to the super(Equatable class)?

hi I am new with bloc in flutter and I'm trying to understand block timer In the doc of flutter_bloc and I would know what's this constructor class mean
#immutable
abstract class TimerState extends Equatable {
final int duration;
//and this list props ??
TimerState(this.duration, [List props = const []])
: super([duration]..addAll(props));
}
Update: For the new version of Equitable package >= v 0.6.0 read my article on Medium, for older version or deep understanding read this answer.
When your father gives you and your brother 2 gifts, both gifts are laptops but they are not the same type of laptops; you want to know are both gifts equal or not! So you will compare all aspects that important to you RAM, SSD, CPU.
on a paper: myLaptop: 16G/256G/i5 | myBrotherLaptop: 8G/512G/i5
Assuming your brain is using Dart language, and you thought of each gift as an object of this class:
class LaptopGiftClass {
int ram;
int ssd;
String cpu;
// Default constructor
LaptopGiftClass(this.ram, this.ssd, this.cpu);
}
then to compare Equailty of both gifts that created using the class above, Dart and other Object oriented languages e.g Java, C# are expecting you to create(override) these functions, in order to make these languages understand the objects and able to compare any two objects of same class:
#override
bool operator ==(Object myBrotherLaptop) =>
identical(myLaptop, myBrotherLaptop) ||
myBrotherLaptop is LaptopGiftClass &&
runtimeType == myBrotherLaptop.runtimeType &&
name == myBrotherLaptop.name;
#override
int get hashCode => name.hashCode;
if these lines scares you off, no one blame you, that's why nice people have created equatable package for us!
Equatable package is telling you "leave this scary job for me" But how to delegate the scary code to equatable package??!
By doing two things:
Make your class extends equatable:
dart class LaptopGiftClass extends Equatable {...}
Pass all properties that you need to compare with inside an array to the Equatable(the super/parent class) from moment one, so inside the constructor:
LaptopGiftClass(this.ram, this.ssd, this.cpu) : super([ram, ssd, cpu]);
your final class is:
class LaptopGiftClass extends Equatable {
int ram;
int ssd;
String cpu;
// Default constructor
LaptopGiftClass(this.ram, this.ssd, this.cpu) : super([ram, ssd, cpu]);
}
AND YOU ARE DONE! you can now check equality of the two gifts, just create the objects then compare:
LaptopGiftClass myLaptop = LaptopGiftClass(16,256,'i5');
LaptopGiftClass myBrotherLaptop = LaptopGiftClass(8, 512,'i5');
AND JUST BEFORE START COMPARING, your brother saw you, and because he is a gamer, he wants you to add more properties in this equality check: GPU and Screen_Resolution! your mother heard that and asked you to add price too!
Now you have a list of new props to compare: [GPU, Screen_Resolution, Price].
So because you follow clean code principle, you expected that, and you made the constructor able to get more properties to compare with:
// This only mean combine both lists
[ram, ssd, cpu]..addAll(myBrotherAndMotherProps)
so your final class is:
class LaptopGiftClass extends Equatable {
int ram;
int ssd;
String cpu;
// Default constructor
LaptopGiftClass(
this.ram,
this.ssd,
this.cpu,
// List of list => because we think "clean code"
// and maybe in the future we will send other data; NOT
// only an array(list)..
// so we here sent the extra props we need to
// compare 'myBrotherAndMotherProps', and
// as sometime brother and mother will not ask you
// to add props to compare, you give it a default value
// as empty "const []", why const here??! just for better
// performance as we are so soooo Professional!!
[ List myBrotherAndMotherProps = const [] ],
) : super([ram, ssd, cpu]..addAll(myBrotherAndMotherProps));
// WHY TO PASS FROM INSIDE THE CONSTRUCTOR?
// because Equatable needs them (required)
// and not at anytime but immediately inside the
// constructor of itself, so we made this
// chaining(constructor pass to another constructor)..
}
So it's obvious that the essintial properties are [RAM, SSD, CPU], but anything extra will be take into consideration too as we made the implementation clean, flexible, and scalable.
before adding this flexible code List<Object> get props => [RAM, SSD, CPU]..addAll(myBrotherAndMotherProps); these used to be EQUAL!!:
// Note first 3 are equal [ram, ssd, cpu]:
LaptopGiftClass myLaptop = LaptopGiftClass(16,256,'i5', ['Nvidia', 1080, '1200$']);
LaptopGiftClass myBrotherLaptop = LaptopGiftClass(16, 256,'i5', ['Intel HD', 720, '900$']);
myLaptop == myBrotherLaptop; // True without ..addAll(myBrotherAndMotherProps);
myLaptop == myBrotherLaptop; // False with ..addAll(myBrotherAndMotherProps);
Same happening with TimerState:
#immutable
abstract class TimerState extends Equatable {
final int duration;
TimerState(this.duration, [List props = const []])
: super([duration]..addAll(props));
}
TimerState is implemented just like LaptopGiftClass above(last implementation).
you can send props to it using the constructor:
TimerState(this.duration, [List props = const []])
: super([duration]..addAll(props));
so TimerState will pass props's list to its parent(super/ the Equatable/ what extended..) in this line like this:
: super([duration]..addAll(props));
and in this timer example; duration is the basic prop, just like [RAM, SSD, CPU] to LaptopGiftClass.
and the hierarchy will be like this:
// Inside class Paused extends TimerState {...}
Paused(int duration) : super(duration); // super is TimerState
// then Inside abstract class TimerState extends Equatable {..}
TimerState(this.duration, [List props = const []])
: super([duration]..addAll(props)); // super is Equatable
// Then Equatable will get props and deal with it for you...
This tutorial use equatable: ^0.2.0, in this version when you want to override the hashcode and == operator you need to pass a List of the properties to the super constructor. Check out the docs.
Whit this in mind, he create a optional parameter called props, and pass to the super constructor a List that contains duration and all elements of the props parameter.
abstract class TimerState extends Equatable {
final int duration;
TimerState(this.duration, [List props = const []])
: super([duration]..addAll(props));
}
In this way the class that extends TimerState can use the props optional parameter to pass other properties, and this properties would be added to the List that is passed to the super constructor of TimerState for use Equatable correctly.
So if you need a state that have other properties you need to do this:
class OtherTimerState extends TimerState {
final int otherProperty1;
final int otherProperty2;
OtherTimerState(int duration, this.otherProperty1, this.otherProperty2)
: super(duration, [otherProperty1, otherProperty2]);
}

Passing constructor as argument in Flutter

I have API communication service in my Flutter app with 10+ different services, and 100+ API calls that heed to parse data. In order to reuse code I've decided to create some common parsing code that is going to parse data from API:
ApiResponse handleObjectResponse({
#required http.Response serverResponse,
#required Function objectConstructor,
}) {
if (serverResponse.statusCode == 200) {
dynamic responseObject = objectConstructor(json.decode(serverResponse.body));
return ApiResponse(responseObject: responseObject);
} else {
ApiError error = responseHasError(serverResponse.body);
return ApiResponse(error: error);
}
}
This way I am able to parse JSON object from API in a reusable way no matter what the Object class is, just by passing constructor function to this method.
When I call this method in any of the Services I've created for fetching data like this:
handleObjectResponse(serverResponse: response, objectConstructor: ChartData.fromJson);
I get error: The getter 'fromJson' isn't defined for the class 'ChartData'.
Try importing the library that defines 'fromJson', correcting the name to the name of an existing getter, or defining a getter or field named 'fromJson'.
Where I think the problem is is in this model class and factory statement, but I don't know how to fix it:
class ChartData {
List<ChartDataPoint> points;
ChartData({
this.points,
});
factory ChartData.fromJson(Map<String, dynamic> json) {
List jsonPoints = json["data"];
return ChartData(
points: List.generate(jsonPoints.length,
(i) => ChartDataPoint.fromJsonArray(jsonPoints[i])));
}
}
You cannot pass constructors as functions. You need to create a function what will call the constructor instead:
(int a) => Foo(a);
Just a 2022 update: since 2.15 it's possible by Class.new, see the complete issue: https://github.com/dart-lang/language/issues/216.
class A {
final String a;
const A(this.a);
#override
String toString() => 'A($a)';
}
class B {
final String b;
const B(this.b);
#override
String toString() => 'B($b)';
}
void main() {
final List<Object Function(String)> constructors = [A.new, B.new];
for (final Object Function(String) constructor in constructors) {
final Object instance = constructor('My Constructor Parameter');
if (instance is A) {
print(instance.toString());
}
}
}
Note that if you're using named params, both class constructors must have the same param name, otherwise the constructor signatures won't match and then it will generate this static error:
The element type X can't be assigned to the list type Y.

Extending base List class with extra functionality in Dart language

This question is about Dart language.
I want to have a class which is just a List but with some extra functionality.
For example I have a class named Model:
class Model{
String name;
int type;
Model(this.name, this.type);
}
I know that Model's type could take only four values: from 0 to 3.
And I want to have a method, which can give me a List of Models of specified type, e.g. List<Model> modelCollection.getByType(int type);.
I plan to to have four 'hidden' Lists of the Models (grouped by type) in that class.
Thus I need to override addition and removal of List elements to make that hidden lists being up to date.
How can I realize this as easy as possible?
P.S. I know this is quite simple, but I'm poorly familiar with Object inheritance and can't find proper examples.
P.P.S. I've also checked this but don't know is it outdated or not and didn't catch the idea.
To make a class implement List there are several ways :
Extending ListBase and implementing length, operator[], operator[]= and length= :
import 'dart:collection';
class MyCustomList<E> extends ListBase<E> {
final List<E> l = [];
MyCustomList();
void set length(int newLength) { l.length = newLength; }
int get length => l.length;
E operator [](int index) => l[index];
void operator []=(int index, E value) { l[index] = value; }
// your custom methods
}
Mixin ListMixin and implementing length, operator[], operator[]= and length= :
import 'dart:collection';
class MyCustomList<E> extends Base with ListMixin<E> {
final List<E> l = [];
MyCustomList();
void set length(int newLength) { l.length = newLength; }
int get length => l.length;
E operator [](int index) => l[index];
void operator []=(int index, E value) { l[index] = value; }
// your custom methods
}
Delegating to an other List with DelegatingList from the quiver package:
import 'package:quiver/collection.dart';
class MyCustomList<E> extends DelegatingList<E> {
final List<E> _l = [];
List<E> get delegate => _l;
// your custom methods
}
Depending on your code each of those options have their advantages. If you wrap/delegate an existing list you should use the last option. Otherwise use one of the two first options depending on your type hierarchy (mixin allowing to extend an other Object).
A basic approach is to extend an Object with IterableMixin. It also seems that you don't even need to override the "length" getter or let's say all methods that the IterableMixin already provides.
import 'dart:collection';
class Model {
String name;
int type;
Model(this.name, this.type) {
}
}
class ModelCollection extends Object with IterableMixin {
List<Model> _models;
Iterator get iterator => _models.iterator;
ModelCollection() {
this._models = new List<Model>();
}
//get one or the first type
Model elementByType(int type) {
for (Model model in _models) {
if (model.type == type) {
return model;
}
}
}
//get all of the same type
List<Model> elementsByType(int type) {
List<Model> newModel = new List<Model>();
for (Model model in _models) {
if (model.type == type) {
newModel.add(model);
}
}
return newModel;
}
add(Model model) {
this._models.add(model);
}
}
Excuse my strong static typing.
You might be interested in quiver.dart's Multimap. It behaves like a Map that allows multiple values per key.
Here's the code on github: https://github.com/google/quiver-dart/blob/master/lib/src/collection/multimap.dart#L20
It's on pub simply as quiver. We'll be hosting the dartdocs somewhere soon.