Late variable in Dart - flutter

I'm new to use Websocket in Flutter.
So I was reading some post (https://blog.logrocket.com/using-websockets-flutter/) to understand how to use websocket, and I have some question.
The guy in this post declare _etheWebsocket,_btcWebsocket variables as late as below.
class CoinbaseProvider {
late final WebSocketChannel _ethWebsocket;
late final WebSocketChannel _btcWebsocket;
CoinbaseProvider()
: _ethWebsocket = WebSocketChannel.connect(
Uri.parse('wss://ws-feed.pro.coinbase.com'),
),
_btcWebsocket = WebSocketChannel.connect(
Uri.parse('wss://ws-feed.pro.coinbase.com'),
);
But why not just declare without late like below??
What is the reason have to declare WebsocketChannel as late.
class CoinbaseProvider {
final WebSocketChannel _ethWebsocket = WebSocketChannel.connect(
Uri.parse('wss://ws-feed.pro.coinbase.com'),
);
final WebSocketChannel _btcWebsocket = WebSocketChannel.connect(
Uri.parse('wss://ws-feed.pro.coinbase.com'),
);
CoinbaseProvider();

Short explanation
You declaration looks better, since in your context you have the values to the variables _ethWebsocket and _btcWebsocket
But why? a dive into late and null-safety features
What's null-safety in Dart? This is a recent released feature (2021) that in summary it prevent us, developers, to create variables of a type that can be implicity null, then avoiding runtime errors as TypeError: can't read property '...' of null
But in summary, unlike before null-safety, now:
All variables can't be null implicity by default!
Show me the code
Before null-safety:
This snippet below is a valid statement before null-safety, this variable can be a String 'hi!' or null
String myVariable; // null by default
After null-safety:
The same snippet now is a invalid statement, the variable should be initialized because her can't be null
String myVariable; // Error!
To declare a variable that can be null by default, as like before the null-safety update you should write as:
String? myVariable; // Note the '?' after the Type
But what if you want to declare variable without the value and without explicitly declaring that it can be null?
The answer is late!
late is just a Dart language feature to allow you say to the compiler: "Hey, I don't have the value of this variable right now, but I promise you I'll set her somewhere else before using it, so her can't be null"
late String myVariable;
print(myVariable); // Throws an LateInitializationError instead of printing 'null'
Conclusion
Given these features and the null-safety use them to avoid wasting time with null pointer exceptions and improve your code typing.
So there's no right or better way, all depends on the context.

For the exact example you used I would say they are identical, and your version would be better. My guess it that they made it like that, is to allow adding additional constructors, maybe like
CoinbaseProvider.customUrl(String eth, String btc)
: _ethWebsocket = WebSocketChannel.connect(
Uri.parse(eth),
),
_btcWebsocket = WebSocketChannel.connect(
Uri.parse(btc),
);

Related

Dart/Flutter : Why should we AVOID public late final fields without initializers?

https://dart.dev/guides/language/effective-dart/design#avoid-public-late-final-fields-without-initializers
AVOID public late final fields without initializers.
Unlike other final fields, a late final field without an initializer
does define a setter. If that field is public, then the setter is
public. This is rarely what you want. Fields are usually marked late
so that they can be initialized internally at some point in the
instance’s lifetime, often inside the constructor body.
Unless you do want users to call the setter, it’s better to pick one
of the following solutions:
Don’t use late. Use late, but initialize the late field at its
declaration. Use late, but make the late field private and define a
public getter for it.
The above explanation is abstract and I have no concrete image of what kind of risk this rule envisions.
I would be grateful if you could give me a hint as to how to think.
The risk is that you accidental try to assign a value twice, which will result in an error.
late final String a;
void someMethod() {
a = "a";
a = "b";
}
The above code compiles perfectly fine and is valid code because of the late but leads to a crash.
As for the suggested solutions
Don’t use late.
Use late, but initialize the late field at its declaration.
final String a = "b";
// or
late final String a = "b";
void someMethod() {
a = "a";
a = "b";
}
This makes it that the above code doesn't even compile, making it sure that the crash doesn't happen.
Late modifier means a variable's value is not known during declaration but will definitely get initialized and not null when it's accessed.
When we declare a variable as final, it means that it will only assigned once. Therefore, all the final fields has to be initialized either at declaration or inside the class constructor.
Given above facts, for one to declare a late final variable as public would probably a mistake. One should either pass the value to final field as class constructor parameter, or declare the late final field as private and initialize it internally.
Let see the example why late final field should not be made public
class Coffee {
late final String temperature; // public field as there's no prefix underscore, e.g. _temperature
// based on description from the guide, a setter will be created automatically for public late final
set temperature(String val) => temperature = val;
// again, do you think you would want above?
// as it's final field, it means it should only be initialized once!
// The creation of the setter for late final (public field) does not make too much sense
// Therefore usage late final as public field is rarely what you will want
}
Not sure if it's true, but I think this advice is to enforce better architecture. If field is final then you can set it value only once and almost always you need to initialize it from within the class it belongs to.
But if setter for final field is public then people might attempt to set it value from elsewhere AFTER it is already initialized, and this will lead to an error.
UPD: two sections above there is this advice, that basically summarizes my point:
Objects shouldn’t generally expose more state than they need to.
...and setter is not needed after final field has been initialized.

What is the difference between using `late` keyword before the variable type or using `?` mark after the variable type it in the Flutter?

I think in new Dart rules the variables can not be declared/initialized as null. So we must put a late keyword before the variable type like below:
late String id;
Or a ? mark after the variable type like below:
String? id;
Are these two equal Or there are some differences?
A nullable variable does not need to be initialized before it can be used.
It is initialized as null by default:
void main() {
String? word;
print(word); // prints null
}
The keyword late can be used to mark variables that will be initialized later, i.e. not when they are declared but when they are accessed. This also means that we can have non-nullable instance fields that are initialized later:
class ExampleState extends State {
late final String word; // non-nullable
#override
void initState() {
super.initState();
// print(word) here would throw a runtime error
word = 'Hello';
}
}
Accessing a word before it is initialized will throw a runtime error.
when you use late keyword you can't let variable uninitialized when you called it with ? allows to you let variable uninitialized when you create it and call it
Null safety rules doesn't mean you can't use null. You can, but you should indicate that a variable might have the value null, by add "?" to its type declaration.
By use keyword late you indicate that variable has a non-nullable type, is not initialized yet, but will be initialized later.
Exception is thrown if try to acсess value of late variable before initialization.

Flutter null-safety conditionals in object methods

I'm just working through this whole null-safety mode with my Flutter project and unsure what the difference is with ? and ! in calls to object methods.
For example, the hint was to add a ! conditional. Here's an example I have right now, and I'm unsure if this should be a ? or a ! at the findNbr!.replaceAll().
Future checkItem({String? findNbr}) async {
int? x = int.tryParse(findNbr!.replaceAll('-', ''));
...
Does this mean replaceAll() will not run if findNbr is null?
Or should it be a ? instead? findNbr?.replaceAll()
EDIT: I just noticed I cannot use findNbr?, it's telling String? can't be assigned parameter String.
Or does it mean I say it's not null and run it anyway?
For your information, I have not come close to running my app yet so I have no idea if it even works. But I figure I better know what it's doing before get too much more done. I'm still in the process of converting everything and there's 75-100 dart files. I'm not sure I get the point of it all to be honest, because I just add ? to everything, so its all nullable anyway.
Future checkItem({String? findNbr}) async {
int? x = int.tryParse(findNbr!.replaceAll('-', ''));
...
Does this mean replaceAll() will not run if findNbr is null?
Correct. If findNbr is null, then findNbr! will throw a runtime exception. That would be bad, especially since checkItem's function signature advertises that findNbr is allowed to be null, and therefore it would violate callers' expectations.
Or should it be a ? instead? findNbr?.replaceAll()
EDIT: I just noticed I cannot use findNbr?, it's telling String? can't be assigned parameter String.
You can't use findNbr?.replaceAll(...) because if findNbr is null, then it would be invoking int.tryParse(null), but int.tryParse is not allowed to take a null argument.
What you need to do is one of:
Make findNbr no longer optional:
Future checkItem({required String findNbr}) async {
int? x = int.tryParse(findNbr.replaceAll('-', ''));
...
Allow findNbr to be optional but have a non-null default value:
Future checkItem({String findNbr = ''}) async {
int? x = int.tryParse(findNbr.replaceAll('-', ''));
...
Allow findNbr to be optional but explicitly decide what to do if it is null. For example:
Future checkItem({String? findNbr}) async {
int? x = findNbr == null ? null : int.tryParse(findNbr.replaceAll('-', ''));
...
I'm not sure I get the point of it all to be honest, because I just add ? to everything, so its all nullable anyway.
If you blindly add ? to all types and add ! to all variables, then yes, null-safety would be pointless: doing that would give you the same behavior as Dart before null-safety.
The point of null-safety is to prevent things that shouldn't be null from ever being null. You could have written such code before, but without null-safety, that meant performing runtime null checks (e.g. assert(x != null);, if (x != null) { ... }, or relying on a null-pointer-exception to crash the program if null was used where it wasn't expected). Null-safety means that such checks now can be done at build-time by static analysis, which means that errors can be caught earlier and more completely. Furthermore, whereas previously functions needed to explicitly document whether arguments and return values were allowed to be null (and inadequate or incorrect documentation could be a source of errors), now they're self-documenting in that regard. It's just like using int foo(String s) versus dynamic foo(dynamic s); using strong types catches errors earlier and better describes the function's contract.
I recommend reading Understanding Null Safety if you haven't already done so.
I would like to advice you to use the ! operator, also the called bang operator, as little as possible. You should only use this operator when the dart analyser is wrong and you know for 100% that the value will never be null.
Below is an example of where the dart analyser would be wrong and you should use the bang operator.
// We have a class dog with a nullable name.
class Dog {
String? name;
Dog({this.name});
}
void main() {
// We create a dog without a name.
final dog = Dog();
// We assign the dog a name.
dog.name = 'George';
// The dart analyser will show an error because it can't know if the
// name of the object is not null.
//
// Will throw: `A value of type 'String?' can't be assigned to a
// variable of type 'String'`.
String myDogsName = dog.name;
// To avoid this, you should use the bang operator because you `know` it
// is not null.
String myDogsName = dog.name!;
}
The ? operator simply tells Dart that the value can be null. So every time you want to place a ? operator, ask yourself, can this value ever be null?
The null safety features in Dart are mainly created for helping the developer remember when a value can be null. Dart will now simply tell you when you made a variable nullable in order to force null checks or default values for example.

How to check 'late' variable is initialized in Dart

In kotlin we can check if the 'late' type variables are initialized like below
lateinit var file: File
if (this::file.isInitialized) { ... }
Is it possible to do something similar to this in Dart..?
Unfortunately this is not possible.
From the docs:
AVOID late variables if you need to check whether they are initialized.
Dart offers no way to tell if a late variable has been initialized or
assigned to. If you access it, it either immediately runs the
initializer (if it has one) or throws an exception. Sometimes you have
some state that’s lazily initialized where late might be a good fit,
but you also need to be able to tell if the initialization has
happened yet.
Although you could detect initialization by storing the state in a
late variable and having a separate boolean field that tracks whether
the variable has been set, that’s redundant because Dart internally
maintains the initialized status of the late variable. Instead, it’s
usually clearer to make the variable non-late and nullable. Then you
can see if the variable has been initialized by checking for null.
Of course, if null is a valid initialized value for the variable, then
it probably does make sense to have a separate boolean field.
https://dart.dev/guides/language/effective-dart/usage#avoid-late-variables-if-you-need-to-check-whether-they-are-initialized
Some tips I came up with from advice of different dart maintainers, and my self-analysis:
late usage tips:
Do not use late modifier on variables if you are going to check them for initialization later.
Do not use late modifier for public-facing variables, only for private variables (prefixed with _). Responsibility of initialization should not be delegated to API users. EDIT: as Irhn mentioned, this rule makes sense for late final variables only with no initializer expression, they should not be public. Otherwise there are valid use cases for exposing late variables. Please see his descriptive comment!
Do make sure to initialize late variables in all constructors, exiting and emerging ones.
Do be cautious when initializing a late variable inside unreachable code scenarios. Examples:
late variable initialized in if clause but there's no initialization in else, and vice-versa.
Some control-flow short-circuit/early-exit preventing execution to reach the line where late variable is initialized.
Please point out any errors/additions to this.
Enjoy!
Sources:
eernstg's take
Hixie's take
lrhn's take
leafpetersen's final verdict as of 2021 10 22
Effective Dart
Self-analysis on how to approach this with some common-sense.
You can create a Late class and use extensions like below:
import 'dart:async';
import 'package:flutter/foundation.dart';
class Late<T> {
ValueNotifier<bool> _initialization = ValueNotifier(false);
late T _val;
Late([T? value]) {
if (value != null) {
this.val = value;
}
}
get isInitialized {
return _initialization.value;
}
T get val => _val;
set val(T val) => this
.._initialization.value = true
.._val = val;
}
extension LateExtension<T> on T {
Late<T> get late => Late<T>();
}
extension ExtLate on Late {
Future<bool> get wait {
Completer<bool> completer = Completer();
this._initialization.addListener(() async {
completer.complete(this._initialization.value);
});
return completer.future;
}
}
Create late variables with isInitialized property:
var lateString = "".late;
var lateInt = 0.late;
//or
Late<String> typedLateString = Late();
Late<int> typedLateInt = Late();
and use like this:
print(lateString.isInitialized)
print(lateString.val)
lateString.val = "initializing here";
Even you can wait for initialization with this class:
Late<String> lateVariable = Late();
lateTest() async {
if(!lateVariable.isInitialized) {
await lateVariable.wait;
}
//use lateVariable here, after initialization.
}
Someone may kill you if they encounter it down the road, but you can wrap it in a try/catch/finally to do the detection. I like it better than a separate boolean.
We have an instance where a widget is disposed if it fails to load and contains a late controller that populates on load. The dispose fails as the controller is null, but this is the only case where the controller can be null. We wrapped the dispose in a try catch to handle this case.
Use nullable instead of late:
File? file;
File myFile;
if (file == null) {
file = File();
}
myFile = file!;
Note the exclamation mark in myFile = file!; This converts File? to File.
I'm using boolean variable when I initiliaze late varible.
My case is :
I'm using audio player and I need streams in one dart file.
I'm sharing my code block this methodology easily implement with global boolean variables to projects.
My problem was the exception i got from dispose method when user open and close the page quickly

Dart / flutter test setup with null safety

Update: It was a documentation bug, fixed with: https://github.com/dart-lang/test/pull/1471
According to the docs/examples for the test package (https://pub.dev/packages/test) this test case should work and not trigger warnings. However it does:
The non-nullable local variable 'b' must be assigned before it can be used.
Try giving it an initializer expression, or ensure that it's assigned on every execution path.dart(not_assigned_potentially_non_nullable_local_variable)
Marking the variable as late works, but I want to check that I'm not missing something before I file a bug saying that the docs are wrong. =)
import 'package:test/test.dart';
void main() {
String b;
setUp(() {
b = 'test';
});
group('foo', () {
test('bar', () {
print(b);
});
});
}
You can use late keyword.
ref: https://dart.dev/guides/language/language-tour#late-variables
With Null Safety you have to specifically declare the the variable can be null or you have to initialize it. In this case you may have seen they have also initialized the strings before test. Or You can declare the variable nullable by using
String? b;