How to use json_annotation with a Dart Extended ListBase class - dart

So I have a #JsonSerializable class that's doing the job without an issue.
#JsonSerializable()
class BaseValue {
String id;
var value;
DateTime valueDate;
BaseValue({
this.id,
this.value,
this.valueDate
});
factory BaseValue.fromJson(Map<String, dynamic> json) => _$BaseValueFromJson(json);
Map<String, dynamic> toJson() => _$BaseValueToJson(this);
}
No I want to have the same fromJson toJson enabled for an extended ListBase class but can't find how to implement this.
class BaseValues<BaseValue> extends ListBase<BaseValue> {
final List<BaseValue> l = [];
BaseValues();
void set length(newLength) => l.length = newLength;
int get length => l.length;
BaseValue operator [](int index) => l[index];
void operator []=(int index, BaseValue value) => l[index] = value;
}
Maybe I need to use something else instead of ListBase.
Thanks in advance for any help provided.

It's possible, but it's not super easy. You need to create a custom TypeHelper to support your class.
Then, if you want to use pub run build_runner... you'll need to create a custom builder that instantiates an instance of JsonSerializableGenerator and include an instance of your TypeHelper.
There is a pending commit that will give you some more freedom here - https://github.com/dart-lang/json_serializable/commit/4f19d468bf05eed3e4a8ebc27244fc3b8d411dc9 – but you'll still have to handle the possible generic type args of BaseValues.

Related

How to create a mixin for advanced enum and use it in a generic widget?

