What's the benefit of having setState() accept a function just to immediately call it and then request rebuild? In particular, what's the advantage over having users explicitly call a "rebuild" type function?
When Flutter had a "markNeedsBuild" function, developers ended up just sort of calling it at random times. When the syntax switched to setState(() { ... }), developers were much more likely to use the API correctly. They are functionally equivalent from the machine's point of view, but they seem to evoke different code from developers.
If you follow the convention of only mutating member variables inside a setState closure, you'll avoid a situation you're refactoring some code and accidentally remove the call to setState, or call setState unnecessarily. And if your State is unmounted, Flutter can fail an assertion so you know something is wrong as soon as you begin trying to mutate members, instead of at the end.
Eventually there will probably be an analyzer warning enforcing that setState is always called when mutating members of a State, so any member variable mutation that happens outside of initState or a setState callback will be flagged as suspect.
If you're just getting started with state in Flutter, check out the Flutter widgets tour. I've found that a lot of cases where I was calling setState can be handled more elegantly with FutureBuilder, StreamBuilder, AnimatedWidget, or AnimatedBuilder, so don't forget to consider those alternatives if you find yourself calling setState a lot.
Adam Barth and Yaroslav Volovich contributed to this question/answer.
To complete Colin's answer, it also ensures that you call setState at the right moment when dealing with asynchronous function.
Mutating your state outside of the callback can lead to an easy mistake:
function() async {
setState(() {});
myState = await future;
}
This causes a problem because if your future doesn't finish synchronously, the build method will be called before the state is mutated.
By using the callback you are forced to do the following:
function() async {
final value = await future;
setState(() {
myState = value;
});
}
This time, it doesn't cause problems because the future is awaited before the setState.
Can't I make an async callback and still have the issue?
No. Because setState method internally check that the callback does not return a future. And if it does, it will throw.
So the following is impossible:
setState(() async {
myState = await future;
});
Related
This timer does not work.
Why?
What is late doing?
import 'dart:async';
void main() => A();
class A {
final a = 10;
late final Timer timer = Timer.periodic(
Duration(seconds: 1),
(timer) {
print(a);
},
);
}
late have multiple meanings in Dart but if used when declaring value of the variable right away, it means the variable are first assigned its value when you access the variable the first time.
So your code are not doing anything since you, at no point, are trying to read the timer variable.
You can read more about this in the official documentation: https://dart.dev/null-safety/understanding-null-safety#lazy-initialization
You are trying to do a Lazy initialization for the timer, but there is just a little point you are missing that causes the current behavior.
As the official docs states:
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.
So by creating a new object of class A by calling it is constructor, this will not initialize the timer, it would be initialized once it is accessed, and that is the meaning of Lazy initialization.
So if we accessed the timer property inside the constructed object, the intended behavior will occur and the timer callback would run as you want.
Try this one:
void main(){
final aObject = A();
print(aObject.timer);
}
I have created the example in DartPad to let you easily test and play around with it, access it via this link.
late modifier means “enforce this variable’s constraints at runtime instead of at compile time”.
You can check lazy-initialization.
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.
Find more late keyword with declaration
And doing void main() => A(); just creating an instace of A.
If you like to run timer, do it like
void main() {
final a = A();
a.timer;
}
I understand that Dart is single-threaded and that within an isolate a function call is popped from the event loop queue and executed. There seems to be two cases, async and sync.
a) Async: An asynchronous function will run without interruption until it gets to the await keyword. At this point, it may release control of the instruction pointer or continue its routine. (i.e. async functions can be but are not required to be interrupted on await)
b) Sync: All instructions from setup -> body -> and teardown are executed without interruption. If this is the case, I would say that synchronous functions are atomic.
I have an event listener that may have multiple calls in the event loop queue. I think I have two options.
Using the Synchronized package
a) Async version:
import 'package:synchronized/synchronized.dart';
final Lock _lock = Lock();
...
() async {
await _lock.synchronized(() async {
if (_condition) {
_signal.complete(status);
_condition = !_condition;
}
});
}
b) Sync version:
() {
if (_condition) {
_signal.complete(status);
_condition = !_condition;
}
}
From my understanding of the Dart concurrency model these are equivalent. I prefer b) because it is simple. However, this requires that there cannot be a race condition between two calls to my sync event handler. I have used concurrency in languages with GIL and MT but not with the event-loop paradigm.
a) Async: An asynchronous function will run without interruption until it gets to the await keyword. At this point, it may release control of the instruction pointer or continue its routine. (i.e. async functions can be but are not required to be interrupted on await)
await always yields. It's equivalent to setting up a Future.then() callback and returning to the Dart event loop.
For your simple example, there's no reason to use _lock.synchronized(). Synchronous code cannot be interrupted, and isolates (as their name imply) don't share memory. You would want some form of locking mechanism if your callback did asynchronous work and you needed to prevent concurrent asynchronous operations from being interleaved.
I've always considered async/await more elegant/sexy over the Futures API, but now I'm faced with a situation where the Future API implementation is very short and concise and the async/await alternative seems verbose and ugly.
I marked my two question #1 and #2 in the comments:
class ItemsRepository
{
Future<dynamic> item_int2string;
ItemsRepository() {
// #1
item_int2string =
rootBundle.loadString('assets/data/item_int2string.json').then(jsonDecode);
}
Future<String> getItem(String id) async {
// #2
return await item_int2string[id];
}
}
#1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
#2: Is this efficient if the method is called a lot? How much overhead does await add? Should I make the resolved future an instance variable, aka
completedFuture ??= await item_int2string;
return completedFuture[id];
1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
async methods are contagious. That means your ItemsRepository method has to be async in order to use await inside. This also means you have to call it asynchronously from other places. See example:
Future<dynamic> ItemsRepository() async {
// #1
myString = await rootBundle.loadString('assets/data/item_int2string.json');
// do something with my string here, which is not in a Future anymore...
}
Note that using .then is absolutely the same as await in a async function. It is just syntactic sugar. Note that you would use .then differently than in your example though:
ItemsRepository() {
// #1
rootBundle.loadString('assets/data/item_int2string.json').then((String myString) {
// do something with myString here, which is not in a Future anymore...
});
}
And for #2 don't worry about a performance impact of async code. The code will be executed at the same speed as synchronous code, just later whenever the callback happens. The only reason async exists is for having an easy way of allowing code to continue running while the system waits for the return of the asynchronously called portion. For example not block the UI while waiting for the disk to load a file.
I recommend you read the basic docs about async in Dart.
then and await are different. await will stop the program there until the Future task is finished. However then will not block the program. The block within then will be executed when the Future task is finished afterwards.
If you want your program to wait for the Future task, then use await. If you want your program to continue running and the Future task do it things "in the background", then use then.
I have two functions
callee() async {
// do something that takes some time
}
caller () async {
await callee();
}
In this scenario, caller() waits till callee() finishes. I don't want that. I want caller() to complete right after invoking callee(). callee() can complete whenever in the future, I don't care. I just want to start it just like a thread and then forget about it.
Is this possible?
When you call the callee function, it returns a Future. The await then waits for that future to complete. If you don't await the future, it will eventually complete anyway, but your caller function won't be blocked on waiting for that. So, you can just do:
caller() {
callee(); // Ignore returned Future (at your own peril).
}
If you do that, you should be aware of what happens if callee fails with an error. That would make the returned future complete with that error, and if you don't listen on the future, that error is considered "uncaught". Uncaught errors are handled by the current Zone and the default behavior is to act like a top-level uncaught error which may kill your isolate.
So, remember to handle the error.
If callee can't fail, great, you're done (unless it fails anyway, then you'll have fun debugging that).
Actually, because of the risk of just forgetting to await a future, the highly reocmmended unawaited_futures lint requires that you don't just ignore a returned future, and instead wants you to do unawaited(callee()); to signal that it's deliberate. (The unawaited function can be imported from package:meta and will be available from the dart:async library in SDK version 2.14).
The unawaited function doesn't handle errors though, so if you can have errors, you should do something more.
You can handle the error locally:
caller() {
callee().catchError((e, s) {
logErrorSomehow(e, s);
});
}
(Since null safety, this code only works if the callee() future has a nullable value type. From Dart 2.14, you'll be able to use callee().ignore() instead, until then you can do callee().then((_) => null, onError: (e, s) => logErrorSomehow(e, s)); instead.)
or you can install an error handling zone and run your code in that:
runZoned(() {
myProgram();
}, onError: logErrorSomehow);
See the runZoned function and it's onError parameter.
Sure, just omit await. This way callee() is called immediately and when an async operation is called the call will be scheduled in the event queue for later execution and caller() is continued immediately afterwards.
This isn't like a thread though. As mentioned processing is enqueued to the event queue which means it won't be executed until the current task and all previously enqueued tasks are completed.
If you want real parallel execution you need to utilize isolates.
See also
https://www.dartlang.org/articles/event-loop/
https://api.dartlang.org/stable/1.16.1/dart-isolate/dart-isolate-library.html
https://pub.dartlang.org/packages/isolate
I'm experimenting some WebGL in Dart and I had created a class that loads shaders from separate files and I would like to throw an event (function) when the object is ready, so I can continue my application knowing that my shaders are properly loaded. Do someone knows an easy way to do this?
One approach is to use a Future pattern to accomplish this:
Future<SomeType> initMyObject(){
final c = new Completer();
// Do my object init stuff
// and when it is complete:
c.complete(instanceOfSomeType);
// Return the Future object to any subscribers.
return c.future;
}
Then elsewhere you can get notified like so:
initMyObject().then((SomeType t){
//executes when future completes
});