I'm trying out Flutter, but I'm having trouble getting the UI to update consistently. I'd like to show a status message while a long-running async method is called, but the setState() call I make just before calling the long-running method doesn't seem to cause my build() method to get invoked.
I've created a simple example that calculates the Fibonacci number for a randomly selected number between 25 and 30. In my sample code/app, hitting the "calc" button calls _calc(). _calc() picks a random number, sets a status message "Calculating Fib of $num..." tied to a text widget (_status) and updates it with setState(); then calls the async _fib() routine to calculate the number; then updates _status with the result using setState(). Additionally, the build() method prints the value of _status to the console, which can be used to see when build() is invoked.
In practice, when the button is pressed, the first status message does not appear either in the debug console, or on the UI. Doing a bit of experimentation, I added a pseudo sleep function that I call just prior to calling _fib(). This sometimes causes the first setState() call to work properly - invoking build(). The longer I make the sleep, the more often it works. (I'm using values from a few milliseconds up to a full second).
So my question are: What am I doing wrong? and What's the right way to do this? Using the pseudo sleep is obviously not the correct solution.
Other, probably not too relevant info: My dev environment is Android Studio 3.1.2 on a Win10 machine. Using Android SDK 27.0.3, with Flutter beta 0.3.2. My target device is the emulator for a pixel2 running Android 8.1. Also, sorry if my lack of 'new' keywords is off-putting, but from what I read in Dart 2 release notes, it's not usually necessary now.
import 'package:flutter/material.dart';
import "dart:async";
import "dart:math";
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Debug Toy',
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String _status = "Initialized";
final rand = Random();
Future sleep1() async {
return new Future.delayed(const Duration(milliseconds: 100),() => "1");
}
Future<Null> _resetState() async {
setState(() { _status = "State Reset"; });
}
Future<Null> _calc() async {
// calculate something that takes a while
int num = 25 + rand.nextInt(5);
setState(() { _status = "Calculating Fib of $num..."; });
//await sleep1(); // without this, the status above does not appear
int fn = await _fib(num);
// update the display
setState(() { _status = "Fib($num) = $fn"; });
}
Future<int> _fib(int n) async {
if (n<=0) return 0;
if ((n==1) || (n==2)) return 1;
return await _fib(n-1) + await _fib(n-2);
}
#override
Widget build(BuildContext context) {
print("Build called with status: $_status");
return Scaffold(
appBar: AppBar(title: Text('Flutter Debug Toy')),
body: Column(
children: <Widget>[
Container(
child: Row(children: <Widget>[
RaisedButton( child: Text("Reset"), onPressed: _resetState, ),
RaisedButton( child: Text("Calc"), onPressed: _calc, )
]),
),
Text(_status),
],
),
);
}
}
Let's start by going to one extreme and rewriting fib as fibSync
int fibSync(int n) {
if (n <= 0) return 0;
if (n == 1 || n == 2) return 1;
return fibSync(n - 1) + fibSync(n - 2);
}
and calling that
Future<Null> _calc() async {
// calculate something that takes a while
int num = 25 + rand.nextInt(5);
setState(() {
_status = "Calculating Fib of $num...";
});
//await Future.delayed(Duration(milliseconds: 100));
int fn = fibSync(num);
// update the display
setState(() {
_status = "Fib($num) = $fn";
});
}
The first setState just marks the Widget as needing to be rebuilt and (without the 'sleep') continues straight into the calculation, never giving the framework the chance to rebuild the Widget, so the 'Calculating' message isn't displayed. The second setState is called after the calculation and once again (redundantly) marks the Widget as needing to be rebuilt.
So, the execution order is:
Set status to Calculating, mark Widget as dirty
Perform the synchronous calculation
Set status to Result, mark Widget as dirty (redundantly)
Framework finally gets chance to rebuild; build method is called
When we uncomment the 'sleep', the execution order changes to
Set status to Calculating, mark Widget as dirty
'Sleep', allowing the framework to call build
Perform the synchronous calculation
Set status to Result, mark Widget as dirty (again)
Framework calls build
(As an aside, note how the synchronous fib calculation is an order of magnitude faster because it doesn't have to do all the microtask scheduling.)
Let's re-consider the async calculation. What's the motivation of making it async? So that the UI remains responsive during the calculation? As you've seen, that doesn't achieve the desired effect. You still only have one thread of execution, and you aren't allowing any gaps in execution for callbacks and rendering to occur. Sleeping for 100ms is not compute bound, so drawing etc can occur.
We use async functions to wait for external events, like replies from web servers, where we don't have anything to do until the reply arrives, and we can use that time to keep rendering the display, reacting to gestures, etc.
For compute bound stuff, you need a second thread of execution which is achieved with an Isolate. An isolate has its own heap, so you have to pass it its data, it works away in its own space, then passes back some results. You can also stop it, if it's taking too long, or the user cancels, etc.
(There are much less computationally expensive ways to calculate fibs, but I guess we're using the recursive version as a good example of an O(n^2) function, right?)
Related
I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.
Building an app with Flutter and Riverpod, using a lot of:
ref.watch(someProvider).when(data: (someData){
// render layout with data
}, error: (err, stack) {
// do stuff with error
}, loading: (){
return LoadingScreen(); <----
})
The problem is that in most cases the loading screen only renders for a split second, causing a bad experience where the app feels a little "jumpy". I would like to be able to set a minimum of say 2 seconds for the loading state, is it possible to force a widget to stay rendered for a minimum amount of time some how?
I have 2 suggestions for you :
Option A - You can put a delay of say 2 seconds in your provider, somewhere between loading and the next state. This way you make the loading screen visible at least 2 seconds.
Option B - You can also make a loadingScreen as an overlay Widget, which dismisses itself after say 2 seconds. This way you make the loading screen visible exactly 2 seconds.
I believe either way is fine in your case, since you say it flashes, I assume no heavy/long tasks are done in between states. In case you do need heavy/long tasks, option A guarantees the tasks are finished before loading screen disappears.
You can create Stopwatch to track (approximate) operation duration. This example will give you some idea.
final someProvider = FutureProvider<int>((ref) async {
final operationDuration = Duration(seconds: 1); //random, you can give it random to test
final stopwatch = Stopwatch()..start();
final data = await Future.delayed(operationDuration);
final minOperationTime = const Duration(seconds: 2);
final extraWaiting = minOperationTime.inMilliseconds - stopwatch.elapsed.inMilliseconds;
if (stopwatch.elapsed.inSeconds < 2) {
await Future.delayed(minOperationTime - stopwatch.elapsed);
}
return extraWaiting;
});
And widget
class LoadingTestOnFutureBuilder extends ConsumerWidget {
const LoadingTestOnFutureBuilder({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(someProvider).when(data: (someData) {
return Text("got data ${someData}");
}, error: (err, stack) {
return Text("Err");
}, loading: () {
return CircularProgressIndicator();
});
}
}
According to Flutter's documentation and this example, as I'm understanding it, a key difference between the Provider package's context.read<T> and context.watch<T> methods relate to triggering widget rebuilds. You can call context.watch<T>() in a build method of any widget to access current state, and to ask Flutter to rebuild your widget anytime the state changes. You can't use context.watch<T>() outside build methods, because that often leads to subtle bugs. Instead, they say, use context.read<T>(), which gets the current state but doesn't ask Flutter for future rebuilds.
I tried making this simple app:
class MyDataNotifier extends ChangeNotifier {
String _testString = 'test';
// getter
String get testString => _testString;
// update
void updateString(String aString) {
_testString = aString;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => MyDataNotifier(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(context.read<MyDataNotifier>().testString),
),
body: Container(
child: Level1(),
),
),
);
}
}
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (val) {
context.read<MyDataNotifier>().updateString(val);
},
),
Text(context.read<MyDataNotifier>().testString),
],
);
}
}
All the calls are to counter.read<T>(). The app's state changes, but the UI is not rebuilt with the new value. I have to change one of the calls to counter.watch<T>() to get the state to rebuild.
On the other hand, in DZone's simple example, the UI rebuilds, and all the calls are to context.read().
What's different between their code and mine? Why can't I rebuild with counter.read() calls?
TLDR: after a quick glance, the DZone article looks like it has a bug.
Longer answer
context.watch<Foo>() does 2 things:
return the instance of the state from the tree
mark context as dependent on Foo
context.read<Foo>() only does 1).
Whenever your UI depends on Foo, you should use context.watch, since this appropriately informs Flutter about that dependency, and it will be rebuilt properly.
In general, it boils down to this rule of thumb:
Use context.watch in build() methods, or any other method that returns a Widget
Use context.read in onPressed handlers (and other related functions)
The main reason people seem to use context.read inappropriately is for performance reasons. In general, preferring context.read over context.watch for performance is an anti-pattern. Instead, you should use context.select if you want to limit how often a widget rebuilds. This is most useful whenever you have a value that changes often.
Imagine you have the following state:
class FooState extends ChangeNotifier {
// imagine this us updated very often
int millisecondsSinceLastTap;
// updated less often
bool someOtherProperty = false;
}
If you had a widget that displays someOtherProperty, context.watch could cause many unnecessary rebuilds. Instead, you can use context.select only depend on a processed part of the state:
// read the property, rebuild only when someOtherProperty changes
final property = context.select((FooState foo) => foo.someOtherProperty);
return Text('someOtherProperty: $property');
Even with a frequently updating value, if the output of the function provided to select doesn't change, the widget won't rebuild:
// even though millisecondsSinceLastTap may be updating often,
// this will only rebuild when millisecondsSinceLastTap > 1000 changes
final value = context.select((FooState state) => state.millisecondsSinceLastTap > 1000);
return Text('${value ? "more" : "less"} than 1 second...');
I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.
So, I have a screen which shows a text and an image above the text and loads, in background, some data from the shared preferences.
But the image is taking forever to load, and when I remove the call to get the shared preferences data the image loads very quiclky. Do someone knows if I'm doing something wrong with this call?
here's the code:
class WelcomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
precacheImage(AssetImage(Images.tomato), context);
_loadData(context);
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
Strings.appName,
style: TextStyle(
color: AppColors.black,
fontSize: 50.0,
fontWeight: FontWeight.w600,
),
),
Image.asset(Images.tomato),
],
),
),
);
}
void _loadData(BuildContext context) async {
final PreferencesRepository repository = PreferenceRepositoryImp();
repository.loadAll().then(
(_) {
sleep(Duration(seconds: 2));
Navigator.pushNamedAndRemoveUntil(context, Routes.HOME, (_) => false);
},
);
}
}
and that's the image
You're using sleep(Duration(seconds: 2)); which halts everything. You should not be using sleep, especially inside a build method. Dart is single threaded so that means when it halts a thread, it prevents the UI from being built.
Also, why is _loadData marked as async if you're not using the await keyword? You only need it if you're going to use the await keyword, otherwise, remove it. If you want to make use of it, you should use await Future.delayed(Duration(seconds: 2)) which makes it wait. Here:
void _loadData(BuildContext context) async {
final PreferencesRepository repository = PreferenceRepositoryImp();
await repository.loadAll();
await Future.delayed(Duration(seconds: 2));
Navigator.pushNamedAndRemoveUntil(context, Routes.HOME, (_) => false);
}
How does await differ from sleep?
await makes Dart say "Hey, I'm going to keep my eye out for this in the future" (haha get it? Future? hahahhahaha I'm funny). Anyways, it won't continue the _loadData function because it wants that value but it will keep running (in your example) the build method and then when the Future comes back with a value, it will be like "Hey! There's that value I was looking for! Now I can keep running that function!"
sleep on the other hand makes Dart say "I'm going to take a nap for the duration of time I get" (in this case two seconds). It won't do anything until it "wakes up" since Dart is single-threaded. After it takes its two-second nap, it will continue the function and after it finishes, the build method will carry on, therefore, loading your image which explains why it takes so long.