This tutorial gives an introduction into asynchronous processing the the Flutter framework.
1. What is Flutter
1.1. Introduction to Flutter
Flutter is a UI toolkit to build native, fast and beautiful apps for iOS, Android, the web and desktop with a single codebase. It was introduced by Google in 2017 and has had its first 1.0 release on December 4th, 2018.
Flutter is written in C, C++ and Dart. Unlike Javascript, Dart is a statically typed, class-based language which is able to compile Just in Time (JIT) or Ahead of Time (AOT). Flutter supports hot reload of the code during development, which results in an extremely fast stateful reload of your app during development. The SDK utilizes the Skia Graphics Engine to render.
The following image shows the general Flutter architecture.
The top layer provides the building blocks of Flutter and is purely written in Dart. It contains libraries for animation, painting, rendering and widgets.
The Framework runs on top of the engine, which is primarily written in C/C++. The engine contains three core libraries:
-
Skia — An open source graphics engine used by Google Chrome, Android, Chrome Os and may more.
-
Dart runtime
-
Text — Engine used to render and layout text
The bottom layer is the embedder. This layer is platform specific and is where the surface for rendering Flutter is configured.
Flutter is motivated by the fact that the most successful apps, like the Netflix one, looks the same on platforms like iOS and Android. Platform specific behavior like the scroll behavior is still supported by Flutter.
Flutter draws everything itself. Native component like the one used in the Android SDK may handle such updates in the native components.
1.2. Libraries
https://pub.dev/ provides Flutter and Dart libraries.
The Flutter tooling uses the pub
tooling to read library and build configuration from a pubspec.yaml
file and downloads and updates the application with the required libraries.
1.3. Development environment
Flutter can be developed by only using a text editor and the command line. More advanced tooling is provided by VSCode and Android Studio.
We currently recommend Visual Studio Code as the development tool, as it is fast, stable and lightweight. |
2. Asynchronous processing in Flutter with futures, async and await
Asynchronous operations allows your program to stay responsive which performing some tasks. Very potential long running operation should be done asynchronously:
-
Fetching data over a network
-
Writing to a database
-
Reading data from a file
Dart provides strong support for asynchronous programming:
-
Futures
-
Streams
-
async / await
2.1. Isolates
Dart uses Isolates
to run threads in.
Each isolate has its own memory and event loop and isolates can only communicate with each other by sending messages to them.
This brings the advantage that garbage collections can be done on an isolate basis.
2.2. Future
To perform asynchronous operations in Dart, you can use the Future
class and the async
and await
keywords.
A future represents the result of an asynchronous operation.
Lots of standard Dart libraries calls, return a Future
as return type, for example:
-
http.get
-
SharedPreference.getInstance()
A future can have two states:
-
Uncompleted - When you call a function that returns a future, the function queues up the operation and returns an uncompleted future
-
Completed - When a future’s operation finishes, the future completes with a value or with an error
A Future<T> instance produces a value of type T. If a future doesn’t produce a usable value, then the future’s type is Future<void>. If the function doesn’t explicitly return a value, then the return type also Future<void>
The following is an example for the usage of futures. The "Another output" is written first to the console and after the delay "Testing futures".
Future<void> printSomeThingDelayed() {
return Future.delayed(Duration(seconds: 3), () => print('Testing futures'));
}
main() {
printSomeThingDelayed();
print('Another output ...');
}
Future provides a few helper methods in case the value or error is already present:
-
Future.value
-
Future.exception
For mocking slow data, you can use the
|
Future allows to register a callback via the then
method which is called then the Future is completed with a value.
Then also returns a Future so future calls can be chained.
The catchError
method allows to register a hook for the error case.
The whenComplete
method is used to executed code after the complete or error as been received an processed.
Here is a complete example:
void main() {
var userIdFuture = getUserId();
// register callback which is executed once the future is completed
userIdFuture.then((userId) => print(userId));
// Hello wird vor 77 ausgegeben, 77 wird erst nach 3 Sekunden ausgegeben
print('Hello');
}
// method which computes a future
Future<int> getUserId() {
// simulate a long network call
return Future.delayed(Duration(seconds: 3), () => 77);
}
2.3. Await and async
The async
keyword is used to define a function as asynchronous
The await
keywords allows to wait for the result of an asynchronous function and works only in async functions.
For example, you can declare your main method as async and await a asynchronous result.
Future<void> printSomeThingDelayed() {
return Future.delayed(Duration(seconds: 3), () => print('Testing futures'));
}
main() async {
await printSomeThingDelayed(); // blocks until the result returns
print('Another output ...');
}
Here is a complete example:
void main() {
var userIdFuture = getUserId();
// register callback which is executed once the future is completed
userIdFuture.then((userId) => print(userId));
// hello is written first to the console and after 3 seconds 77
print('Hello');
}
// method which computes a future
Future<int> getUserId() {
// simulate a long network call
return Future.delayed(Duration(seconds: 3), () => 77);
}
Future<String> getUserName() async {
var userId = await getUserId();
return Future.delayed(Duration(seconds: 1), () => "Username $userId");
}
To handle errors in async functions you use try-catch.
2.4. FutureBuilder
The FutureBuilder
widget allows to use a future directly and to build its UI based on the state of the widget.
FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
return Text('Result: ${snapshot.data}');
}
return null; // unreachable
},
)
2.5. Streams
Streams support reactive programming.
They work similar to a Future
but instead of returning a single value, they return a series of events.
Streams are designed for single subscriptions.
You can use the asBroadcastStream
method to convert this stream into a stream which supports multiple subscriptions.
You can subscribe to a Stream via the following methods:
-
listen
-
onError
-
onDone - called once the stream is closed
Streams can be manipulated via methods:
-
map - converts each element of the stream into something else
-
where - allows to filter elements of the stream
-
distinct - filters out consecutive identical values
Streams can be created via the StreamController
class.
2.6. StreamBuilder in Flutter
StreamBuilder is a Flutter widgets which allows to build your UI based on the state of the stream.
3. Links and Literature
3.3. vogella Flutter and Dart example code
If you need more assistance we offer Online Training and Onsite training as well as consulting