Prevent class properties to be null when instantiating a Dart class - dart

Assuming I have this Dart class:
class Stock {
int id;
String externalCode;
String internalCode;
String name;
double quantity;
}
When I create a new instance of this object like Stock item = new Stock(); all the properties are null.
I know this is a Dart specific behavior, but when sending such objects to an API, since most backend languages like C#, Java etc. don't have nullable primitives exceptions occur when parsing to a corresponding model class.
What is the simplest approach to prevent int, double and bool properties of being null (set them to 0, 0.0 and false respectively) when instantiating a Dart class?
Since many classes might have a lot of properties, a hardwired instantiation like Stock item = new Stock(id: 0, quantity: 0 /*...and so on... */); it's out of the question.
Many thanks!

If you want a default value for members in a class you can just assign each member to a value in the class definition:
class Stock {
int id = 0;
String externalCode = "";
String internalCode = "";
String name = "";
double quantity = 0.0;
}
Alternative, you can also give default values to optional parameters like:
class Stock {
int id;
String externalCode;
String internalCode;
String name;
double quantity;
Stock(
{this.id = 0,
this.externalCode = '',
this.internalCode = '',
this.name = '',
this.quantity = 0.0});
}

Related

Initializing members in a Constructor

Let's assume a simple class like this
class MyClass {
String aString;
DateTime aDate;
MyClass({this.aString = '', this.aDate = DateTime.now()});
}
This code does not work because optional parameters must be constant. I do not want aDate be nullable.
I can do this:
class MyClass {
String aString;
DateTime aDate;
MyClass({this.aString = ''}) : this.aDate = DateTime.now();
}
This is accepted by the compiler but I cannot set aDate when constructing an instance of MyClass like so:
var c = MyClass(aString: 'bla bla', aDate: DateTime(2021, 9, 20));
Instead it seams I have to write:
var c = MyClass(aString: 'bla bla');
c.aDate = DateTime(2021, 9, 20);
Is there really no better way to initialize complex objects with optional named parameters in constructors with null safety turned on? This is just a simplified sample. In reality, I have classes with lots of complex objects that I get from a remote server. The method shown above forces me to write hundreds of lines of assignment statements.
You can use the following:
class MyClass {
String aString;
DateTime aDate;
MyClass({this.aString = '', DateTime? aDate}) : aDate = aDate ?? DateTime.now();
}
You were on the right track, you just needed to combine the two methods you provided above. This uses an optional named parameter, which must be nullable, and only assigns it to this.aDate when it's not null using the ?? operator. If it is null, it uses your desired default value of DateTime.now().

How do I initialize non-nullable members in a constructor body?

