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

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());
}
}

Related

NoSuchMethodEror: tried to call a non-function, such as null:

I have this code which works well on android emulator but gives error on web.
import 'package:parivaar/components/screens/home/Home.dart';
typedef T Constructor<T>();
final Map<String, Constructor<Object>> _constructors =
<String, Constructor<Object>>{};
void register<T>(Constructor<T> constructor) {
_constructors[T.toString()] = constructor;
}
class ClassBuilder {
static void registerClasses() {
register<Home>(() => Home());
}
static dynamic fromString(String type) {
return _constructors[type]();
}
}
And i am calling that function as follows:
class _MyHomePageState extends State {
KFDrawerController _drawerController;
#override
void initState() {
super.initState();
_drawerController = KFDrawerController(
initialPage: ClassBuilder.fromString('Home'),
.
..
...
....
You are probably assuming that T.toString() returns the source name of the type as a string. Nobody ever promised that.
It works on native, but on the web you often optimize for size and "minification" turns all the long names into shorter names. With that, the name of the type Home is no longer the string "Home".
I generally do not recommend depending on the string representation of types (or Type objects for that matter).
Consider changing register and fromString to:
void register<T>(Constructor<T> constructor) {
_constructors[T] = constructor;
}
and
static T fromType<T>() => _constructors[T]();
That relies on Type object equality, which is a well-defined operation.
Not perfect, but still better than going through strings.
If you need to create the objects dynamically from strings, where you don't know the type, then I'd instead require you to provide the key string on registration, changing register to:
void register<T>(String key, Constructor<T> constructor) {
_constructors[key] = constructor;
}
and register types like:
static void registerClasses() {
register<Home>("Home", () => Home());
}

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);
}
}

Is a good practice pass Widgets as class argument in flutter?

I mean create a class like this:
class HighLightAnimationState extends State<HighLightAnimation> {
HighLightAnimationState(Card this.child, this._elevation, this._boxShadow);
final Card child;
final double _elevation;
final double _boxShadow;
#override
Widget build(BuildContext context) {
return this.child;
}
}
class HighLightAnimation extends StatefulWidget {
HighLightAnimation(Card this.child, [this._elevation = 1.0, this._boxShadow = 0.0]);
final Card child;
final double _elevation;
final double _boxShadow;
#override
createState() => new HighLightAnimationState(this.child, this._elevation, this._boxShadow);
}
It remarks on Card Widget and indicates "Don't type annotate initializing formals"
When I google it, I went redirected to https://www.dartlang.org/guides/language/effective-dart/usage, so, that's why I wanna know if the thing that I am doing is right.
It's OK to pass widgets to constructors, of course. Remove the type Card from Card this.child. That type is not wrong, just unnecessary, that's why you are getting the warning.
It should be:
HighLightAnimationState(this.child, this._elevation, this._boxShadow);
HighLightAnimation(this.child, [this._elevation = 1.0, this._boxShadow = 0.0]);

How to use json_annotation with a Dart Extended ListBase class

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.

What does the Object<type> syntax mean in Dart?

In the following code example, from the flutter docs:
class RandomWords extends StatefulWidget {
#override
createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
#override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return Text(wordPair.asPascalCase);
}
}
What exactly does the State<RandomWords> syntax mean?
I understand that you can specify the type for the objects contained in a collection, like lists, using this syntax - List <String>
But I cannot understand the motive behind State<RandomWords>.
Moreover, how can you reference RandomWordsState in RandomWords declaration and also reference RandomWords in RandomWordsState declaration? Shouldn't that cause a circular reference error or something?
I come from dynamically typed languages like python, and this looks a little odd to me, can someone please point me to the right place?
<RandomWords> is a generic type parameter passed to the State class.
The State class looks like
abstract class State<T extends StatefulWidget> extends Diagnosticable {
and RandomWords will be passed to the T type parameter which has a constraint that T needs to be a subclass of StatefulWidget.
State also has a field and getter where the type parameter is used
T get widget => _widget;
T _widget;
This results in a property of the type of the widget
which provides proper autocompletion and type checks in its subclass RandomWordsState
Assume you have
class RandomWords extends StatefulWidget {
RandomWords({this.fixed});
final WordPair fixed;
#override
createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
#override
Widget build(BuildContext context) {
// vvvv here we can access `fixed` in a strongly typed manner
final wordPair = widget.fixed ?? WordPair.random();
return Text(wordPair.asPascalCase);
}
}
See also https://www.dartlang.org/guides/language/language-tour#generics

Resources