Correct way to handle variable initialisation in flutter? Late or make it nullable? - flutter

Hello good folk of stack overflow, teaching myself flutter and have come across null safety.
I believe I 'understand' the difference between making a variable nullable and telling dart I will initialise later. But is there a correct way to do it?
In what instance would you choose late over making a variable nullable?
Consider the small class I have below, I've chosen to use the late method (admittedly I don't really know why) Would you chose this same method or should I be making the variables nullable?
Thanks!
class Quote{
late String text;
late String author;
Quote(String text, String author){
this.text = text;
this.author=author;
}
}

There is also a third solution and forth (done in two ways) solution here.
You can add a non-null default value to the field.
String text = '';
But I would go here with initializing the fields directly in the constructor argument list.
class Quote {
String text;
String author;
Quote(this.text, this.author);
}
...or using a constructor initializer list:
class Quote {
String text;
String author;
Quote(String text, String author)
: text = text,
author = author;
}
Using the forth solution (second and third code snippet) you can also make those fields final and the constructor constant.
Lastly, quoting the documentation Classes - Language Tour | Dart:
Instance variables can be final, in which case they must be set exactly once. Initialize final, non-late instance variables at declaration, using a constructor parameter, or using a constructor’s initializer list (...)
If you need to assign the value of a final instance variable after the constructor body starts, you can use one of the following:
Use a factory constructor.
Use late final, but be careful: a late final without an initializer adds a setter to the API.

You mentioned the answer in the question too. If your business logic requires to check if a value is null use nullable variable and if you want some definite value then use late. For example you may not know if you will get a response from an api as expected. So better to define as nullable.

Related

Flutter: Late Initializing a "widget-dot" Class-level Variable in a Stateful Widget -- What's Going on?

Consider the following class level property inside the state class of a stateful widget:
int myInt = widget.int;
Android Studio informs that: "The instance member 'widget' can't be accessed in an initializer."
(I understand what this error means).
So then if we add the late keyword, it appears to be fine:
late int myInt = widget.int;
However... this is surprising to me that I’m allowed to do all that in one line — I thought that late variables had to be not set/set as null:
late int myInt;
... and then assign inside onInit.
Since I didnt declare when to assign it, I dont know when the assignment takes place.
The question is:
Is the one-liner “late int myInt = widget.int;” exactly equivalent to assigning it myself in the initState method?
The late keyword in Dart has 2 distinct usages, melt into a single keyword.
The first usage, e.g. late int i:
This usage is well-known: delay assigning a value until later. This is most commonly used to make a field non-nullable, even though you might not have the value right away. I'm sure you are familiar with this usage.
The second usage, e.g. late int i = 0:
This is to delay the value calculation until the field is being accessed. This is useful when the value is expensive to calculate, so you might want to delay its calculation until it's needed for the first time. It's stated on the official documentation:
When you do this, the initializer becomes lazy. Instead of running it
as soon as the instance is constructed, it is deferred and run lazily
the first time the field is accessed. In other words, it works exactly
like an initializer on a top-level variable or static field. This can
be handy when the initialization expression is costly and may not be
needed.
So basically, depends on whether you assign a value right away (on the same line), Dart will decide which of the 2 usages you are using. If you write late int i it will be the first usage, if you write late int i = 0 or late int i = calculateValue() it will be the second usage: delay the calculation until when the field i is accessed for the first time. It's like lateinit in Kotlin or lazy in Swift.
Now back to your case. By assigning a value on the same line as the late keyword, you are using the second usage, basically "lazy init" until the field is accessed for the first time. By the time it's accessed, this class would've been instantiated, so (by that time) you are allowed to use the this keyword.
In the first case Android studio throws that error because int myInt requires a value the moment you are declaring it.
In that particular moment, in the Statefull widget state, the widget object is not accessible.
In the second case:
late int myInt = widget.int;
That is a valid one line declaration and assignment of the variable, but the effect is a bit different from the onInit alternative.
The late keyword works in a lazy way.
Instead of running as soon as the instance is built, it run the first time the field is used. In that moment the widget object will be accessible.
Take a look at the answer to this question, it can be helpful: here
Assigning the value inside the onInit guarantees that the value is actually assigned only once when the widget is initialized.
widget.xxx corresponds to the value of xxx of the instance of a widget, ie the widget once it exists.
So when you use widget.xxx in initialisation of the widget, the var xxx does not exists.
That's why the dart compiler tell you The instance member 'widget' can't be accessed in an initializer .
By adding the keyword late in front of the declaration, you tell the compiler that this variable will be defined later.
But be careful, it will then really have to be defined later (in initState for example) and in any case before any use.
This error comes from the fact that dart is now a null safety aware language.
That is to say a language that strives to ensure that no variabales can have a null value. This is for reasons of code quality and greater code security.

