Enum Generic not allow typed enum - flutter

I created a class that accepts a generic enum as one of it's parameters. However when I then go to pass in a specified enum type, it throws the error Expected a value of type '(Enum?) => void', but got one of type '(GradeLevel?) => void'.
My classes are:
Radio Group Presentable
class RadioGroupPresentable<T extends Enum> implements Presentable {
final List<T> values;
final T groupValue;
void Function(T? value) onChanged;
RadioGroupPresentable(this.values, this.groupValue, this.onChanged);
}
RadioGroup
class RadioGroup<T extends Enum> extends StatelessWidget {
final List<T> _values;
final T _groupValue;
ValueChanged<T?> onChanged;
RadioGroup(this._values, this._groupValue, this.onChanged, {super.key});
RadioGroup.fromPresentable(RadioGroupPresentable<T> p): this(p.values, p.groupValue, p.onChanged);
#override
Widget build(BuildContext context) {
print("building radio grouo");
return Column(
children: _values.map((e) {
return ListTile(
title: Text(e.name),
leading: Radio<T>(
value: e,
groupValue: _groupValue,
onChanged: onChanged,
),
);
}).toList(),
);
}
}
Enum
enum GradeLevel {
tenth, eleventh, twelfth
}
Presentable creation
RadioGroupPresentable(GradeLevel.values, GradeLevel.tenth, (value) {
if (value != null) {
gradeLevel.setValue(value, action: "Update Grade Level");
_application.gradeLevel = value;
print(value);
}
})
View (where error occurs)
case RadioGroupPresentable<GradeLevel>:
RadioGroupPresentable<GradeLevel> presentable = p as RadioGroupPresentable<GradeLevel>;
RadioGroup widget = RadioGroup.fromPresentable(presentable);
content.add(widget);
break;
I have tried to make the class generic so it would accept any enum, however it crashes when I pass it an enum.

The issue is caused by this line:
RadioGroup widget = RadioGroup.fromPresentable(presentable);
Type inference is failing here, so this is being interpreted as RadioGroup<Enum>.fromPresentable(presentable) rather than RadioGroup<GradeLevel>.fromPresentable(presentable).
When the RadioGroup.fromPresentable structure attempts to store the onChanged function, it finds a function that has the wrong type. It wants a function that is capable of taking an argument of any Enum type, but the function it finds is only capable of taking arguments of the GradeLevel type.
Remember, a function is only a subtype of another if its argument types are equally or more permissive than its supertype.
(Enum value) {} is void Function(GradeLevel);
(GradeLevel value) {} is! void Function(Enum);

Related

Flutter generic function callback

