Why enums can't be used in const constructors in Flutter? - flutter

can someone tell me if there's a way to use enhanced enums in const constructors? I tried both named constructors and factory constructors, with no luck:
enum MyEnum {
first('first-string'),
second('second-string');
final String string;
const MyEnum(this.string);
}
class MyClass {
final String input;
const MyClass({required this.input});
const MyClass.first({
this.input = MyEnum.first.string, //error
});
factory MyClass.second() {
return const MyClass(input: MyEnum.second.string); //error
}
}
Named constructor gives error: The default value of an optional parameter must be constant
Factory constructor gives error: A value of type 'Null' can't be assigned to a parameter of type 'String' in a const constructor. Try using a subtype, or removing the keyword 'const'
Right now the only solution I found was to replace enum with a class containing static const params, like this:
class MyEnumClass {
static const String first = 'first-string';
static const String second = 'second-string';
}

You can use enums if you use the enum type itself for the member.
enum MyEnum {
first('first-string'),
second('second-string');
final String string;
const MyEnum(this.string);
}
class MyClass {
final MyEnum _input;
String get input => _input.string;
const MyClass({required MyEnum input}) : _input = input;
const MyClass.first({
MyEnum input = MyEnum.first,
}) : _input = input;
factory MyClass.second() {
return const MyClass(input: MyEnum.second);
}
}
In general, getters are equivalent to function calls and aren't constant expressions. (There are a some exceptions, and maybe enum members should be too.)

Related

Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late' Non-nullable instance field ''_catalog' initia [duplicate]

