Recently dart realeased the new feature called null safety. I have a little bit confusion in how to create constructors with named parameters. When I follow the use of curly braces, I get an error.
Here is my code:
void main() {
}
class Student {
String name = '';
num age = 0;
List<num> marks = [];
Student({
this.name,
this.age,
this.marks
});
void printStudentDetails() {
print('Student Name: ' + this.name);
}
}
And here is the error that I get:
Since the properties of your class are not nullable (type is String instead of String? for example), you either need to add required to the properties in the constructor, or provide a default value (which seems to be what you want to do).
Student({
this.name = '',
this.age = 0,
this.marks = [],
});
You can now probably also make your properties final:
final String name;
final num age;
final List<num> marks;
Or, with the required keyword:
Student({
required this.name,
required this.age,
required this.marks,
});
Related
I am trying to use a base class in a data model.
I have a base class of Symptoms and I want to add Headache as an extension of Symptom
Right now this is my code
import 'package:cloud_firestore/cloud_firestore.dart';
class Symptom {
final String id;
final String path;
final DateTime startTime;
final String? type;
String get patientId => path.split('/')[1];
Symptom({
required this.id,
required this.path,
required this.startTime,
this.type,
});
factory Symptom.fromJson(
String id,
String path,
Map<String, Object?> doc,
) {
final start = doc['startTime'] as Timestamp;
return Symptom(
id: id,
path: path,
startTime: start.toDate(),
type: doc['type'] as String?,
);
}
Map<String, Object?> toJson() {
return {
'startTime': startTime,
'type': type,
};
}
}
class Headache extends Symptom {
int? intensity;
DateTime? endTime;
List<String> symptoms;
List<String> effects;
Map<String, int> medications;
bool? medsEffective;
String? notes;
Duration? get duration => endTime?.difference(startTime);
double get hours {
final inHours = duration?.inHours ?? 0;
final inMins = duration?.inMinutes ?? 0;
if (inHours < 1) {
return inMins / 60;
} else {
return inHours.toDouble();
}
}
Headache({
this.intensity,
this.medsEffective = false,
this.endTime,
this.notes,
this.symptoms = const [],
this.effects = const [],
this.medications = const {},
});
factory Headache.fromJson(
String id,
String path,
Map<String, Object?> doc,
) {
final start = doc['startTime'] as Timestamp;
final end = doc['endTime'] as Timestamp?;
final tempMeds = doc['medications'] as Map<String, dynamic>;
return Headache(
intensity: doc['intensity'] as int?,
notes: doc['notes'] as String?,
endTime: end?.toDate(),
medsEffective: (doc['medsEffective'] as bool?),
symptoms:
(doc['symptoms'] as List).map((item) => item as String).toList(),
effects: (doc['effects'] as List).map((item) => item as String).toList(),
// ignore: unnecessary_lambdas
medications: tempMeds.map((key, value) => MapEntry(key, value)),
);
}
Map<String, Object?> toJson() {
return {
'intensity': intensity,
'notes': notes,
'endTime': endTime,
'symptoms': symptoms,
'medsEffective': medsEffective,
'effects': effects,
'medications': medications,
};
}
}
When I try to do
Headache({
this.intensity,
this.medsEffective = false,
this.endTime,
this.notes,
this.symptoms = const [],
this.effects = const [],
this.medications = const {},
});
It gives me an error
The superclass 'Symptom' doesn't have a zero argument constructor.
Try declaring a zero argument constructor in 'Symptom', or explicitly invoking a different constructor in 'Symptom'
I am wondering how to fix this but also why is this error coming up and why does it need a zero argument constructor. Is extending a base class of a data model a good practice or should I shy away from this and make an entirely separate data model for headaches separate from symptoms?
If you don't explicitly call the super constructor in the constructor of child class, the compiler will try to implicitly call the default constructor of the super class (which, in this case, would be Symptom()).
Since you've defined a Symptom constructor that takes several arguments, there is no automatic default constructor for the class, so the Headache constructor is unable to initialize the fields of the super class.
You can resolve this by having your Headache constructor take additional arguments to initialize the super class:
Headache({
this.intensity,
this.medsEffective = false,
this.endTime,
this.notes,
this.symptoms = const [],
this.effects = const [],
this.medications = const {},
required String id,
required String path,
required DateTime startTime,
String? type,
}): super(
id: id,
path: path,
startTime: startTime,
type: type,
);
Refer to Michael's answer regarding the error you are receiving, but I want to comment on the how you are structuring your objects.
I would make a generic Illness class, with the name of the illness (e.g. "Headache") as a property so that you don't need to predefine every possible type of illness. Then I would suggest that Symptom should have a property field that holds an Illness object. If you want to constrain the types of illnesses, you can make the illness an enum that defines all possible illness types.
If you decide you have a good reason for directly creating classes for each specific illness, create an abstract Illness class and Headache should inherit from it so that all of the illnesses are interchangeable throughout the application.
I have a class with multiple named constructors. All of them have a set of defined parameters and for now I have to rewrite them all over again when I add a new named constructor.
Is there a way to reduce the boilerplate of the below code to not enter all parameters over again ?
class Person {
final String name;
final String lastName;
final String login;
final String anotherValue;
final String address;
final int age;
final String _text;
const Person.sayHello(
this.name,
this.age,
this.lastName,
this.login,
this.anotherValue,
this.address,
) : _text = "Hello";
const Person.sayBye(
this.name,
this.age,
this.lastName,
this.login,
this.anotherValue,
this.address,
) : _text = "Bye";
void readText() {
debugPrint(_text);
}
}
I tried with abstract or extend method but I don't find a proper way to do it.
It may not be what you want, but we can divide the code about named constructor like this.
class Person {
const Person(
this.name,
this.age,
this.lastName,
this.login,
this.anotherValue,
this.address,
this._personText,
);
final String name;
final String lastName;
final String login;
final String anotherValue;
final String address;
final int age;
final PersonText _personText;
void readText() {
debugPrint(_personText.text);
}
}
class PersonText {
const PersonText.sayHello() : text = 'Hello';
const PersonText.sayBye() : text = 'Bye';
final String text;
}
You can also replace it like this.
enum PersonText {
hello,
bye,
}
extension PersonTextHelper on PersonText {
String get text {
switch (this) {
case PersonText.hello:
return 'Hello';
case PersonText.bye:
return 'Bye';
}
}
}
I am following a Flutter tutorial that has such a following code, but code doesn't work on my computer, and I don't know how to fix it:
import 'package:flutter/foundation.dart';
class CartItem {
final String id;
CartItem({
#required this.id,
});
}
But I get such these errors:
The parameter 'id' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.dartmissing_default_value_for_parameter
{String id}
The latest dart version now supports sound null safety. The tutorial must be using an old version.
To indicate that a variable might have the value null, just add ? to its type declaration:
class CartItem {
final String? id = null;
CartItem({
this.id,
});
}
or
class CartItem {
final String? id;
CartItem({
this.id,
});
}
You have a few options depending on your own project...
Option1: Make id nullable, you can keep #required or remove it.
class CartItem {
final String? id;
CartItem({
this.id,
});
}
Option2: Give a default (non-null) value to id
class CartItem {
final String id;
CartItem({
this.id="",
});
}
more in this link
You can just replace #required this.id with required this.id.
class City {
int id;
String name;
String imageUrl;
bool isPopular;
City(
{required this.id,
required this.name,
required this.imageUrl,
required this.isPopular});
}
I am new to Flutter.
I am creating a named constructor to work with flutter Models. But for some reason it is showing an error:
class ImageModel {
int id;
String url;
String title;
// constructor
ImageModel(this.id, this.url, this.title);
// named constructor
ImageModel.fromJSON(Map<String, dynamic> parsedJson) {
id = parsedJson['id'];
url = parsedJson['url'];
title = parsedJson['title'];
}
}
Error:
Non-nullable instance field 'url' must be initialized.
Try adding an initializer expression, or add a field initializer
in this constructor, or mark it 'late'.dartnot_initialized_non_nullable_instance_field
I read the documentation, and found this solution, not sure why this is required at this place. I know its use case, but should not this work without this ?
class ImageModel {
late int id; // refactor
late String url; // refactor
late String title; // refactor
.
.
.
You have used incorrect syntax for the named constructor.
Instead of
ImageModel.fromJSON(Map<String, dynamic> parsedJson) {
id = parsedJson['id'];
url = parsedJson['url'];
title = parsedJson['title'];
}
it must be
ImageModel.fromJSON(Map<String, dynamic> parsedJson) :
id = parsedJson['id'],
url = parsedJson['url'],
title = parsedJson['title'];
The object is initialized after colon(:) in named constructor and curly brackets({}) are then used if you want to perform some task after initialization of object. Since you directly used {} after named constructor, it created an empty object for you and hence all parameters were null which you were trying to initialize in the function body. That's why this issue was solved after using 'late' keyword.
do you like this way
factory ImageModel.fromJson(Map<String, dynamic> json) {
return ImageModel(
json["id"],
json["url"],
json["title"],
);
}
And i prefer
class ImageModel {
int id;
String url;
String title;
// constructor
ImageModel({
required this.id,
required this.url,
required this.title,
});
factory ImageModel.fromJson(Map<String, dynamic> json) {
return ImageModel(
id: json["id"],
url: json["url"],
title: json["title"],
);
}
}
The Dart compiler complains because of its "null safety" feature. This means, that variable types like int or String must be initialised. This is been checked for at compile time. You can either add required in the constructor
ImageModel({
required this.id,
required this.url,
required this.title,
});
so that you cannot call the constructor without initialising the fields or let the compiler know that you will take care of the initialisation later by adding the late keyword (as you did). Of course you can also initialise the variables with some default values, if there are such
int id = 0;
String url = "https://www.example.com/default.jpg";
String title = "Default Text";
but that seems to be more of a corner case.
I have a class as follows:
class QueueItem {
final String name;
final Type type;
final String imageUrl;
const QueueItem({
#required this.name,
#required this.type,
#required this.imageUrl,
});
}
Where Type is an enum. This does not work, so I would like an alternative which ensures that only certain strings can be chosen.
Your question are complicated since you don't state how you are going to use the class and what kind of check you want. For example, if you just want some runtime checking you can do:
class QueueItem {
final String name;
final String type;
final String imageUrl;
QueueItem({
#required this.name,
#required this.type,
#required this.imageUrl,
}) {
if (!['Christmas', 'New Years'].contains(type)) {
throw Exception('$type is not a valid input!');
}
}
}
This solution will work but does not have any statically guarantees so it can be difficult to use it in code since it is not any statically checks for which value is valid for type.
If type is restricted to a subset of values I think the best is to use a enum. You are saying that is not going to work but here is an example:
enum Type { christmas, newYears }
class QueueItem {
final String name;
final String type;
final String imageUrl;
factory QueueItem({
#required String name,
#required Type type,
#required String imageUrl,
}) {
switch (type) {
case Type.christmas :
return QueueItem._(name, 'Christmas', imageUrl);
case Type.newYears :
return QueueItem._(name, 'New Years', imageUrl);
default :
throw Exception('Unhandled enum value: $type');
}
}
QueueItem._(this.name, this.type, this.imageUrl);
}
This solution makes it more clear what intended value you want to use as argument. The signature of the factory constructor restricts the valid values but the value itself in the class are now a String.
I have also made a more advance solution based on same principle which outsource the logic into another class:
class ValidStringValue {
final String value;
const ValidStringValue._(this.value);
static ValidStringValue christmas = const ValidStringValue._('Christmas');
static ValidStringValue newYears = const ValidStringValue._('New Years');
static List<ValidStringValue> values = [christmas, newYears];
factory ValidStringValue.fromString(String input) =>
values.firstWhere((element) => element.value == input);
}
class QueueItem {
final String name;
final String type;
final String imageUrl;
QueueItem({
#required this.name,
#required ValidStringValue inputType,
#required this.imageUrl,
}) : type = inputType.value;
}
This solution does both have a statically way to assign a valid value and have ValidStringValue.fromString to create a value from a given input (which will crash if the input is not one of the accepted values).