can you comment?
typedef ButtonChangedCallback = void Function<T>(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback? onCallback;
const MyWidget(
{required Key key,
this.onCallback})
: super(key: key);
I would like to create such template widget to be used with different enums. So it can signal what value from the enum was selected.
But I am unable to find how to later assign to the "onCallback" method.
MyWidget(
key: const Key("RadioControls"),
onCallback: <MyEnum>(MyEnum value) =>
setState(() {
someSettings.value = value;
}),
)
This does not work with Value of type MyEnum can not be assigned to variable of type MyEnum By some experiments I discovered that inside the lambda does not seem to correspond to MyEnum as defined before.
EDIT: Solution
typedef ButtonChangedCallback<T> = void Function(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback<T>? onCallback;
used as
MyWidget<MyEnum>(
key: const Key("RadioControls"),
onCallback: (MyEnum value) =>
setState(() {
someSettings.value = value;
}),
)
When you do:
typedef ButtonChangedCallback = void Function<T>(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback? onCallback;
You're declaring that ButtonChangedCallback must be a generic function. Whatever callback is assigned to MyWidget.onCallback must itself be generic. That is, you would only be able to use it as:
MyWidget(onCallback: <T>(T x) { ... });
In the above, T is the name of the type parameter to your generic, anonymous function. It is not a type argument. T could be named anything, and in your attempt, you happened to name it MyEnum, so you ended up in a confusing situation where a generic type parameter had the same name as an actual type.
Additionally, the type parameter for the function would be unrelated to the type parameter for MyWidget.
What you probably want is to for the typedef to be generic and for the Function object to not be:
typedef ButtonChangedCallback<T> = void Function(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback<T>? onCallback;
and now you can use it as:
MyWidget<MyEnum>(
onCallback: (value) =>
setState(() {
someSettings.value = value;
}),
This way should work:
class MyWidget<T> extends StatefulWidget {
final Function<T>? callback;
const MyWidget({required this.key, this.callback}) : super(key: key);
}
And you instantiate the widget this way:
MyWidget<MyEnum>(
key: const Key("RadioControls"),
callback: (MyEnum value) {
setState((){
someSettings.value = value;
});
}
)

Dart correct way to specify generic argument type on callback function

I'vo got a strange error, for a class similar to this one:
class UpdatableListPage<T> extends ConsumerStatefulWidget {
final StateNotifierProvider<UpdatableNotifier, List<T>> provider;
final Widget Function(T t) callbackWidget;
[...]
#override
_UpdatableListPageState<T> createState() => _UpdatableListPageState<T>();
}
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
#override
Widget build(BuildContext context) {
// Here the IDE said modelList is dynamic
var modelList = ref.watch(widget.provider);
[...]
ListView(
key: _refreshKey,
shrinkWrap: true,
scrollDirection: widget.scrollDirection,
children: [
for (final product in modelList as List<T>) widget.callbackWidget.call(product),
],
}
}
And I call the funciton as:
UpdatableListPage<RsMsgMetaData>(
userPostsProvider,
callbackWidget: (t) => PostTeaserCard(t,),
),
Where PostTeaserCard is a statefull Widget that recieve a RsMsgMetaData object as parameter.
The IDE say that everything is Ok but at run time, I got the following error:
type '(RsMsgMetaData) => PostTeaserCard' is not a subtype of type '(dynamic) => Widget'`
Seems like callbackWidget acts as (dynamic) => Widget function, but anyway... Should this function be compatible with the function signature of the anonymous function, right?
I don't know what is going on with this...
You wrote:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
which is equivalent to:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<dynamic>> {
Consequently, the type of _UpdatableListPageState<T>.widget.callbackWidget is Widget Function(dynamic t). You cannot pass a PostTeaserCard Function(RsMsgMetaData) for a Widget Function(dynamic) because the latter is callable with any argument, but the function you actually passed can be called only with an RsMsgMetaData argument.
To fix this, fix your _UpdatableListPageState class declaration to avoid the implicit use of dynamic and to fully depend on T:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<T>> {
This probably will fix the type for modelList to List<T> instead of List<dynamic>.
Enabling the strict-raw-types check in your analysis_options.yaml should help catch this kind of error in the future.

How can I iterate a collection (e.g. Map<String, supertype>) and render a separate widget for each subtype in the collection?

I have a list of objects that are all subtypes of Animal. I cast them all to supertype in the animal list to keep their references in one place. So far so good.
Now, when I render state information about each animal object I would like to use different widgets based on the subtype of each Animal object. When I cast from supertype to subtype, even when I specify the field is dynamic, I get the error.
Type 'type' is not a subtype of type '<subtype>'
It seems that the dart compiler uses the type information from the animalTiles map to enforce that all elements are of type Animal, even if the constructor that I pass the type into takes a dynamic named parameter, which I hoped would be looser.
// ANIMAL LIST
class AnimalList extends StatelessWidget {
final Map<String, Animal> animals;
#override
Widget build(BuildContext context) {
List<Widget> animalTiles = [];
animals.forEach(
(key, setting) => animalTiles.add(
AnimalTile(animal: animal),
),
);
return Column(
children: animalTiles,
);
}
}
// ANIMAL TILE
class AnimalTile extends StatelessWidget {
AnimalTile({
required animal,
}) {
_buildDescription(animal);
}
late dynamic animal;
late Widget description;
Widget _buildDescription(dynamic setting) {
if (animal.type == Animal.CAT) {
Cat cat = animal;
return CatWidget(cat: cat);
} else if (animal.type == Animal.DOG) {
Dog dog = animal;
return DogWidget(dog: dog);
} else if (animal.type == Animal.MOUSE) {
Mouse mouse = animal;
return MouseWidget(mouse: mouse);
} else {
return Container(child: Text(':('));
}
}
#override
Widget build(BuildContext context) {
return(
...
);
}
}
The Dart type system requires that I initialize final fields before the constructor body in an initialization list or assign directly to instance fields in the params list. If I call a helper method in the body of my constructor, that is not considered final so I would not like to use this for an immutable Stateless Widget.
This has to be a somewhat common pattern..I am trying to keep all subtypes in the same list and then use the subtype type to display different widgets. Why does this approach fail and what is a valid way to implement this pattern in Dart?
The Flutter Cookbook has an example of creating a list of items of different subtypes. However I find myself repeating data fields that I was hoping would be common to the base class. Because, if I use extends in place of implements (i.e. inheritance over interface) for the abstract Animal class, I get an Exception indicating that Dart is looking at the method of the base class, not the overridden method in the subclass. This is not the best solution for keeping your code DRY, but it works.
Unfortunately, this means that I do not have a solution that enables me to keep data fields used by my many animals within a single class, but I am posting this to demonstrate a pattern that works.
UPDATE:
You may also be able to reuse data-fields across subtypes while ensuring that the methods called are those overridden by the subtype using both extends (inheritance) and implements (interface).
Here is my solution, please outdo me:
// Base Class
abstract class AnimalBase {
AnimalBase({
required this.height,
required this.weight,
required this.name,
required this.genus,
required this.species,
});
final int height;
final double weight;
final String name;
final String genus;
final String species;
static const CAT = 0;
static const DOG = 1;
static const MOUSE = 2;
static AnimalFactory(Map<String, dynamic> json, int type) {
// IN MY CASE I PASS THE TYPE TO A FACTORY
switch (type) {
case CAT:
return Cat.fromJson(json);
case DOG:
return Dog.fromJson(json);
case MOUSE:
return Mouse.fromJson(json);
default:
return null;
}
}
}
// Interface Class
abstract class Animal; {
Widget buildTile();
Widget description();
}
// Example SubType
class Dog extends AnimalBase with Animal {
Dog({
required height,
required weight,
required name,
required genus,
required species,
}) : super(height: height, weight: weight, name: name, genus: genus, species: species);
#override
Dog.fromJson(Map<String, dynamic> json)
throw new UnimplementedError();
}
Map<String, dynamic> toJson() {
throw new UnimplementedError();
}
#override
Widget buildTile() {
return DogTile(dog: this);
}
#override
Widget description() {
return DogDescription(dog: this);
}
}
class AnimalList extends StatelessWidget {
AnimalList({
Key? key,
required this.animals,
}) : super(key: key);
final Map<String, Animal> animals;
#override
Widget build(BuildContext context) {
List<Widget> animalTiles = [];
animals.forEach(
(key, animal) => animalTiles.add(
AnimalTile(animal: animal),
),
);
return CustomScrollView(
shrinkWrap: true,
slivers: [
SliverToBoxAdapter(
child: Column(
children: ...animalTiles.map(animal => animal.buildTile()).toList(),
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Divider(
color: Colors.black54,
height: 18,
thickness: 4,
indent: 20,
endIndent: 20,
),
],
),
),
],
);
}
}
I wish I had a solution that only required one keyword (extends or implements) but this is the best I have :)

how to pass a function with callback type in Dart?

say for example I have a custom text field class like this
class CustomTextField extends StatelessWidget {
final Function onSubmit;
CustomTextField({
required this.onSubmit,
});
#override
Widget build(BuildContext context) {
return TextField(
onSubmitted: (value) => onSubmit(value),
);
}
}
as you can see, I need to pass a function to get the submitted text string value from text field. and I can use it like this
CustomTextField (onSubmit: () {
print("something");
})
but I don't have any error when I write my code like that. what I want is like this
CustomTextField (onSubmit: (myValue) { // <--- if I don't write 'myValue' in here it will have error
print(myValue);
})
I want my code to give me error if I don't write a variable name (string) inside the parentheses
how to do that?
You need to change the type of onSubmit from a general function to a function that takes in a String parameter like so.
final Function(String value) onSubmit;

Receive value with unspecified data type then find out the data type

For the sake of this question I have simplified my case here. I want to reuse a button widget which opens up a textfield dialog. If the button receive String data value then use text keyboard and if data type is int then use number keyboard. How can I do that? Here is my code
my screen
class MyScreen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: ReuseButton(value: 'pp'), //passing String value
);
}
}
reusable button
class ReuseButton extends StatelessWidget {
final dynamic value; // receiving unspecified data type
const ReuseButton({this.value});
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text('Enter Value'),
onPressed: () async {
String s = await textFieldDialog(
context: context,
initialValue: '',
keyboardType: TextInputType.numberWithOptions(
decimal: true), //here to do comparison
title: 'Equal to');
print(s);
},
);
}
}
I have already tried to make a model which is a simple class
class SpecVal<T> {
T data;
SpecVal(this.data);
}
and then pass this data in my screen to the button widget
ReuseButton(value: SpecVal<String>('some value'))
But then I was having problem to find out what data type SpecVal is using in the button widget to do comparison
You can check the type of a variable using is.
void main() {
printType(42); // 42 is an int
printType("some string"); // some string is a String
}
void printType(dynamic value) {
if (value is int)
print("$value is an int");
if (value is String)
print("$value is a String");
}
More generally, you can retrieve the type of a variable with value.runtimeType.
void main() {
printType(42); // 42 is an int
printType("some string"); // some string is a String
}
void printType(dynamic value) {
Type type = value.runtimeType;
if (type == int)
print("$value is an int");
if (type == String)
print("$value is a String");
}
The type parameter of a generic function/class is also a Type so you can check it like above.
void main() {
printType<String>(); // T is a Type / the value of T is String
}
void printType<T>() {
if (T is Type)
print("T is a Type");
if (T == int)
print("the value of T is int");
if (T == String)
print("the value of T is String");
}
There are many ways you can use these techniques on your problem, e.g. you can change your SpecVal to include a function like below:
class SpecVal<T> {
...
bool get isString => T == String;
...
}
Related to How to perform runtime type checking in Dart?