Flutter Class inherence - flutter

I'm having problems with super and sub classses contructors. I have a set of variables declared in the super class, and I call super on my subclasses, but then I cannot acess the superclass variables through the sub-classes.
As shown below, in my fromJson method, I cannot acess the userRating field in TennisAttributes.
abstract class UserSportAttributes {
int selfRating;
int userRating;
String description;
int gamesPlayed;
UserSportAttributes({
required this.selfRating,
required this.description,
this.gamesPlayed = 0,
this.userRating = 0,
});
Map<String, dynamic> toJson() => {
'SelfRating': selfRating,
'UserRating': userRating,
'Description': description,
};
static fromJson(Map<String, dynamic> json, String sportName) {
if (sportName == "Tennis") {
return TennisAttributes(
selfRating: json['SelfRating'],
userRating: json['UserRating'], //error
description: json['Description']);
}
---------
class TennisAttributes extends UserSportAttributes {
TennisAttributes({required int selfRating, required String description})
: super(selfRating: selfRating, description: description);
}

You need to have TennisAttributes constructor property, like
class TennisAttributes extends UserSportAttributes {
TennisAttributes({
required int selfRating,
required String description,
required int userRating, //this one
})...
You can also do
class TennisAttributes extends UserSportAttributes {
TennisAttributes({
required super.selfRating,
required super.description,
required super.userRating,
});
}

Related

How to set a default value dependent on the value of another attribute in an Entity / Model?

For example, every time I want to create a new entity or model, there is an attribute called global that I need to set to true or false depending on its other attribute called id :
If the id is 0, I want the global to be true
Entity :
class Folder extends Equatable {
const Folder({
required this.id,
required this.global /// here i want this.global = (id == 0)
});
final int id;
final bool global;
#override
List<Object> get props {
return [
id,
global,
];
}
}
Model :
class FolderModel extends Folder{
FolderModel ({
required this.id,
required this.global,
}) : super(
id: id,
global: global,
);
FolderModel copyWith({
int? id,
bool? global,
}) {
return FolderModel(
id: id ?? this.id,
global: global ?? this.global,
);
}
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'id': id});
result.addAll({'global': global});
return result;
}
factory FolderModel.fromMap(Map<String, dynamic> map) {
return FolderModel(
id: map['id']?.toInt() ?? 0,
global: map['global'] ?? false,
);
}
String toJson() => json.encode(toMap());
factory FolderModel.fromJson(String source) => FolderModel.fromMap(json.decode(source));
}
Where should I add that, or should I create a special function to read that value ? or just don't add anything and everything is logic out of these classes ?
The best way would be to make global a getter, since you don't use it anyway.
In case you do want the explicit option of having a global with an id, the simplest way to achieve this is:
class Folder {
const Folder({
required this.id,
bool? global
}) : global = (global ?? (id == 0));
final int id;
final bool global;
#override
String toString() {
return 'id=$id, global: $global';
}
}
void main() {
const configurable = Folder(id: 17, global: true);
const automaticGlobal = Folder(id: 0);
const automaticNonGlobal = Folder(id: 17);
print(configurable);
print(automaticGlobal);
print(automaticNonGlobal);
}
By making global a non-required named parameter, it is optional. If you set it, the value will be used, if you don't, the value will be calculated.
This will print:
id=17, global: true
id=0, global: true
id=17, global: false
I think you can try use factory constructor :
import 'package:equatable/equatable.dart';
class Folder extends Equatable {
const Folder({required this.id, required this.global});
final int id;
final bool global;
factory Folder.byId(int id, {bool? global}) {
return Folder(id: id, global: global ?? id == 0);
}
#override
List<Object> get props {
return [
id,
global,
];
}
}
IMO this will work for your issue:
class Folder extends Equatable {
Folder({required this.id, required this.global
}) {
global = (id == 0);
}
int id;
bool global;
#override
List<Object> get props {
return [
id,
global,
];
}
}

Flutter: Type "A" is not a sup type of type "B" even though B extends A