My goal is to write a generic Widget that, in this case, enables the user for selecting an enum value among all the values from the enum.
So I'd like to write something like so:
class WheelPickerWidget<T extends Enum> extends StatelessWidget {
/// The initial value
final T? value;
/// The onChanged callback
final void Function(T)? onChanged;
/// Retuns the wheel enum picker
const WheelPickerWidget(
{super.key, required this.value, required this.onChanged});
#override
Widget build(BuildContext context) {
return ListWheelScrollView(
physics: const BouncingScrollPhysics(),
itemExtent: 50,
diameterRatio: 0.6,
//offAxisFraction: -0.4,
squeeze: 1.8,
//useMagnifier: true,
//overAndUnderCenterOpacity: 0.8,
clipBehavior: Clip.antiAlias,
onSelectedItemChanged: (value) => onChanged?.call(T.fromValue(value)),
children: T.values.map((c) => Text("$c")).toList());
}
}
But I see T.fromValues() and T.values are generating errors as follows:
The method 'fromValue' isn't defined for the type 'Type'.
Try correcting the name to the name of an existing method, or defining a method named 'fromValue'.
The getter 'values' isn't defined for the type 'Type'.
Try importing the library that defines 'values', correcting the name to the name of an existing getter, or defining a getter or field named 'values'.
I usually write my enums as follows:
/// Theme to use for the app
enum AppTheme {
green(0),
yellow(1),
nightBlue(2);
const AppTheme(this.value);
final int value;
factory AppTheme.fromValue(int v) => values.firstWhere((x) => x.value == v,
orElse: () => throw Exception("Unknown value $v"));
/// Returns the name corresponding to the enum
#override
String toString() {
switch (this) {
case green:
return i18n_Green.i18n;
case yellow:
return i18n_Yellow.i18n;
case nightBlue:
return i18n_Night_blue.i18n;
}
}
}
Where I make fromValue() readily available.
And I guess I could use mixin to create a specific form of enum that complies to the requirements.
/// Advanced enum
mixin EnumMixin {
}
But I didn't manage to do it: one reason is the factory cannot be supported by the mixin.
So to sum up, my questions are:
How to make my wheel picker class works with my enum?;
How to create a generic way (possibly being a mixin) to conform all my enums to a way it can be supported by my generic wheel picker?
You cannot make T.someConstructor() or T.someStaticMethod() work for some generic type T. Dart does not consider constructors and static methods to be part of the class interface, and they are not inherited.
In general, whenever you want to use something like T.someConstructor() or T.someStaticMethod(), you're probably better off using a callback instead. Similarly, instead of using T.values, you can accept a List<T> argument.
For example:
class WheelPickerWidget<T extends Enum> extends StatelessWidget {
WheelPickerWidget({required this.values, required this.fromValue});
final List<T> values;
final T Function(int) fromValue;
...
#override
Widget build(BuildContext context) {
return ListWheelScrollView(
...
onSelectedItemChanged: (value) => onChanged?.call(fromValue(value)),
children: values.map((c) => Text("$c")).toList());
}
}
and then callers would use:
WheelPickerWidget(values: AppTheme.values, fromValue: AppTheme.fromValue);
Note that fromValue is a bit redundant in principle if you already have values; you could just iterate over values to find the Enum you want. For example, you could do:
abstract class HasValue<T> {
T get value;
}
enum AppTheme implements HasValue<int> {
green(0),
yellow(1),
nightBlue(2);
const AppTheme(this.value);
#override
final int value;
...
}
class WheelPickerWidget<T extends Enum> extends StatelessWidget {
WheelPickerWidget({required this.values})
final List<T> values;
...
#override
Widget build(BuildContext context) {
return ListWheelScrollView(
...
onSelectedItemChanged: (value) => onChanged?.call(values.findValue(value)),
children: values.map((c) => Text("$c")).toList());
}
}
extension<T extends Enum> on List<T> {
T findValue<U>(U value) {
for (Object e in this) {
if (e is HasValue<U> && e.value == value) {
return e as T;
}
}
throw Exception("Unknown value $value");
}
}
Unfortunately, findValue is slightly awkward because there doesn't seem to be a good way to enforce that T derives from both Enum and HasValue, so it must perform runtime type-checking. Additionally, Dart will not perform automatic type promotion between unrelated types (in this case, Enum and HasValue), so findValue upcasts to Object first as a workaround.
If you don't want callers to pass extra arguments, one alternative would be to store those arguments in a global lookup table with the generic type as the key. This isn't a great general approach since a Map<Type, ...> depends on exact Type matches, so looking up a subtype wouldn't match a supertype in the Map. However, Enums are not allowed to be extended nor implemented, so that is not a concern. I would consider it to be less robust, however, since it would require extra work to initialize such a Map, and there's no way at compile-time that it's been initialized with all of the types you care about. As an example of how this could look:
final _fromValueMap = <Type, Enum Function(int)>{
AppTheme: AppTheme.fromValue,
};
final _lookupValuesMap = <Type, List<Enum>>{
AppTheme: AppTheme.values,
};
T fromValue<T>(int value) => _fromValueMap[T]!(value) as T;
List<T> lookupValues<T>() => _lookupValuesMap[T]! as List<T>;
class WheelPickerWidget<T extends Enum> extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return ListWheelScrollView(
...
onSelectedItemChanged: (value) => onChanged?.call(fromValue<T>(value)),
children: lookupValues<T>().map((c) => Text("$c")).toList());
}
}

Deserialization of abstract types in Dart

