Super class doesn't have a zero argument constructor - flutter

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.

Related

Why I am getting Instance members can't be accessed from a factory constructor?

I am getting following errors:
Instance members can't be accessed from a factory constructor. (Documentation) Try removing the reference to the instance member.
The argument type 'List<Map<String, dynamic>>?' can't be assigned to the parameter type 'List<Vaccination>'. (Documentation)
at line _convertVaccinations(json['vaccinations'] as List<dynamic>));
Code:
final String name;
final String? notes;
final String type;
final List<Vaccination> vaccination;
final String? referenceId;
Pet(this.name, {this.notes, required this.type, required this.vaccination, this.referenceId});
factory Pet.fromJson(Map<String, dynamic> json) =>
Pet(
json['name'] as String,
notes: json['notes'] as String,
type: json['types'] as String,
referenceId: json['referenceId'] as String,
vaccination:
_convertVaccinations(json['vaccinations'] as List<dynamic>));
List<Map<String, dynamic>>? _convertVaccinations(List<dynamic>? vaccinations) {
if (vaccinations == null) {
return null;
} else {
final vaccinationMap = <Map<String, dynamic>>[];
for (var element in vaccinations) {
vaccinationMap.add(element.toJson);
}
return vaccinationMap;
}
}
Factory and instance member error:
Well it is because factory Pet.fromJson(...) is a factory constructor, and the class method _convertVaccinations(...) is an instance member.
If you make _convertVaccinations(...) static, it will accept the use of it in the factory constructor.
Argument error:
vaccination is of type List<Vaccination> and the method _convertVaccination(...) returns either null or List<Map<String, dynamic>>
In other words, you cannot assign null to List<T> unless it says List<T>? and the class Vaccination is not a Map<String, dynamic>
Maybe you want to do something like final List<Vaccination>? vaccinations; OR return <Vaccination>[] instead of null if vaccinations == null.
So you'd probably want to do write _convertVaccinations(...) as:
static List<Vaccination>? _convertVaccination(List<dynamic>? vaccinations) {
return vaccinations?.map((e) => Vaccination.fromJson(e as Map<String,dynamic>).toList();
}
or:
static List<Vaccination> _convertVaccination(List<dynamic>? vaccinations) {
return vaccinations?.map((e) => Vaccination.fromJson(e as Map<String,dynamic>).toList() ?? <Vaccination>[];
}
Side note: Maybe you have more methods that you haven't presented here. Because it looks a bit wonky when your Pet.fromJson(...) use a element.toJson down the call stack.

Flutter: Generics assignment is not working on extended types of T

Why this generic classes throw errors? Since T extends DataModel, it should allow to assign instances of DataModel but isn't.
class TestClass<T extends DataModel> {
List<T> variablesList = [];
late T variable;
void run() {
variablesList.add(DataModel(id: '', name: '', value: '')); // error
variable = DataModel(id: '', name: '', value: ''); // error
}
void run2() {
variablesList.add(DateModelImpl(id: '', name: '', value: '')); // error
variable = DateModelImpl(id: '', name: '', value: ''); // error
}
}
class DataModel {
final String id;
final String name;
final String value;
DataModel({
required this.id,
required this.name,
required this.value,
});
}
class DateModelImpl extends DataModel {
DateModelImpl({
required String id,
required String name,
required String value,
}) : super(id: id, name: name, value: value);
}
Error:
The argument type 'DataModel' can't be assigned to the parameter type 'T'.
The argument type 'DateModelImpl' can't be assigned to the parameter type 'T'.
Since T extends DataModel, it should allow to assign instances of DataModel but isn't.
No, it should not. It's pretty simple: A Pack<Wolf> is something like Pack<T extends Animal>, but that does not mean you can just add any animal to a pack of wolves, nor can you add any derived class of Animal that is not wolf (lets say Sheep) to a Pack<Wolf>. Only another Wolf can be added. If you want to be able to add any animal, maybe generics is not the way to go and you should rather use interfaces or base classes.

Null Safety in Dart

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,
});

Cannot create named constructor in Flutter

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.

flutter lint argument type rules usage

How i can control witch rules i want to apply to my code.
I added lint package but argument type rules are happening to much.
I have a lot dynamic data from API calls and i tried disable theme but it didn't work.
Can i decide which rules i want to apply?
There are rules that make the code more efficient like the const rule, adding lint to active project can be headache so i think if it is worth it?
The current rules i try to disable:
argument_type_not_assignable: false
invalid_assignment: false
Article.fromMap throw The argument type 'dynamic' can't be assigned to the parameter type 'String'.dartargument_type_not_assignable
class Article {
String id;
String image;
String title;
Map contentEditor;
List<Map<String, dynamic>> teams;
List<Map<String, dynamic>> leagues;
String content;
String updateDate;
Article({
this.id,
this.image,
this.title,
this.contentEditor,
this.teams,
this.leagues,
this.content,
this.updateDate,
});
String getDateString() {
DateFormat formatter = DateFormat("MM/dd/yyyy");
return formatter.format(DateTime.parse(this.updateDate));
}
String getTimeString() {
final dateTime = DateTime.parse(this.updateDate).toLocal();
return DateFormat.Hm().format(dateTime);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'image': image,
'title': title,
'contentEditorId': contentEditor,
'teams': teams,
'leagues': leagues,
'content': content,
'updateDate': updateDate
};
}
factory Article.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Article(
id: map['id'],
image: map['image'],
title: map['title'],
contentEditor: map['contentEditor'],
teams: List<Map<String, dynamic>>.from(map['teams']?.map((x) => x)),
leagues: List<Map<String, dynamic>>.from(map['leagues']?.map((x) => x)),
content: map['content'],
updateDate: map['updateDate'],
);
}
String toJson() => json.encode(toMap());
factory Article.fromJson(Map json) => Article.fromMap(json);
}
I like more this approach
dynamic getPoints(dynamic property) {
if (property == null) {
return 0;
}
return property.won * 3 + property.draw;
}
than this:
int getPoints(Map<String, int> property) {
if (property == null) {
return 0;
}
return property["won"] * 3 + property["draw"];
}
You can disable the strict type casting by following:
analyzer:
language:
strict-casts: false
strict-casts is a language mode of stricter type checks.
There are 3 modes.You can find more on this at Enabling stricter type checks
argument_type_not_assignable and invalid_assignment are errors, not lints.
You can make the Dart analyzer ignore them by modifying your analysis_options.yaml file to have:
analyzer:
errors:
argument_type_not_assignable: ignore
invalid_assignment: ignore
However, it does not make any sense to disable those rules. Even though the analyzer would no longer complain about violations, violations would still be illegal and ultimately would generate compilation errors. What would you expect int x = 'string'; to do?