How to check 'late' variable is initialized in Dart - flutter

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

Related

Isolates in Flutter - Where can I place the isolates inside my Flutter code

I have some confusion regarding how Isolates can be used inside a Flutter application.
If we go through the documentation, It is said that functions that you pass inside the isolates should only be declared as top-level functions. Does that mean we cannot declare them inside a class ?
I created a class TestIsolate inside my lib/business_logic/bloc folder.
class TestIsolate {
Future<void> handle(int _m) async {
final response = ReceivePort();
await Isolate.spawn(_isolate, response.sendPort);
final sendPort = await response.first as SendPort;
final answer = ReceivePort();
sendPort.send([_m, answer.sendPort]);
await answer.first.then((p) {
log(p);
});
}
static void _isolate(SendPort _initialReplyTo) {
final port = ReceivePort();
_initialReplyTo.send(port.sendPort);
port.listen((message) {
final data = message[0] as int;
final send = message[1] as SendPort;
send.send(_syncHandle(data));
});
}
}
Future<String> _syncHandle(int data) async {
return 'done - $data';
}
I then called await TestIsolate.handle(15) upon an onTap event from my presentation layer which worked like a charm.
Am I doing this correctly ? If yes, can we call handle() placed inside TestIsolate class as a top-level function?
Any help would be really appreciated!
What you are doing is correct. The source you reference (which I will point out is an article, not documentation) says:
The function passed to the isolate spawn() must be a top-level function *(a function that is not within the boundary of a class) or a static method.
You are spawning an isolate with an entry point of _isolate(), which is a static method. So, according to your source, that is ok.
However, it may be the case that your source is outdated. According to the changelog for Dart 2.15:
Allow closures both in inter-isolate messages as well as as entrypoints in Isolate.spawn(<entrypoint>, ...) calls. Closures and their enclosing context may need to be copied in this process. The enclosing context is - as with normal messages - verified to only contain objects that are sendable.
Note of caution: The Dart VM's current representation of enclosing variables in closures can make closures hang on to more variables than strictly needed. Using such closures in inter-isolate communication can therefore lead to copying of larger transitive object graphs. If the extended transitive closure includes objects that are illegal to send, the sending will fail.
It would appear that closure (and non-static method) arguments to Isolate.spawn() were introduced after your source article was written.
As the changelog cautions though, you do want to be cognizant of the memory copying that will occur.
Further references:
Is DartDocs about the entry of Isolate.spawn wrong or something?

Dart: how do I initialize a class such that one of its properties is filled upon initialization via a function that returns a future?

I have a file called database.dart. In it, I have a string property called currentUsername. This is the currently logged in user's username. I want this class to call the getUsernameFS() function only once and then be able to reuse this string for the rest of the class's existence inside its other functions. How do I accomplish this?
The code below gives an error: Error: A value of type 'Future<String>' can't be assigned to a variable of type 'String'.
class Database {
late String currentUsername = getUsernameFS(); //ERROR IS HERE
Future<String> getUsernameFS() async {...}
String someFunction() {...//some function that uses currentUsername//...}
}
A direct (and perhaps naive) approach would be to add an asynchronous initialization step to initialize an instance of your class:
class Database {
late String currentUsername;
Future<void> initialize() async {
currentUsername = await getUsernameFS();
}
}
However, that's potentially error-prone since it creates more work for callers, and callers could accidentally neglect to call (or neglect to wait for) initialize, and there's no way to enforce that at compile-time.
Instead, I'd recommend a couple of other options:
Make your member variable a Future instead:
late Future<String> currentUsername = getUsernameFS();
This has the advantage of safely avoiding accidental errors from callers who neglect to explicitly call an asynchronous initialization method first. However, this has the disadvantage of forcing all callers to await the result, making them also asynchronous.
If possible, make your class constructor private and force callers to obtain instances with an asynchronous, factory-like static method:
class Database {
late String currentUsername;
Database._();
static Future<Database> create() async {
var db = Database._();
db.currentUsername = await db.getUsernameFS();
return db;
}
...
}
This also has the advantage of safely avoiding accidental errors, and it avoids forcing all consumers of currentUsername to be asynchronous. A disadvantage is that a private constructor would prevent your class from being extended.
If possible, I'd also make getUsernameFS a static method and pass the username to the private constructor. Then currentUsername wouldn't need to be late, and you would avoid any risk of accidentally using a late variable before it's initialized.

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.

Why can't I use local variable in a callback?

Let's say I've a stream() which returns Stream<int>. stream().listen returns StreamSubscription.
var subs = stream().listen((e) {
if (someCondition) subs.cancel(); // Error
});
I don't understand why is there an error, because by the time I start listening for events in the listen method, I would have definitely a valid object subs.
Note: I know this can be done by creating a StreamSubscription instance/top-level variable but why they have prevented the use of local variable like this?
We know that Stream.listen does not call its callback until after a value is returned, but the Dart compiler does not.
Consider the following function, which simply calls a callback and returns the result:
T execute<T>(T Function() callback) => callback();
Now, consider using it to assign a variable:
int myVariable = execute(() => myVariable + 1);
The problem here is that the given callback is called synchronously, before myVariable is assigned, but it tries to use myVariable to calculate a value!
To resolve this issue with your stream question, you can use the new late keyword. Using late tells the compiler that you know the variable will be assigned by the time it's accessed.
late final StreamSubscription<MyType> subscription;
subscription = stream().listen(/* ... */);
Likely because it's possible that subs will be used before it's assigned. We know that the callback passed to listen will be called on stream events, but it's also possible that the callback is called immediately and it's return value or a calculation done by it may be required for the return value of the function it was passed to.
Take this fakeFunc for instance, which I made an extension on the int class for convenience:
extension FakeListen on int {
int fakeFunc(int Function(int x) callback, int val) {
return callback(val);
}
}
The return value depends on the result of callback!
int subs = x.fakeFunc((e) {
print(e);
subs.toString();//error
return e + 1;
}, 5);
I can't use subs because subs will be guaranteed to not be exist at this point. It's not declared. This can be easily solved by moving the declaration to a separate line, but also forces you to make it nullable. Using late here won't even help, because subs won't exist by the time you try to use it in the callback.
Your scenario is different, but this is an example of where allowing that would fail. Your scenario involves a callback that is called asynchronously, so there shouldn't be any issues with using subs in the callback, but the analyzer doesn't know that. Even async-marked methods could have this issue as async methods run synchronously up until its first await. It's up to the programmer to make the right decision, and my guess is that this error is to prevent programmers from making mistakes.

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;