Dart null variable creation

I just started learning Dart. There is some problem when I am creating a null variable. When I type
String someVar;
it throws an error but when i type
dynamic someVar;
it doesn't. I tried doing alternative methods mentioned in Dart's doc but even those methods do not seem to work until i have a dynamic var type. Can anyone tell me what is it?
Dart has a feature called null safety, so when you define something which might have a null value, you have to use ?.
In your code above, try something like String? somevar;
If you are going to initialize the variable later, then define late String somevar;
If you have null safety on String someVar; will give you an error because it cannot be null.
If u want a variable to be a nullable string, use String? someVar.
Try avoiding the use of dynamic because it will allow for the type to not be fixed. This can potentially cause the accidental assigning of a value with a type you are not expecting.

Flutter & Dart, Constructor before the properties

I was watching a course and the instructor always write the constructor first, then the properties, in java, c# and other OOP languages the properties are written first. Is there a reason to write de constructor first?
class MyClass{
MyClass({this.name, this.age});
final String name;
final int age;
}
It is a personal preference, you can continue writing the properties first
There's no specific rule stating the constructor must be declared before the properties. It's a personal preference of the instructor's, I guess, and it shouldn't affect the program.

How to create multiple setters

I have a variable which I want to give the option to call the setter with multiple types, but I would set the variable with one type of course. For example, if I have a variable called list, I want to be able to call the setter using a single list item i.e. a string and then in the setter method I would add the string to the end of the list. Another way to set the variable would be through an actual list, and the setter, I would just set the list to the list passed in.
Here's an example of what I would like to do, but the second time I call the list setter method, I get an error saying The name 'list' is already defined:
List<String> _list = [];
List<String> get list => list;
set list(List<String> newList) {
_list = newList;
}
set list(String newListItem) {
_list = [..._list, newListItem];
}
As Remi mentioned, it is not possible to do so. I would argue that it would be best not to do so because it changes the expectation of the contract of the setter. That is, you expect a setter to replace the value of a variable. Having a setter that appends to it is counter-intuitive and will lead to confusion for other developers.
Generally, for the maintainability of a code base, it's better to have an explicit interface to your class's behavior than to provide terse shortcuts.
That is not possible.
In Dart you can have only one setter per variable.
You will need to rename one of your setters to use a different name.

setOnAction restrictions and datepicker class

I'm writing an application involved in time difference, there is something I don't get it,
final DatePicker datePick=new DatePicker();
LocalDate time;
Label label=new Label();
datePick.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent e){
time=datePick.getValue();//doesn't work, must 'LocalDate time=datePick.getValue();
label.setText(time);//doesn't work must label.setText("Time now:"+time)
}
});
It gives an error saying "Local variables referenced from an inner class must be final or effectively fine", anyway, all I did, declare 'time' variable inside setOnAction() and it worked, so what was wrong? and 'label.setText(time);' if I don't write some text inside, like 'label.setText("Time now: "+ time);' ,won't work too, that's weird, yet why?
Java restricts local variables you access from a anonymus class/lambda expression to those that never change their values after assignment (i.e. thoser the compiler wouldn't complain about, if you declared them as final). If you could write to the local variable from a anonymus class this would violate this condition. It's not possible to write to local variables of the containing method from anonymus classes for this reason.
You should declare variables in the scope where they are needed anyways.
As for label.setText(time);:
Label.setText is a method with a String parameter. Since String is not a supertype of LocalDate, a simple assignment won't work. You need to convert the date to a String. String concatenation does this automatically, i.e. the + operator receiving a String s as first operand and a Object o as second operand outputs the concatenation of s and Objects.toString(o).
You could simply convert to string yourself:
label.setText(Objects.toString(time, ""));
Or use a more sophisticated method for convertion to string.