Trying to assign an instance of class NumberTrivia to an instance of class NumberTriviaModel but it is giving me the error:
The argument type 'NumberTrivia' can't be assigned to the parameter type 'NumberTriviaModel'.. So I tried to cast it but now I get an uncaught error. Reso code could assign NumberTrivia to NumberTriviaModel before. But it was an older dart version and it can't be done the same way now. Now the main question is how to do so in the newer dart version.
void main() {
NumberTrivia remoteTrivia = NumberTrivia(text : "Text", number : 5);
cacheNumberTrivia(remoteTrivia as NumberTriviaModel);
}
class NumberTrivia {
final String text;
final int number;
NumberTrivia({
required this.text,
required this.number,
});
}
class NumberTriviaModel extends NumberTrivia {
NumberTriviaModel({
required String text,
required int number,
}) : super(text: text, number: number);
factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
return NumberTriviaModel(
text: json['text'],
number: (json['number'] as num).toInt(),
);
}
Map<String, dynamic> toJson() {
return {
'text': text,
'number': number,
};
}
}
void cacheNumberTrivia(NumberTriviaModel triviaToCache) {
print(triviaToCache.number);
}
but it's giving me the following error
Uncaught Error: TypeError: Instance of 'NumberTrivia': type 'NumberTrivia' is not a subtype of type 'NumberTriviaModel'
Downcasting is not safe: the extending-object may have additional and thus missing properties when casting down. Which is why it results in an error.
As an alternative, one can use a special constructor for cloning / copying an object:
void main() {
NumberTrivia remoteTrivia = NumberTrivia(text: "Text", number: 5);
cacheNumberTrivia(NumberTriviaModel.copy(remoteTrivia));
}
class NumberTrivia {
final String text;
final int number;
NumberTrivia({
required this.text,
required this.number,
});
}
class NumberTriviaModel extends NumberTrivia {
NumberTriviaModel.copy(NumberTrivia nt) // HERE
: this(text: nt.text, number: nt.number);
NumberTriviaModel({
required String text,
required int number,
}) : super(text: text, number: number);
factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
return NumberTriviaModel(
text: json['text'],
number: (json['number'] as num).toInt(),
);
}
Map<String, dynamic> toJson() {
return {
'text': text,
'number': number,
};
}
}
void cacheNumberTrivia(NumberTriviaModel triviaToCache) {
print(triviaToCache.number);
}
Before Edit
Instance of 'NumberTrivia':
type 'NumberTrivia' is not a subtype of type 'NumberTriviaModel'
This means that NumberTrivia does not extend NumberTriviaModel.
Looking at the code, it is the other way around. NumberTriviaModel extends NumberTrivia, NumberTriviaModel is a sub-type NumberTrivia:
class NumberTriviaModel extends NumberTrivia {
! SUBTYPE ! TYPE
Your headline states Type "A" is not a sup type of type "B" even though A extends B. But the code does not reflect that.
Instead, the code can be described as Type "A" is not a sup type of type "B" BECAUSE B extends A.
The way you are calling the method is wrong. Here A extends B. If you want to access method of A you need to use instance of A. If you have added method in B and tried to access via instance of A, you can use it.
void main() {
NumberTriviaModel remoteTrivia = NumberTriviaModel(text : "Text", number : 5);
cacheNumberTrivia(remoteTrivia as NumberTriviaModel);
}
class NumberTrivia {
final String text;
final int number;
NumberTrivia({
required this.text,
required this.number,
});
}
class NumberTriviaModel extends NumberTrivia {
NumberTriviaModel({
required String text,
required int number,
}) : super(text: text, number: number);
factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
return NumberTriviaModel(
text: json['text'],
number: (json['number'] as num).toInt(),
);
}
Map<String, dynamic> toJson() {
return {
'text': text,
'number': number,
};
}
}
void cacheNumberTrivia(NumberTriviaModel triviaToCache) {
print(triviaToCache.number);
}

Super class doesn't have a zero argument constructor

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.

Have multiple types for one argument for a generic class in Dart

