Flutter - Einblick in die Future

Async und await in Flutter erklärt
Keine Kommentare

Im ersten Teil dieses Artikels [1] wurde eine Kurzeinführung in Flutter gegeben. Zur Erinnerung: mit Flutter kann man mobile Applikationen für iOS und Android erstellen, aber auch Webapplikationen und bald auch welche für den Desktop. „Write once, deploy everywhere“ kommt also wieder näher. In diesem Teil werden wir uns anschauen, wie man in der Programmiersprache Dart asynchrone Operationen durchführt und dann in Flutter elegant damit arbeiten kann.

Wer traditionelle IDEs wie IntelliJ startet, kennt das: man muss erst einmal eine Weile warten, bis diese lahmen Dinger endlich bereit sind. Programmiersprachen wie Java erlauben zwar langlaufende Operationen im Hintergrund zu starten, aber der Programmierer muss das immer explizit machen, und um ehrlich zu sein, so richtig elegant ist das nicht in den APIs verankert. Und wenn etwas nicht einfach ist, bzw. man nicht dazu gezwungen wird, macht man es auch eher nicht. Dart löst das anders. Operationen, die potenziell langsam sein können, wie File-Operationen oder Netzwerkzugriff, sind immer asynchron. Das heißt, man kann sie gar nicht explizit synchron aufrufen.

IT Security Camp

Freiberuflicher Whitehat Hacker, Trainer und Security-Coach

Christian Schneider (Schneider IT-Security)

IoT Conference 2020

All Inclusive

Katrin Rabow

Zurück in die Future

Rufe ich in Dart eine Operation auf, die potenziell langsam sein könnte, bekomme ich nie direkt ein Objekt zurück, sondern immer ein Future, getypt mit dem eigentlichen Objekttyp. Dieses Future hat zwei Stati, uncompleted und completed. Das Future führt die langlaufende Operation also aus und ist zunächst im Status uncompleted. Irgendwann ist die langlaufende Operation fertig und das Future ist completed. Dart bietet Code, der nicht auch asynchron ist, keine Möglichkeit, synchron auf das eigentliche Objekt zuzugreifen. Was eventuell hier noch wie Bevormundung der Entwicklergemeinschaft klingt, verhindert, dass man es sich als Programmierer leichtmachen kann und doch mal eben schnell auf das Objekt zugreift. „Wird schon nicht so lange laufen“-Denken wird hier gar nicht erst zugelassen.

Damit man aber noch etwas mit dem Future anfangen kann, kann man einen Callback registrieren, der von Dart aufgerufen wird, wenn das Future fertig ist. Damit haben wir in Dart das „Hollywood Design“-Prinzip für langsame Operationen: Versuchen Sie nicht, uns anzurufen, wir rufen Sie an. Sobald Dart das Future angeführt hat, wird der Clientcode aufgerufen, im neuen IT-Slang auch reaktives Programmieren genannt und zurzeit mit den reactive Extensions von Microsoft sehr populär. Klingt kompliziert? Ist es aber gar nicht: Listing 1 zeigt eine Dart-Methode, die einen langsamen Zugriff simuliert. Es wird mit drei Sekunden Verzug die Zahl 77 als Future berechnet.