Please consider the following code segment, declaration of one base class and two extenders.
abstract class Animal {
final bool flag;
Animal(this.flag);
}
class Cat extends Animal {
final String name;
Cat(this.name, bool flag) : super(flag);
Cat.fromJson(Map<String, dynamic> json)
: name = json['name'],
super(json['flag']);
}
class Fish extends Animal {
final int memory;
Fish(this.memory, bool flag) : super(flag);
Fish.fromJson(Map<String, dynamic> json)
: memory = json['memory'],
super(json['flag']);
}
What is the best practice of serializing and deserializing the following list?
List<Animal> animals = [Cat('Nancy', true), Fish(10, false)];
So, how to serialize the type information for each class in order to construct the appropriate instance upon deserialization?
The serialization code is ommited for the sake of simplicity, however it would return a map of the member fields. The code above is only an example - there is no meaning in the structure or the data.
EDIT: I would prefer not using any library or code generator, but using the internal capabilities of the language.
Any suggestion is appretiated, thank you.
I had to figure that out, so I provide my naive solution for the problem. However, if anyone gives a better approach, I will mark that as an answer.
My naive solution is to decorate every JSON result with a 'type' field that contains the name of the subclass. Then I could create a factory for the base type that constructs the appropriate instance depending on that 'type' field. The full code:
abstract class Animal {
final bool flag;
Animal(this.flag);
factory Animal.fromJson(Map<String, dynamic> json) {
switch (json['type']) {
case 'cat':
return Cat.fromJson(json);
case 'fish':
return Fish.fromJson(json);
default:
throw 'Invalid animal type';
}
}
}
class Cat extends Animal {
final String name;
Cat(this.name, bool flag) : super(flag);
Cat.fromJson(Map<String, dynamic> json)
: name = json['name'],
super(json['flag'].toString() == 'true');
Map<String, dynamic> toJson() {
return {'type': 'cat', 'name': name, 'flag': flag.toString()};
}
}
class Fish extends Animal {
final int memory;
Fish(this.memory, bool flag) : super(flag);
Fish.fromJson(Map<String, dynamic> json)
: memory = json['memory'],
super(json['flag'].toString() == 'true');
Map<String, dynamic> toJson() {
return {'type': 'fish', 'memory': memory, 'flag': flag.toString()};
}
Obviously, during deserialization the list items should be mapped using the factory method as follows:
var data = jsonDecode("[{'type':'cat','name':'Nancy','flag':'true'},{'type':'fish','memory':10,'flag':'false'}]");
List<Animal> list = data.map((e) => Animal.fromJson(e)).cast<Animal>().toList();

How to set a default value for an optional positional parameter of type Function?

I am passing a Function as an optional parameter to the constructor but I can't assign a default value.
void main() {
Person p = Person();
print(p.foo('Hello'));
}
class Person {
final String Function(String) foo;
Person({this.foo});
}
now trying to assign a default value: Person({this.foo = (val) {return val;});
produces the error: Error: Not a constant expression. I am aware the parameter must be const but using const or even static infront of (val) {return val;} does not work.
Does anyone have an idea how to solve this problem?
You can try this:
void main() {
Person pLower = Person(foo: (a) => a.toLowerCase());
print(pLower.foo('Hello'));
Person pDefault = Person();
print(pDefault.foo('Hello'));
}
class Person {
static String defaultFoo(String a) => a.toUpperCase();
final String Function(String) foo;
Person({this.foo = defaultFoo});
}
Output
hello
HELLO
You can only use constant values (aka. compile-time constants) as default values.
You cannot create a constant function literal, so there is no way to write the function in-line in the constructor.
However, references to top-level or static functions are constants, so you can declare the default value function as a static function or top-level function.
void main() {
Person p = Person();
print(p.foo('Hello')); // Prints "Hello"
}
class Person {
final String Function(String) foo;
Person({this.foo = _identity});
static String _identity(String value) => value;
}
// or as top-level.
// String _identity(String value) => value;
You can (and should) choose to make the function public if the default value is on an instance method, and you expect anyone to extend or implement your class. In that case, they need to declare the same default value.
Another option, which is often at least as useful, is to not use a default value, but replace a null before using the value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = foo ?? _identity;
static String _identity(String value) => value;
}
or even using a non-constant value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = (foo ?? (String x) => x);
}
For a constructor, it makes very little difference. If it was an instance method instead, using ?? to replace null avoids subclasses having to use the exact same function as default value.
Personally I recommend always using ?? instead of a default value. It's more flexible since it allows non-constant values. For non-function default values, you'll have to document the default behavior instead of just letting the dartDoc show {int x = 42}, but for functions, you'll have to document them anyway.

What's the equivalent to this[x] in Dart?