I've created my class in Dart this way, but I'm getting the Non-nullable instance field 'text' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'. I would like to know if there's a way to do it in a 'Python' style where this kind of class creation is possible, thank you in advance.
class Lexer {
String _text;
int _pos;
String _current_char;
Lexer(String text) {
this._text = text;
this._pos = -1;
this._current_char = '';
this.advance();
}
void advance() {
this._pos++;
this._current_char = this._pos < this._text.length ? this._text[this._pos] : '';
}
}
class Lexer {
String _text;
int _pos;
String _current_char;
This declares several members with type String. Since they are declared as String and not as String?, these members are non-nullable; they are not allowed to ever be null. (This is part of the new null-safety feature from Dart 2.12.)
Dart initializes objects in two phases. When the constructor's body runs, Dart expects all member variables to already be initialized. Because your members are non-nullable and haven't been initialized to non-null values yet, this is an error. The error message explains what you can do:
Non-nullable instance field 'text' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Use initializer expressions. This means using an initializer list:
Lexer(String text)
: _text = text,
_pos = -1,
_current_char = '' {
advance();
}
Note that if you're initializing members with a construction parameter of the same name, you can use shorthand:
Lexer(this._text)
: _pos = -1,
_current_char = '' {
advance();
}
Adding field initializers. This means initializing members inline in the class declaration.
class Lexer {
String _text = '';
int _pos = -1,
String _current_char = '';
Marking your members as late. This means that you promise that the variables will be initialized before anything attempts to use them.
class Lexer {
late String _text;
late int _pos,
late String _current_char;
Making your members nullable, which allows them to be implicitly null by default:
class Lexer {
String? _text;
int? _pos,
String? _current_char;
However, that will require that all accesses explicitly check that the members aren't null before using them.
You also might want to read: Dart assigning to variable right away or in constructor?

Is it possible to declare a Map type that requires all enum values to be present as keys?

How can I require the Dart compiler to warn me when I forget to include all members of an enum in a map? For example, in the following:
enum Size {
small,
medium,
large,
}
// This is a valid Dart code. Dart compiler doesn't require `Size.large` to be present.
final Map<Size, ButtonSize> sizeMap = {
Size.small: const MyClass(),
Size.medium: const MyClass(),
};
The Dart compiler isn't that restrictive. It doesn't require all enum values to be present in the Map, so I can't be sure that the following code will return an instance of MyClass. It might resolve to null:
final MyClass instance = sizeMap[Size.small]; // unsafe
I have to either do this:
final MyClass? instance = sizeMap[Size.small]; // `instance` might be `null`
or this:
final MyClass instance = sizeMap[Size.small] as MyClass; // `instance` might still be `null`, but we're pretending it's not.
Both solutions are far from perfect. The first one implies further null checks in the code, the second one smells because of typecasting.
Is there any way to declare the type of sizeMap so that all enum values must be present?
Instead of using a Map, you'd be better off with a function and a switch statement:
enum Size {
small,
medium,
large,
}
class MyClass {
const MyClass();
}
MyClass mapSize(Size size) {
switch (size) {
case Size.small:
return const MyClass();
case Size.medium:
return const MyClass();
}
}
the above code generates a compile-time error from not handling all enum values:
Error: A non-null value must be returned since the return type 'MyClass' doesn't allow null.
With null-safety, the above code cannot be executed until the above problem is fixed.
Additionally, dart analyze will produce a more descriptive warning:
Missing case clause for 'large'. Try adding a case clause for the missing constant, or adding a default clause. • missing_enum_constant_in_switch
If you want to use a Map because you want to allow it to be mutated, you could provide separate functions to set and get values:
var _smallValue = const MyClass();
var _mediumValue = const MyClass();
var _largeValue = const MyClass();
MyClass mapSize(Size size) {
switch (size) {
case Size.small:
return _smallValue;
case Size.medium:
return _mediumValue;
case Size.large:
return _largeValue;
}
}
void setMappedSize(Size size, MyClass value) {
switch (size) {
case Size.small:
_smallValue = value;
break;
case Size.medium:
_mediumValue = value;
break;
case Size.large:
_largeValue = value;
break;
}
}
If you find that to be too verbose, and you just want callers to avoid returning null, you could wrap a Map internally:
const _defaultValue = const MyClass();
final _sizeMap = <Size, MyClass>{};
MyClass mapSize(Size size) => _sizeMap[size] ?? _defaultValue;
void setMappedSize(Size size, MyClass value) => _sizeMap[size] = value;

How to create generic class of type T, where T is Type variable?

I'm trying to create list of generic typed class from mixed-type list.
It works, but the problem is each Data instance type is dynamic.
class Data<T> {
final T value;
const Data(this.value);
}
final List<dynamic> bases = [...];
final List<Data> data = bases.map((b) {
return Data(b);
}).toList();
Attempt #2
class Data<T> {
final T value;
const Data(this.value);
}
final List<dynamic> bases = [...];
final List<Data> data = bases.map((b) {
final Type T = b.runtimeType;
return Data<T>(b);
}).toList(); // List<Data<dynamic>>
But it fails, due to: The name 'T' isn't a type so it can't be used as a type argument. Try correcting the name to an existing type, or defining a type named
Generic type parameters must be known statically (i.e., at compile-time). If you start off with a heterogeneous List<dynamic>, then the static type of each element is dynamic, and therefore when you construct a Data object from that element, it will be Data<dynamic>.
If your heterogeneous list has a limited number of types, you could do:
var data = <Data<dynamic>>[];
for (var b in bases) {
if (b is Foo) {
var d = Data(b); // `d` is of type `Data<Foo>`
data.add(d);
} else if (b is Bar) {
var d = Data(b); // `d` is of type `Data<Bar>`
data.add(d);
} else {
throw UnimplementedError('Unrecognized type: ${b.runtimeType}');
}
}
(Note that if you do data.add(Data(b)) in the above, the Data object will be constructed as Data<dynamic> because the generic type parameter will be inferred from data (of type List<Data<dynamic>>) instead of from b.)
Otherwise what you want isn't really possible.

How to pass a function onto a generic function parameter in Flutter?

For example, I have this function parameter:
class MyClass {
U Function<T, U>(T data) callback;
MyClass ({ this.callback }) : super();
}
var int Function(String value) func = (String value) => int.parse(value);
MyClass(callback: func); // error
The error is:
The argument type 'int Function(String)' can't be assigned to the
parameter type 'U Function<T, U>(T)'.
How can I make this work?
EDIT:
Based on Shubhamhackz's answer, I conclude that the only thing wrong with my code is that because the generics are on the variables and not on the function parameters, and the variables are created when the class is declared and instantiated. I should put the <T, U> on the class declaration itself, and not on the function variable declaration. So the class declaration becomes like this:
class MyClass<T, U> {
U Function(T data) callback;
MyClass ({ this.callback }) : super();
}
To declare a generic function parameter and pass it as argument. This is the way how I would implement it in dart.
typedef Callback<T,U> = U Function(T data);
void main() {
Callback<String,int> func = (String value) {
return int.parse(value);
};
MyClass(callback: func);
}
class MyClass {
Callback<String,int> callback;
MyClass ({ this.callback }) : super() {
print('MyClass Called');
}
}
Output :
MyClass Called

Deduced conflicting types without template

I'm trying to create a type that can store either an int, a double, or an uint, like so:
struct Value
{
/*...*/
Value& operator=(const int value) { /*...*/ }
Value& operator=(const double value) { /*...*/ }
Value& operator=(const uint value) { /*...*/ }
operator int() const { /*...*/ }
operator double() const { /*...*/ }
operator uint() const { /*...*/ }
}
I got errors about "deduced conflicting types" when I'm trying to use it. I read somewhere that "deduction guide" can help but it seems to require template. My type doesn't need template.
Is there a solution to use this Value type without the need to cast it in int,double or uint everytime?
Value v;
v=123;
// I would like to type:
std::clamp(v,0,1234); // error
// But I need to type:
std::clamp(int(v),0,1234); // ok
I also have the same kind of problem with operator (with different error messages)
int x=v+12;
I think I should add more operator overloading, but I don't found which one.
// I would like to type:
std::clamp(v,0,1234); // error
Try with
// .......VVVVV
std::clamp<int>(v, 0, 1234);
The problem is the signature of std::clamp() is
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
so if you call it without explicating T,
std::clamp(v, 0, 1234);
the template type T is deduced Value, from v, and int, from 0 and from 1234.
Given the conflicting types, you get an error.
If you explicit the template type
// .......VVVVV
std::clamp<int>(v, 0, 1234);
there is no more deduction, the compiler expect an int in first position so the operator int () is called over v.