I have a class called Player (player.dart) that has a field called isCaptain:
#JsonKey(name: "is_captain") #Default(false) bool isCaptain,
The line above produces the following in player.g.dart:
isCaptain: json['is_captain'] as bool,
When I create a Player object using Player.fromJson(playerJson) that's missing the is_captain key, isCaptain is set to null on the Player object instead of false as provided by #Default.
When I add defaultValue: false to the #JsonKey(...) as follows:
#JsonKey(name: "is_captain", defaultValue: false) #Default(false) bool isCaptain,
...the implementation in player.g.dart changes to:
isCaptain: json['is_captain'] as bool? ?? false,
Now everything works as expected and if is_captain is not present in the API response, isCaptain gets the default value of false.
I'm confused because freezed's documentation says that adding #Default automatically adds defaultValue to the #JsonKey(...) too but that doesn't seem to be the case here. What am I missing?
Freezed adds an implicit JsonKey with the default value.
Your issue is that you passed a custom JsonKey. In which case, your annotation takes over what Freezed auto-generates.
So by specifying a JsonKey, you basically removed the implicit default value added by Freezed.
Related
I'm using Freezed to generate my models. I want to ignore a specific key, so I'm using #JsonKey(ignore: true).
#freezed
class ObjectA with _$ObjectA {
const factory ObjectA({
#JsonKey(ignore: true)
List<ObjectB> ObjectBList,
}) = _ObjectA;
factory ObjectA.fromJson(Map<String, dynamic> json) => _$ObjectA(json);
}
When I try to run the build_runner, I'm getting the above exception:
The parameter ObjectBList of ObjectA. is non-nullable but is neither required nor marked with #Default
Why doesn't it been ignored?
The issue you are facing is with Dart syntax. When you declare parameters in constructors.
You can have a nullable parameter or a non-nullable parameter; this also apply to variables.
If you want the parameter you declared in the constructor to be non-nullable then it must either have a default value or the declared parameter must be prefixed with the required keyword.
You have three options:
Add the required keyword
required List<ObjectB> ObjectBList
Make it nullable
List<ObjectB>? ObjectBList
Give it a default value. With freezed this can be done with the #Default annotation
#Default([]) List<ObjectB> ObjectBList
I have a Job class which has a list of Tasks assigned to it. Everything is working fine, but I am trying to add a boolean field to mark a Job as complete. For now I am not checking all sub tasks. Just a simple check box that the Job is complete or not.
I have a data file that has predefined Jobs and Tasks in it. If I assign true or false to the complete variable in the data file, it all loads, the check boxes are populated, etc... but I can't change the value as it is hardcoded.
If I leave the value undefined I get a boolean expression must not be null error.
So in my Widget were I generate the list of Jobs (which can be pressed to show list of Tasks), I assume I need to initialize the complete variable before it renders the list. This Widget was a Stateless Widget before I added the boolean variable, I have changed it to Stateful as I am thinking I need to setState() to change the boolean variable. But since the Job is in data file this is where I am getting a bit stuck trying to assign or change the boolean variable.
Any pointers in what I am missing would be much appreciated.
class Job {
int jobID;
List<Task> task_list;
String imageURL;
String jobName;
bool complete;
Job({
this.jobId,
this.task_list,
this.imageURL,
this.jobName,
this.complete,
});
}
class Task{
int taskID;
String imageURL;
String name;
String type;
int taskTime;
Task({
this.taskID,
this.imageURL,
this.name,
this.type,
this.taskTime,
})
}
For your Job class constructor, could you change:
Job({
this.jobId,
this.task_list,
this.imageURL,
this.jobName,
this.complete,
});
to this:
Job({
this.jobId,
this.task_list,
this.imageURL,
this.jobName,
this.complete = false,
});
Giving complete a default value of false should avoid the must not be null error. I assume this is OK since a job shouldn't be done by default, correct?
by any chance is the data file generated with sqflite? if yes, the bool type field must be changed to an int field using 0 and 1 for representing the bool
If you want to see changes on the widget, you have to surround the declaration with the setState(() {complete = true});
I'm having trouble understanding how to type promote an object's field if it is nullable. Let's say I had the following Comment class and tried to access its one nullable field:
class Comment {
final String? text;
Comment(this.text);
}
void main() {
final comment = Comment("comment");
if (comment.text!= null) {
String text = comment!.text;
}
}
The Dart compiler would give me an error for trying to assign a nullable variable to a non-nullable variable. From what I've gathered from looking into this topic, it's impossible to have type promotion with instance variables because instance variables can be modified which can then break the sound null-safety. I've seen the Null assertion operator (!.) being used in these circumstances, however it doesn't seem to work with fields, only with methods.
With dart null-safety, how should I go about assigning a nullable field such as String? to a non-nullable variable (String). To the same effect, how should I go about passing a nullable field to a function that requires a non-null argument.
You can fix it in different ways:
Use local variable (recommended)
final local = comment.text; // <-- Local variable
if (local != null) {
String text = local;
}
Use ?. and provide a default value
String text = comment?.text ?? 'Default value';
Use Bang operator !
if (comment.text != null) {
String text = comment.text!; // <-- Bang operator
}
Note: Bang operator can result in a runtime error if the text was null.
I found this package that implements Optional for Dart: https://pub.dev/packages/optional/example
On the examples, it does things like this:
void filterExample() {
final hello = Optional.of('hello');
final world = Optional.of('world');
final name = Optional.of('harry');
for (var o in [hello, world, name]) {
final filtered = o.filter((v) => v.startsWith('h'));
print(filtered.isPresent);
} // prints "true", "false", "true"
}
But how do I force a variable to be Optional of some type? I wanted to have Optional<String>, Optional<int>, etc, but I'm forced to give a value in the beginning.
The closest I can think is
final anEmpty = Optional.ofNullable(null);
which is already in the example, but what is a Nullable? If I do like this, I cannot constraint the value to be a String or int, it can be changed to anything. I want to stick to strong typing while using Optional.
If this is not possible with this library, then how can I make my own simple Optional type that supports templates so I can have Optional<String>, Optional<int>, etc?
The behavior you are looking for (doing something if null and something else otherwise) can be achieved using the ?? operator.
This is basically a null check which equals the left hand if not null and right hand if null. Example: doSomething(myString ?? "Default value of my string");
Does this answer your question? Sorry, I haven't used that package and don't see a need to.
I have a class that I am creating that looks like this:
class Movie {
final String title, posterPath, overview;
Movie(this.title, this.posterPath, this.overview);
Movie.fromJson(Map json) {
title = json["title"];
posterPath = json["poster_path"];
overview = json['overview';
}
}
I am getting a warning that says that "The final variables 'overview', 'posterPath', & '1' more must be initialized. There are also warnings around each variable saying 'title' can't be used as a setter because it is final.
When I write the constructor using this syntax, the warnings go away:
Movie.fromJson(Map json)
: title = json["title"],
posterPath = json["poster_path"],
overview = json['overview'];
What exactly is going on here?
Dart objects must be fully initialized before anyone gets a reference to the new object. Since the body of a constructor can access this, the object needs to be initialized before entering the constructor body.
To do that, generative Dart constructors have an initializer list, looking similiar to C++, where you can initialize fields, including final fields, but you cannot access the object itself yet. The syntax:
Movie.fromJson(Map json)
: title = json["title"],
posterPath = json["poster_path"],
overview = json['overview'];
uses an initializer list (the list of assignments after the :) to initialize the final instance variables title, posterPath and overview.
The first constructor uses an "initializing formal" this.title to directly put the parameter into the field.
The constructor
Movie(this.title, this.posterPath, this.overview);
is effectively a shorthand for:
Movie(String title, String posterPath, String overview)
: this.title = title, this.posterPath = posterPath, this.overview = overview;
Your constructor can combine all of these and a body:
Movie(this.title, this.posterPath, String overview)
: this.overview = overview ?? "Default Overview!" {
if (title == null) throw ArgumentError.notNull("title");
}
(A const constructor cannot have a body, but it can have an initializer list with some restrictions on the allowed expressions to ensure that they can be evaluated at compile-time).
Dart separates properties initialization from the constructor body.
A constructor has 3 parts :
the name/parameters definition
properties initialization/super call/asserts
A body, similar to a function immediately run on construction
Both the initialization and body parts are optional.
final variables must be initialized on the first 2 parts. They cannot be initialized inside the body.
A full constructor will look like the following :
MyClass(int value)
: assert(value > 0),
property = value,
super();
{
print("Hello World");
}
The main purpose of this initializer part is for body-less constructors which allows const constructors, a dart specific feature. See How does the const constructor actually work? for more details on these.
I just found some documentation around this, & it seams that the second version with the : is what's called the "initializer list" which allows you to initialize instance variables before the constructor body runs.
There is more detail around this in the documentation here.