Is it possible to automatically initialize fields from a parent class in the constructor?
I get the syntax error:
Could not match parameter initializer 'this.name' with any field
class Type {
String name;
}
class Language extends Type {
String id;
Language(this.name) {
While your case is common, at this time the dart language spec specifically says:
Executing an initializing formal this.id causes the field id of the immediately surrounding class to be assigned the value of the corresponding actual parameter.
This essentially tells us that this.variable notation, in the constructor arguments, will only work on variables in the immediate class and not any parent classes. There are a couple of solutions available: The first is to assign it within the constructor's body:
class Type {
String name;
}
class Language extends Type {
String id;
Language(name) {
this.name = name;
}
}
Alternatively, if we can change the parent class to have a constructor which will initialize the variable then we can use the initializer list in the child class:
class Type {
String name;
Type();
Type.withName(this.name);
}
class Language extends Type {
String id;
Language(name) : super.withName(name);
}
This is assuming there's some reason that the default constructor for Type doesn't automatically initialize name so we created the 2nd named constructor instead.
Related
Here's a little fun class:
abstract class Concept {
late Enum option;
String get name => option.name;
}
and you might implement it like this:
enum FeeOption {
fast,
standard,
slow,
}
class FastFeeRate extends Concept {
FeeOption option = FeeOption.fast;
}
print(FastFeeRate().name); // 'fast'
but then you get an error:
FastFeeRate.option=' ('void Function(FeeOption)') isn't a valid override of 'Concept.option=' ('void Function(Enum)').
So, how do you specify a variable as any kind of enum, not Enum itself?
Your class Concept has a mutable (late, but that doesn't matter) field with type Enum. That means it has a setter named option= with an argument type of Enum.
The subclass FastFeeRate is a subclass. It has another field (your class has two fields!) also named option, which has a setter with an argument type of FastFeeRate.
That's not a valid override. The subclass setter must accept all arguments that the superclass setter does, but it doesn't accept all Enum values.
What you might have intended to do is:
abstract class Concept<T extends Enum> {
T option;
Concept(this.option);
String get name => option.name;
}
class FastFeeRate extends Concept<FeeOption> {
FastFeeRate() : super(FeeOption.fast);
}
or
abstract class Concept<T extends Enum> {
abstract T option;
String get name => option.name;
}
class FastFeeRate extends Concept<FeeOption> {
FastFeeRate option = FeeOption.fast;
}
depending on whether you want to define the field in the superclass or the subclass (but make sure to only define a concrete field in one of them).
When I compile the following code:
class Student {
int id;
Student() {
this.id = 12345;
}
}
void main() {
var student1 = new Student();
}
I get the following error:
Error: Field 'id' should be initialized because its type 'int' doesn't
allow null.
But why do I get this error? I did initialize id in the constructor!
In Dart, the creation of objects are split into two phases:
Initialization of all values.
Execution of constructor body.
So when you are running code inside the constructor body (between the {...} in the constructor definition) then all class defined variables must have been provided a default value that is valid for the type of variable.
In your case, the variable is typed int but are not provided a default value. In Dart, all variable will by default be set to null in case of no other value provided. But since int is a non-nullable type it does not allow null to be a value and the compiler are therefore giving you the error.
The solution are to provide a value before the constructor is running. You can do that like this:
class Student {
int id;
Student() : id = 12345;
}
Or:
class Student {
int id = 12345;
Student(); // The constructor can in theory just be removed here
}
In case you cannot define a value as part of the initialization phase, you can (but should be prevented if possible) mark the variable as late which makes it so you promise, the Dart compiler, that you are going to provide a value for the variable before the first time you are trying to read from that variable:
class Student {
late int id;
Student() {
this.id = 12345;
}
}
In case you are trying to read from id before it have been provided a value, the program will crash with a LateInitializationError at runtime.
And at last, you can set the type to be a nullable type, like int?, to allow the variable to have a default value of null. But doing so will require you to check for null when you are trying to do something with the value in a context where null is not allowed:
class Student {
int? id;
Student() {
this.id = 12345;
}
}
I have a class with a nullable property. I would like to make a superclass that overrides that property with a non nullable one
so
class Example {
String? name;
}
class NamedExample extends Example {
#override
String name;
}
Is there some way to do that? if not how is this goal conventionally accomplished.
I basically want two identical classes except one of them always has a property while it is optional in another.
This is a place for the covariant keyword. Normally it does not make sense to override a parameter's type with its subtype and it is invalid to do so. This keyword tells the analyzer this is intentional. It can be added in either the super or subclass.
Subclass:
class Example {
String? name;
}
class NamedExample extends Example {
#override
covariant String name;
NamedExample(this.name);
}
Superclass:
class Example {
covariant String? name;
}
class NamedExample extends Example {
#override
String name;
NamedExample(this.name);
}
The reason why you can't override the String? name member with String name is because it can violate the contract of the setter in the base class and therefore could be unsafe. The base class advertises that:
var example = Example();
example.name = null;
is legal. However, if example instead is an instance of NamedExample, the example.name = null assignment would no longer be legal. The covariant keyword disables this safety check and trusts that you will never perform such an assignment in practice.
In general, you should avoid overriding fields.
You could safely have the override if your classes expose only a getter. Both of the following examples would be legal and safe:
class Example {
String? _optionalName;
String? get name => _optionalName;
}
class NamedExample extends Example {
NamedExample(this._requiredName);
String _requiredName;
#override
String get name => _requiredName;
}
or
class Example {
Example([this.name]);
final String? name;
}
class NamedExample extends Example {
NamedExample(this.name);
#override
final String name;
}
Why doesn't constructor syntactic sugar for setting fields work when importing fields from a mixin?
mixin Nameable {
var name = '';
}
class Person with Nameable {
Person(this.name);
}
Fails with error-message 'name' isn't a field in the enclosing class.
This is ok though
mixin Nameable {
var name = '';
}
class Person with Nameable {
Person(String name) {
this.name = name;
}
}
Report as a bug?
Initializing formals, and initializer list entries, in generative constructors can only initialize fields which are declared in the same class.
A field introduced by a mixin, like here, is not declared in the class. It's mixed into a mixin application class, Object with Namable, which becomes the superclass of Person.
Assigning to the field in the body of a constructor is just normal assignment, not initialization. It wouldn't work if you wanted the field to be final.
I am trying to create a base class for my models but I am struggling with the error The name 'cls' isn't a type so it can't be used as a type argument.. So, how can I pass the object's constructor to the Hive.box method?
import 'package:hive/hive.dart';
class AppModel {
#HiveField(0)
int id;
#HiveField(1)
DateTime createdAt;
#HiveField(2)
DateTime updatedAt;
save() async {
final Type cls = this.runtimeType;
// The name 'cls' isn't a type so it can't be used as a type argument.
final Box box = await Hive.openBox<cls>(cls.toString());
await box.put(this.id, this);
return this;
}
}
#HiveType(typeId: 0)
class UserModel extends AppModel {
#HiveField(3)
String email;
#HiveField(4)
String displayName;
}
void main() {
final UserModel user = UserModel()
..email = 'user#domain.com'
..displayName = 'john doe';
user.save().then(() {
print('saved');
});
}
Dart does not have a way to refer to the dynamic type of this (a "self type").
The way such things are often handled is to have a self-type as type argument, so:
class AppModel<T extends AppModel> {
save() async {
final Box box = await Hive.openBox<T>(T.toString());
await box.put(this.id, this as T);
return this;
}
...
and then ensure that each subclass tells the superclass what type it is:
class UserModel extends AppModel<UserModel> {
...
}
(or, if you expect to subclass UserModel eventually:
class UserModel<T extends UserModel> extends AppModel<T> {
...
}
so that a subclass can still pass its type through).
You are also talking about constructors, and for that there is no easy solution.
Dart's type parameters are types, not classes. You cannot access static members or constructors from a type variable, and there is also no other way to pass a class around.
The only way you can have something call a constructor that it doesn't refer to statically, is to wrap the constructor call in a function and pass that function.
(I can't see how you need the constructor here).