I've created my class in Dart this way, but I'm getting the Non-nullable instance field 'text' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'. I would like to know if there's a way to do it in a 'Python' style where this kind of class creation is possible, thank you in advance.
class Lexer {
String _text;
int _pos;
String _current_char;
Lexer(String text) {
this._text = text;
this._pos = -1;
this._current_char = '';
this.advance();
}
void advance() {
this._pos++;
this._current_char = this._pos < this._text.length ? this._text[this._pos] : '';
}
}
class Lexer {
String _text;
int _pos;
String _current_char;
This declares several members with type String. Since they are declared as String and not as String?, these members are non-nullable; they are not allowed to ever be null. (This is part of the new null-safety feature from Dart 2.12.)
Dart initializes objects in two phases. When the constructor's body runs, Dart expects all member variables to already be initialized. Because your members are non-nullable and haven't been initialized to non-null values yet, this is an error. The error message explains what you can do:
Non-nullable instance field 'text' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Use initializer expressions. This means using an initializer list:
Lexer(String text)
: _text = text,
_pos = -1,
_current_char = '' {
advance();
}
Note that if you're initializing members with a construction parameter of the same name, you can use shorthand:
Lexer(this._text)
: _pos = -1,
_current_char = '' {
advance();
}
Adding field initializers. This means initializing members inline in the class declaration.
class Lexer {
String _text = '';
int _pos = -1,
String _current_char = '';
Marking your members as late. This means that you promise that the variables will be initialized before anything attempts to use them.
class Lexer {
late String _text;
late int _pos,
late String _current_char;
Making your members nullable, which allows them to be implicitly null by default:
class Lexer {
String? _text;
int? _pos,
String? _current_char;
However, that will require that all accesses explicitly check that the members aren't null before using them.
You also might want to read: Dart assigning to variable right away or in constructor?

Checking a nullable property after a null-conditional check of parent object

Appreciate there have been some question close to what I am asking but not quite like here. I have been checking the ?. operator and I came upon the following scenario. Situation is as follows:
internal class Dog
{
public int? Age { get; set; }
}
Checks in main code were as follows:
Dog d2 = new Dog() { Age = 10 };
int age1 = d2.Age.Value; // compiles okay
int age2 = d2?.Age.Value; // CS0266
I would like to know why the code line with age3 is requesting an explicit cast. d2.Age being type int? and Age.Value being type int doesn't vary between the two usages.
Once you use the null-condicional operator, the resulting value can be null. that's why it can never be int.
What you need is:
int age2 = (d2?.Age).Value;

Writing to mutable property for a struct record is not allowed in F#. Why?

When I have the following code:
[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}
let john = { FirstName = "John"; LastName = "Connor"}
john.FirstName <- "Sarah";
The compiler complains that "A value must be mutable in order to mutate the contents". However when I remove the Struct attribute it works fine. Why is that so ?
This protects you from a gotcha that used to plague the C# world a few years back: structs are passed by value.
Note that the red squiggly (if you're in IDE) appears not under FirstName, but under john. The compiler complains not about changing the value of john.FirstName, but about changing the value of john itself.
With non-structs, there is an important distinction between the reference and the referenced object:
Both the reference and the object itself can be mutable. So that you can either mutate the reference (i.e. make it point to a different object), or you can mutate the object (i.e. change the contents of its fields).
With structs, however, this distinction does not exist, because there is no reference:
This means that when you mutate john.FirstName, you also mutate john itself. They are one and the same.
Therefore, in order to perform this mutation, you need to declare john itself as mutable too:
[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}
let mutable john = { FirstName = "John"; LastName = "Connor"}
john.FirstName <- "Sarah" // <-- works fine now
For further illustration, try this in C#:
struct Person
{
public string FirstName;
public string LastName;
}
class SomeClass
{
public Person Person { get; } = new Person { FirstName = "John", LastName = "Smith" };
}
class Program
{
static void Main( string[] args )
{
var c = new SomeClass();
c.Person.FirstName = "Jack";
}
}
The IDE will helpfully underline c.Person and tell you that you "Cannot modify the return value of 'SomeClass.Person' because it is not a variable".
Why is that? Every time you write c.Person, that is translated into calling the property getter, which is just like another method that returns you a Person. But because Person is passed by value, that returned Person is going to be a different Person every time. The getter cannot return you references to the same object, because there can be no references to a struct. And therefore, any changes you make to this returned value will not be reflected in the original Person that lives inside SomeClass.
Before this helpful compiler error existed, a lot of people would do this:
c.Person.FirstName = "Jack"; // Why the F doesn't it change? Must be compiler bug!
I clearly remember answering this question almost daily. Those were the days! :-)

Dart: How to bind to variables annotated with int via Web UI?

What is the best practice in Dart when dealing with classes as data records?
To Elaborate: When writing an app, it is likely that a class for a table row will be created. As in
class Item { int itemid, String itemName, double score }
Item item = new Item();
This allows compile time catching of any typos etc. in Dart. (Unlike using a class that relies on NoSuchMethod.)
It will also need a corresponding string structure to bind to the HTML such as
<input id="itemname" type="text" bind-value="itemEdit.itemName">
So the Dart would be:
class ItemEdit { String itemId, String itemName, String score }
ItemEdit itemEdit = new ItemEdit();
Next we need a way to get from one to the other, so we add a method to Item
fromStrings(ItemEdit ie) {
itemid = ie.itemId == null ? null : int.parse(ie.itemId);
itemName = ie.ItemName;
score = ie.score == null ? null : double.parse(ie.score);
}
And the other way around:
toStrings(ItemEdit ie) {
ie.itemid = itemId == null ? '' : ie.itemId.toString();
ie. itemName = itemName == null ? '' : itemname; // Web_ui objects to nulls
ie.score = score == null ? null : score.toStringAsFixed(2);
}
Also, we get jason data from a database, so we need to add another method to Item:
fromJson(final String j) {
Map v = JSON.parse(j);
itemid = v['itemid'];
itemname = v['itemname'];
score = v['score'];
}
And we need to be able to revert to default values:
setDefaults() { itemId = 0; itemName = "New item"; score = 0; }
This verbosity gets me feeling like I am writing COBOL again!
There is something fundamental missing here - either in my understanding, or in the Dart/WebUI libraries.
What I would like to write is something like
class Item extends DataRecord {
int itemid = 0,
String itemName = 'New item',
double score = 0.0;
}
Then, without further coding, to be able to write code such as
item.toStrings();
...
item.fromStrings();
...
item.fromJson(json);
...
item.setDefaults(); // results in {0,'New item',0.0}
And to be able to write in the HTML:
value="{{item.strings.score}}"
If this was possible, it would be quicker, simpler, clearer, and less error prone than the code I am writing at the moment.
(Full disclosure, this answer is written with the assumption that at least one bug will be fixed. See below)
Three suggestions that might help.
Use named constructors to parse and create objects.
Take advantage of toJson() when encoding to JSON.
Use bind-value-as-number from Web UI
1) Named constructors
import 'dart:json' as json;
class Item {
int itemid;
String itemName;
double score;
Item.fromJson(String json) {
Map data = json.parse(json);
itemid = data['itemid'];
itemName = data['itemName'];
score = data['score'];
}
}
2) Encoding to JSON
The dart:json library has a stringify function to turn an object into a JSON string. If the algorithm encounters an object that is not a string, null, number, boolean, or collection of those, it will call toJson() on that object and expect something that is JSON-encodable.
class Item {
int itemid;
String itemName;
double score;
Map toJson() {
return {'itemid':itemid, 'itemName':itemName, 'score':score};
}
}
3) Now, having said that, sounds like you want to easily bind to HTML fields and get primitive values back, not just strings. Try this:
<input type="number" min="1" bind-value-as-number="myInt" />
(Note, there seems to be a bug with this functionality. See https://github.com/dart-lang/web-ui/issues/317)
(from https://groups.google.com/a/dartlang.org/forum/#!topic/web-ui/8JEAA5OxJOc)
Just found a way to perhaps help a little in the this situation:
class obj {
int gapX;
String get gapXStr => gapX.toString();
set gapXStr(String s) => gapX = int.Parse(s);
...
Now, from the HTML you can use, for example
bind-value="gapXStr"
and in code you can use
x += ob.gapX;

Resources