For instance, in Javascript I can do something like:
class Foo {
x = 'baz';
bar() {
const someVar = 'x';
console.log(this[someVar]);
// Output: 'baz';
}
}
Hopefully that's relatively clear - it boils down to accessing a member variable by another variable's contents. How is this achieved in Dart?
This is not trivial in Dart. Dart doesn't have a syntax to access class properties with [].
There are a couple of approaches though:
Mirrors:
https://api.dartlang.org/stable/2.6.1/dart-mirrors/dart-mirrors-library.html
Basically you have access to everything and offers the biggest freedom. You can check what properties a class has, access them via names and so on. Big disadvantage is that the generated JS (if targeting web) will be huge. Flutter doesn't support it at all.
Reflectable
To deal with the large generated JS, you can use package:reflectable. Never tried it with Flutter. It's a bit more to set up and start using bit it works.
Dart only solution 1
You can overload [] operator on a class:
class Foo {
final _backing = <String, String>{
'foo': 'bar'
};
operator [](String val) {
return _backing[val];
}
}
void main() {
final inst = Foo();
print(inst['foo']);
}
Dart only solution 2
Just use a map :) Well sort of... If you are dealing with complex types and you want to add some extra functionality to your map, you can do something like this:
import 'dart:collection';
class StringMap extends Object with MapMixin<String, String> {
final _backing = <String, String>{};
#override
String operator [](Object key) {
return _backing[key];
}
#override
void operator []=(String key, String value) {
_backing[key] = value;
}
#override
void clear() {
_backing.clear();
}
#override
Iterable<String> get keys => _backing.keys;
#override
String remove(Object key) {
return _backing.remove(key);
}
}

How do I extend a List in Dart?

I want to create a more specialized list in dart. I can't directly extend List. What are my options?
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
}
Delegating to an other List with DelegatingList from the collection package:
import 'package:collection/wrappers.dart';
class MyCustomList<E> extends DelegatingList<E> {
final List<E> _l;
MyCustomList() : this._(<E>[]);
MyCustomList._(l) :
_l = l,
super(l);
// your custom methods
}
Depending on your code each of those options has 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 another Object).
There is a ListBase class in dart:collection. If you extend this class, you only need to implement:
get length
set length
[]=
[]
Here is an example:
import 'dart:collection';
class FancyList<E> extends ListBase<E> {
List innerList = new List();
int get length => innerList.length;
void set length(int length) {
innerList.length = length;
}
void operator[]=(int index, E value) {
innerList[index] = value;
}
E operator [](int index) => innerList[index];
// Though not strictly necessary, for performance reasons
// you should implement add and addAll.
void add(E value) => innerList.add(value);
void addAll(Iterable<E> all) => innerList.addAll(all);
}
void main() {
var list = new FancyList();
list.addAll([1,2,3]);
print(list.length);
}
A new way of extending classes was introduced with Dart 2.6.
You can now create an extension of List like this:
extension MyCustomList<T> on List<T> {
// Any methods you want can be added here.
}
The methods you add can be used implicitly, i.e. you can just use them on any List when you have your extension imported.
Here is an example from the feature specification:
extension MyFancyList<T> on List<T> {
int get doubleLength => this.length * 2;
List<T> operator-() => this.reversed.toList();
List<List<T>> split(int at) =>
<List<T>>[this.sublist(0, at), this.sublist(at)];
List<T> mapToList<R>(R Function(T) convert) => this.map(convert).toList();
}
You can use these new members on any List, e.g. like this:
const list = <String>['some', 'elements'];
list.doubleLength; // Evaluates to 4.
The answers to this are pretty outdated, and I'm in the process of doing this for my own project, so I thought I'd help some people out by posting a really clean answer that doesn't involve any overriding or implementing of things.
The quiver package has an extendable List class called DelegatingList that makes extending a list trivial.
class FruitList extends DelegatingList<Fruit> {
final List<Fruit> _fruits = [];
List<Fruit> get delegate => _fruits;
// custom methods
}
Hopefully this helps someone who comes across this question like I did!
Following on from the answer above, you can create an immutable list like this:
class ImmutableList<E> extends ListBase<E> {
late final List<E> innerList;
ImmutableList(Iterable<E> items) {
innerList = List<E>.unmodifiable(items);
}
#override
int get length => innerList.length;
#override
set length(int length) {
innerList.length = length;
}
#override
void operator []=(int index, E value) {
innerList[index] = value;
}
#override
E operator [](int index) => innerList[index];
}
//list is your given List and iterable is any object in dart that can be iterated
list.addAll(Iterable)

Resources