Future<int> getUserId() {
// simuliert eine langlaufende Operation
 return Future.delayed(Duration(seconds: 3), () => 77);

Wer immer diese Methode aufruft, bekommt sofort das Future zurück, d. h. die Berechnung des Callers kann weiterlaufen. Über den Callback kann man auf das Ergebnis reagieren, wenn das Future completed ist (Listing 2).

void main() {
  var userIdFuture = getUserId();
  // hier registieren wir unseren Callback
  userIdFuture.then((userId) => print(userId));
 
  // Hello wird vor 77 ausgegeben, 77 wird erst nach 3 Sekunden   // ausgegeben
  print(‚Hello‘);
}

Warte mal!

So weit, so gut. Manchmal kann das Registrieren von Callbacks, insbesondere dann, wenn man mehrere asynchrone Calls verschachteln will, doch recht komplex werden. Und Dart soll einfach bleiben, oder? Stimmt. Und deshalb kann man doch synchron auf das Ergebnis eines asynchronen Calls zugreifen, wenn man auch asynchron läuft. Asynchroner Code darf also synchron auf asynchrone Ergebnisse zugreifen. So darf eine Methode, die asynchron ist, einen REST-Call synchron absetzen und dann mit dem Ergebnis noch einmal einen Netzwerk-Call durchführen und das Ergebnis am Ende wieder als Future zurückgeben.

Eine Methode markiert sich in Dart mit dem async-Keyword als asynchron. Um den Wert eines Futures in einer solchen Methode synchron zu bekommen, wird await verwendet. Wahrscheinlich erklärt das ein Codebeispiel besser. Wir nutzen hier unsere alte getUserId()-Methode, die im synchronen Fall ein Future zurückgibt mit dem await, um auf das Ergebnis zu warten. Das geht wie gesagt nur, weil unsere Methode mit async markiert ist (Listing 3).

Future<String> getUserName() async {
  var userId = await getUserId();
  return Future.delayed(Duration(seconds: 1), () => "Username $userId");
}

Bisher war das alles Dart-Code. Damit wir dem Titel des Artikels noch gerecht werden, kommen wir nun zu Flutter.

Die Futures in Flutter

In Flutter sind alles Widgets. Wie spielt Flutter mit Futures zusammen? Man könnte natürlich einen Callback auf dem Flutter registrieren und in diesem Callback dann den State in einem StatefulWidget updaten und diesen State zum Bauen der Widget Hierarchy verwenden. Also in Pseudologik: Wenn ich noch keine Daten habe, dann zeige einen Platzhalter. Wenn die Daten kommen, baue mein UI neu mit den neuen Daten. Das würde gehen, wäre aber nicht so elegant, weil Dart ja sehr häufig Futures nutzt. Von daher hat Flutter das FutureBuilder-Widget. Dieses erlaubt es, effizient mit Futures zu arbeiten. Für das Beispiel erzeugt man eine Flutter-App wie im letzten Teil oder unter [2] beschrieben. Dann muss man in sein pubspec yaml noch http: ^0.12.0+4 als Dependency hinzufügen (Listing 4).

dependencies:
  flutter:
    sdk: flutter
  http: ^0.12.0+4

Dann kann man sein Datenmodell schreiben und es im Internet mit JSON füllen (Listing 5). Das Ganze ist asynchron.

class Todo {
  int userId;
  String title;
  bool completed;
 
  Todo._internal({this.userId, this.completed, this.title});
  factory Todo(Map<String, dynamic> map) {
    var userId = map['userId'];
    bool completed = map['completed'];
    var title = map['title'];
 
    return Todo._internal(userId: userId, completed: completed, title: title);
  }
}
 
Future<List<Todo>> _getTodos() async {
  List<Todo> todos = List();
  http.Response response =
      await http.get("https://jsonplaceholder.typicode.com/todos");
  if (response.statusCode == 200) {
    String body = response.body;
    var json = jsonDecode(body);
    for (Map<String, dynamic> map in json) {
      todos.add(Todo(map));
    }
  }
  return todos;
}

Dieses Future kann man dann mit dem FutureBuilder verwenden. In Listing 6 ist ein Widget zu sehen, das es nutzt. Das muss man nur noch in seiner App einbauen, wie im letzten Teil oder in [2] beschrieben.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
 
class TodoListWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: FutureBuilder(
        builder: (context, AsyncSnapshot<List<Todo>> snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              padding: EdgeInsets.all(16),
              itemCount: snapshot.data.length,
              itemBuilder: (context, number) {
                return Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey),
                    borderRadius: BorderRadius.circular(5.0),
                  ),
                  child: ListTile(
                    title: Text("${snapshot.data[number].title}"),
                    subtitle: Text("${snapshot.data[number].completed}"),
                  ),
                );
              },
            );
          } else {
            return Center(child: CircularProgressIndicator());
          }
        },
        future: _getTodos(),
      ),
    );
  }
}

Das Ganze sieht dann so aus wie in Abbildung 1. Wir haben also eine Liste erzeugt, die wir asynchron mit geparstem JSON befüllt haben. Und das alles in ca. 70 Zeilen Code. Ganz schön beeindruckend, finde ich.

Abb. 1: Ergebnis des asynchronen JSON-Vorgangs

Abb. 1: Ergebnis des asynchronen JSON-Vorgangs

Fazit: Flutter ist die Zukunft

Flutter und Dart zeichnen sich durch ein intelligentes und modernes Konzept für asynchrone Programmierung aus. Wir haben reaktive Programmierung schon eingebaut, wer hier noch mehr benötigt, kann z. B. auf die DartRx Library zurückgreifen. Durch die konsequente Verwendung von Futures bei potenziell langläufigen Operationen kann man in Flutter nur schwer langsam programmieren und das API erlaubt die elegante Nutzung.


Links & Literatur

[1] Entwickler Magazin 1.20: Flutter-haft. Mobiles Entwicklen mit dem Flutter SDK. Lars Vogel, Jonas Hungershausen.

[2] https://www.vogella.com/tutorials/Flutter/article.html

 

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -