I have the following Dart code
void main() {
final filters = [
FilterOption(
label: 'Category',
hint: 'All categories',
items: A.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toUpperCase()
),
FilterOption(
label: 'Time',
hint: 'All time',
items: B.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase()
)
];
try {
filters.first.itemLabeler;
} catch(e) {
print(e);
}
print('Program terminated');
}
enum A {
here, are, some, options
}
enum B {
here, are, another, few, options
}
class FilterOption<T> {
final String label;
final String hint;
final List<T> items;
final void Function(T item) onChange;
final String Function(T item) itemLabeler;
const FilterOption({
required this.label,
required this.hint,
required this.items,
required this.onChange,
required this.itemLabeler
});
}
This is the output on DartPad
TypeError: Instance of '(A) => String': type '(A) => String' is not a subtype of type '(_Enum) => String'
Program terminated
I am aware of similar questions on StackOverflow, however, many either don't have answers to the problem or suggest implementation outside of a list. I would like to be able to send any number of FilterOptions to some UI element to display. I would also like the generic type to be any data type, not just enums. How can I solve this problem? Thank you to anyone who can offer any insight or solution.
Assuming filters are not just on enums, and don't have a common supertype, there are a few things you can try.
First, you can try to cast the FilterOption
before using it.
void main() {
final filters = [
FilterOption(
label: 'Category',
hint: 'All categories',
items: A.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toUpperCase(),
),
FilterOption(
label: 'Time',
hint: 'All time',
items: B.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase(),
),
];
try {
(filters.first as FilterOption<A>).itemLabeler;
} catch (e) {
print(e);
}
print('Program terminated');
}
enum A { here, are, some, options }
enum B { here, are, another, few, options }
class FilterOption<T> {
final String label;
final String hint;
final List<T> items;
final void Function(T item) onChange;
final String Function(T item) itemLabeler;
const FilterOption({
required this.label,
required this.hint,
required this.items,
required this.onChange,
required this.itemLabeler
});
}
Second, you can promote by testing with is
or pattern matching with case
before using.
void main() {
final filters = [
FilterOption(
label: 'Category',
hint: 'All categories',
items: A.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toUpperCase(),
),
FilterOption(
label: 'Time',
hint: 'All time',
items: B.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase(),
),
];
try {
// copy to local variable then check with is
var first = filters.first;
if (first is FilterOption<A>) {
first.itemLabeler;
}
// check with pattern match
if (filters.last case FilterOption<B> last) {
last.itemLabeler;
}
} catch (e) {
print(e);
}
print('Program terminated');
}
enum A { here, are, some, options }
enum B { here, are, another, few, options }
class FilterOption<T> {
final String label;
final String hint;
final List<T> items;
final void Function(T item) onChange;
final String Function(T item) itemLabeler;
const FilterOption({
required this.label,
required this.hint,
required this.items,
required this.onChange,
required this.itemLabeler,
});
}
Third, if you have a limited number of types you want to use, say Enum
, String
, and int
, you could maybe get away with FilterOption
a sealed
class. This would allow you to exhaustively cover all options when switching over the data.
void main() {
List<FilterOption> filters = [
FilterOptionEnum(
label: 'Category',
hint: 'All categories',
items: A.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toUpperCase(),
),
FilterOptionEnum(
label: 'Time',
hint: 'All time',
items: B.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase(),
),
FilterOptionString(
label: 'String',
hint: 'All string',
items: ['hello', 'world'],
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase(),
),
];
try {
switch (filters.first) {
case FilterOptionEnum<A> a:
a.itemLabeler;
case FilterOptionEnum<B> b:
b.itemLabeler;
case FilterOptionEnum e:
e.itemLabeler;
case FilterOptionInt i:
i.itemLabeler;
case FilterOptionString s:
s.itemLabeler;
}
} catch (e) {
print(e);
}
print('Program terminated');
}
enum A { here, are, some, options }
enum B { here, are, another, few, options }
sealed class FilterOption<T> {
final String label;
final String hint;
final List<T> items;
final void Function(T item) onChange;
final String Function(T item) itemLabeler;
const FilterOption({
required this.label,
required this.hint,
required this.items,
required this.onChange,
required this.itemLabeler,
});
}
class FilterOptionEnum<T extends Enum> extends FilterOption<T> {
FilterOptionEnum({
required super.label,
required super.hint,
required super.items,
required super.onChange,
required super.itemLabeler,
});
}
class FilterOptionInt extends FilterOption<int> {
FilterOptionInt({
required super.label,
required super.hint,
required super.items,
required super.onChange,
required super.itemLabeler,
});
}
class FilterOptionString extends FilterOption<String> {
FilterOptionString({
required super.label,
required super.hint,
required super.items,
required super.onChange,
required super.itemLabeler,
});
}
Fourth, you can try to wrap the callback functions in method calls:
void main() {
final filters = [
FilterOption(
label: 'Category',
hint: 'All categories',
items: A.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toUpperCase(),
),
FilterOption(
label: 'Time',
hint: 'All time',
items: B.values,
onChange: (_) => print('Not implemented yet'),
itemLabeler: (item) => item.toString().toLowerCase(),
),
];
try {
filters.first.itemLabeler;
} catch (e) {
print(e);
}
print('Program terminated');
}
enum A { here, are, some, options }
enum B { here, are, another, few, options }
class FilterOption<T> {
final String label;
final String hint;
final List<T> items;
final void Function(T item) _onChange;
final String Function(T item) _itemLabeler;
void onChange(T item) => _onChange(item);
String itemLabeler(T item) => _itemLabeler(item);
const FilterOption({
required this.label,
required this.hint,
required this.items,
required void Function(T item) onChange,
required String Function(T item) itemLabeler,
}) : _onChange = onChange,
_itemLabeler = itemLabeler;
}