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.
Related
This question already has an answer here:
Complex models with json_serializable - List<objects> not converting to map
(1 answer)
Closed 5 months ago.
Background
I'm trying to use the dart json_serializable package to encode/decode a custom type I'm writing/reading with Google Cloud Firestore, and I'm having an issue with the custom type having properties that are also custom types.
I'm storing a Habit which has two custom properties:
HabitFrequency is a combination of an integer, and an enum frequencyType that I want to store together as a map in Firestore.
HabitCompletion is a list of the type HabitCompletion, which contains a date, completion status, and an optional string value/numeric value. I want this to be stored as an array of maps.
Issue
Whenever I try to save, I get this error:
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: Instance of 'HabitFrequency'
I have the top-level class and both custom classes used as properties (HabitFrequency and HabitCompletion) listed as JsonSerializable, and they have the appropriate fromJson/toJson methods specified.
Question
Is there a different way I need to set these up before I run build_runner build to generate the right code to serialize these properly?
Code Examples
Here is my top-level class:
#JsonSerializable()
class Habit {
String? id;
String title;
#JsonKey(name: 'frequency')
HabitFrequency frequency;
List<HabitCompletion>? completions;
Habit({
this.id,
required this.title,
required this.frequency,
this.completions,
});
/// Connect the generated [_$PersonFromJson] function to the `fromJson`
/// factory.
factory Habit.fromJson(Map<String, dynamic> json) => _$HabitFromJson(json);
/// Connect the generated [_$PersonToJson] function to the `toJson` method.
Map<String, dynamic> toJson() => _$HabitToJson(this);
}
}
The Habit Frequency:
#JsonSerializable()
class HabitFrequency {
int amount;
HabitFrequencyType frequencyType;
HabitFrequency(this.amount, this.frequencyType);
/// Connect the generated [_$HabitFrequencyFromJson] function to the `fromJson`
/// factory.
factory HabitFrequency.fromJson(Map<String, dynamic> json) =>
_$HabitFrequencyFromJson(json);
/// Connect the generated [_$HabitFrequencyToJson] function to the `toJson` method.
Map<String, dynamic> toJson() => _$HabitFrequencyToJson(this);
}
And the Habit completion:
#JsonSerializable()
class HabitCompletion {
DateTime timestamp;
bool completion;
String? stringValue;
double? numericValue;
HabitCompletion(
{required this.timestamp,
required this.completion,
this.stringValue,
this.numericValue});
/// Connect the generated [_$PersonFromJson] function to the `fromJson`
/// factory.
factory HabitCompletion.fromJson(Map<String, dynamic> json) =>
_$HabitCompletionFromJson(json);
/// Connect the generated [_$PersonToJson] function to the `toJson` method.
Map<String, dynamic> toJson() => _$HabitCompletionToJson(this);
}
They each have one property that is just a simple enumeration.
If you want to see more of the code, here's a gist containing the file for this type (and the custom types used for the properties in questions), as well as the auto-generated code coming from json_serializable.
this can be resolved by setting explicitToJson on the JsonSerializable annotation for the Habit class as mentioned in your gist
#JsonSerializable(explicitToJson: true)
class Habit{
....
}
u should be getting syntax as some properties are not defined but u are using them in the constructor?
#JsonSerializable()
class Habit {
String? id;
String title;
#JsonKey(name: 'frequency')
HabitFrequency frequency;
List<HabitCompletion>? completions;
Habit({
this.id,
required this.title,
// not defined??????
this.description,
// not defined?????? //etc...
required this.userId,
this.attachedDashboardId,
this.attachedGoalId,
required this.startDate,
this.endDate,
this.isActive = true,
this.positivity = HabitPositivity.positive,
this.frequency,
this.completions,
});
/// Connect the generated [_$PersonFromJson] function to the `fromJson`
/// factory.
factory Habit.fromJson(Map<String, dynamic> json) => _$HabitFromJson(json);
/// Connect the generated [_$PersonToJson] function to the `toJson` method.
Map<String, dynamic> toJson() => _$HabitToJson(this);
}
}
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.
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 simple class like this:
class Restaurant{
final String id;
final String name;
List<Serving> servingList;
Restaurant({
required this.id,
required this.name,
this.servingList = [], // ERROR
});
}
By default I want an empty List for servingList and add Objects to this List later. But I get the error The default value of an optional parameter must be constant.
What do I need to do?
I appreciate every help, thanks!
Actually the answer is within the error. The default value should be constant.
class Restaurant{
final String id;
final String name;
List<Serving> servingList;
Restaurant({
required this.id,
required this.name,
this.servingList = const [], // ERROR
});
}
You need to add "const" keyword before the square brackets.
This is My model class. I am trying to get an http response and print it to my app.
At line 12 DataModel it's showing me this error
"Non-nullable instance field 'buttonStartingYpoint' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'."
Non-nullable instance field 'buttonHeight' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'
String buttonLetter;
int buttonHeight;
int buttonWidth;
int buttonStartingXpoint;
int buttonStartingYpoint;
DataModel(
{required this.buttonLetter,
required this.buttonHeight,
required this.buttonWidth,
required this.buttonStartingXpoint,
required this.buttonStartingYpoint});
DataModel.fromJson(Map<String, dynamic> json) {
buttonLetter = json['Button_letter'];
buttonHeight = json['Button_height'];
buttonWidth = json['Button_width'];
buttonStartingXpoint = json['Button_Starting_xpoint'];
buttonStartingYpoint = json['Button_Starting_ypoint'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['Button_letter'] = this.buttonLetter;
data['Button_height'] = this.buttonHeight;
data['Button_width'] = this.buttonWidth;
data['Button_Starting_xpoint'] = this.buttonStartingXpoint;
data['Button_Starting_ypoint'] = this.buttonStartingYpoint;
return data;
}
}
I tried to add late but then its showing error in both late and model. And I am new to flutter
You have to initialize them in the initializer list. When you get to the constructor body it's too late. Here's the fix:
DataModel.fromJson(Map<String, dynamic> json) :
buttonLetter: json['Button_letter'],
buttonHeight: json['Button_height'],
buttonWidth: json['Button_width'],
buttonStartingXpoint: json['Button_Starting_xpoint'],
buttonStartingYpoint: json['Button_Starting_ypoint'];
For reference: https://dart.dev/guides/language/language-tour#constructors