I want to have a custom class where I can pass the type of my generic argument
Is it possible to do this or I have to use the dynamic type ?
Is there a equivalent to this : Team|Site item;
Or I need to use this : dynamic item;
class Site {
String name;
int levels;
Site({
required this.name,
required this.levels,
});
}
class Team {
String name;
String description;
Team({
required this.name,
required this.description,
});
}
class CustomItem {
// Related line
Team|Site item;
CustomItem({
required this.item,
});
getName() {
print(item.name);
}
}
void main() {
final team = Team(name: 'dev', description: 'a team of dev');
final site = Site(name: 'paris', levels: 6);
final customTeam = CustomItem(item: team);
final customSite = CustomItem(item: site);
customTeam.getName();
customSite.getName();
}
As of Flutter 2.10.3, this feature does not exist yet but there are two workarounds.
If you only need this special handling for one class, #1 works fine but is hard to maintain.
#2 is a better solution -- easy to extend, reuse and understand.
1. Use dynamic property and convert the item type.
class CustomItem {
// Related line
dynamic item;
CustomItem({
required this.item,
});
String getName() {
switch (item.runtimeType) {
case Site:
return (item as Site).name;
case Team:
return (item as Team).name;
default:
return ''; // or throw error
}
}
}
Use abstract class with same property name.
abstract class ItemWithName {
String name;
ItemWithName({required this.name});
}
class Site extends ItemWithName {
int levels;
Site({
required name,
required this.levels,
}) : super(name: name);
}
class Team extends ItemWithName {
String description;
Team({
required name,
required this.description,
}) : super(name: name);
}
class CustomItem {
// Related line
ItemWithName item;
CustomItem({
required this.item,
});
String getName() {
return item.name;
}
}

How to give child class property a different type from the superclass in Dart?

I am in a situation where I'm working with entities and models.
Basically my entities describe how my data should look like, and my models extends those entities and have a function toMap to transform them into a json object.
So I have 2 entities, Car and Brand, and one of the Car properties is of type Brand.
class Car {
final String name;
final Brand brand;
const Car({
required this.name,
required this.brand,
});
}
class Brand {
final String label;
final String color;
const Brand({
required this.label,
required this.color,
});
}
Then I have 2 models CarModel and BrandModel, which extends respectively Car and Brand.
class CarModel extends Car {
CarModel({
required String name,
required BrandModel brand,
}) : super(
name: name,
brand: brand,
);
Map<String, dynamic> toMap() {
return {
'name': name,
'brand': brand,
};
}
}
class BrandModel extends Brand {
BrandModel({
required String label,
required String color,
}) : super(
label: label,
color: color,
);
Map<String, dynamic> toMap() {
return {
'label': label,
'color': color,
};
}
}
As you can see, in CarModel, I would like the brand property to have a type BrandModel.
However, it takes its superclass type.
So in the end, the brand property in my CarModel has a type of Brand.
My problem is that in the toMap function, I would like to use brand.toMap like so:
Map<String, dynamic> toMap() {
return {
'name': name,
'brand': brand.toMap(), // <= I want to use toMap() here, so I need brand to be of type BrandModel
};
}
Is there a way to force my child class to have a different type for a property inherited from the superclass?
Interesting problem. You are trying to achieve inheritance. So, what is happening when you are providing required BrandModel brand, in the constructor of CarModel which extends Car having final Brand brand; type of field. Brand get the necessary attributes/definition from BrandModel object, and it is the only reason that your are unable to call 'brand': brand.toMap(), inside the Map<String, dynamic> toMap() method bacause Brand class does not have this method.
Child has all the attributes/functions/definition of Parent but Parent does not possess all the properties of child.
SOLUTION
So, you need to put the toMap() in the Brand class. Here Abstraction comes in. At this stage, might be the toMap() method is unclear to you like what will it have inside the body, what will it map? Simply, declare it as abstract. Also, declare the class as abstract class. abstract class should have on abstract method.
Now, Polymorphism comes in. Inside the BrandModel class, the toMap() method will be an override method. Check the following tested code (complete example).
class Car {
final String name;
final Brand brand;
const Car({
required this.name,
required this.brand,
});
}
abstract class Brand {
final String label;
final String color;
const Brand({
required this.label,
required this.color,
});
Map<String, dynamic> toMap();
}
class CarModel extends Car {
CarModel({
required String name,
required BrandModel brand,
}) : super(
name: name,
brand: brand,
);
Map<String, dynamic> toMap() {
return {
'name': name,
'brand': brand.toMap(),
};
}
}
class BrandModel extends Brand {
BrandModel({
required String label,
required String color,
}) : super(
label: label,
color: color,
);
#override
Map<String, dynamic> toMap() {
return {
'label': label,
'color': color,
};
}
}
void main() {
BrandModel brand = BrandModel(label:'Test',color : 'white');
CarModel car = CarModel(name: 'T',brand : brand);
print(car.toMap());
}