Dass zwei komplett getrennte Entwicklungslinien nicht effizient sind, hat auch Google erkannt und das Flutter SDK aus der Taufe gehoben. Damit ist die Entwicklung plattformübergreifender mobiler Applikationen möglich, die auf einer gemeinsamen Codebasis fußen. Dieses Framework werden wir im ersten Teil dieser Artikelserie vorstellen, um dann im zweiten Teil fortgeschrittene Konzepte vorzustellen.
Entstanden ist das Flutter SDK aus einem Experiment des Google-Chrome-Teams, das untersucht hat, ob man die Performance des Browsers verbessern kann, wenn man das traditionelle CSS-Layoutmodell ignoriert. 2015 wurde diese Engine auf der damaligen DartCon-Konferenz vorgestellt. Damit war es theoretisch möglich, mit unglaublichen 120 FPS (Frames per Second) auf Android zu zeichnen, wenn denn die Hardware schnell genug gewesen wäre. Dadurch motiviert, wurde im Anschluss an einem vollständigen SDK gearbeitet, das am 4. Dezember 2018 als Release 1.0 freigegeben wurde.
Technischer Unterbau
Wenn man mit dem Flutter SDK mobile Applikationen schreibt, nutzt man die Programmiersprache Dart, eine ehemalige JavaScript-Alternative aus dem Hause Google. Dart existiert schon länger und wurde der Welt bereits öfter feilgeboten, fand bisher aber kaum Akzeptanz. Inzwischen hat Google wohl sein AdWords Frontend auf Dart umgeschrieben und positioniert Dart mit Flutter wieder neu.
Dart ist eine effiziente, Java- bzw. C#-nahe Sprache, die einige Features beinhaltet, die der Entwicklergemeinde das Leben leichter machen sollen. Dart in Kombination mit Flutter erfindet sicherlich nicht das Rad neu, hat aber im Vergleich zu Java die folgenden interessanten Eigenschaften:
- Benannte und optionale Parameter: Methoden und Konstruktoren können Parameter als optional deklarieren. Aufrufer können gezielt Parameter angeben, die verwendet werden sollen. Damit kann eine Methode viele Parameter enthalten, der Aufrufer muss aber nur die relevanten mitgeben. Dadurch entfallen unnötige Parameterplatzhalter wie meineMethode(10, null, null, null, 4, null, null).
- Hot Reload: Änderungen im Quellcode werden in den meisten Fällen on the fly übernommen. Dadurch wird der Zyklus Änderung – Deployment – Test deutlich beschleunigt. Während sich das Bauen und das Deployment für die native Android-Entwicklung träge und altbacken anfühlen, mutet die Flutter-Entwicklung dynamisch und modern an.
- Rendering: Flutter übernimmt das komplette Rendering der Applikation. Dabei werden viele bekannte Widgets der Zielplattformen bereitgestellt. Diese können nativ aussehen oder auch individuell gestylt werden. Unter Android wird eine Aktivität erzeugt, die dann die Flutter Engine lädt, die danach das Zeichnen übernimmt.
Installation des Flutter SDKs und der Entwicklungstools
Die Installation von Flutter ist schnell erledigt, doch eine genaue Beschreibung würde diesen Artikel unnötig verlängern. Deshalb haben wir die entsprechenden offiziellen Links in unserem freien Flutter-Tutorial [1] gesammelt. Wir aktualisieren sie auch stetig, sodass sie immer aktuell sein sollten.
Als Entwicklungsumgebung empfehlen wir Visual Studio Code, auch wenn im Prinzip ein beliebiger Texteditor und eine Kommandozeile ausreichen. Android Studio kann man auch verwenden, doch fühlt sich die Flutter-Entwicklung dann nicht mehr so innovativ an, da man wieder die gleiche träge IntelliJ-IDEA-Variante verwenden muss, die man eventuell schon während der nativen Android-Entwicklung im Einsatz hatte. Einer der Gründe, warum die mobile Entwicklung mit Flutter effizienter ist, ist die mögliche Verwendung leichtgewichtiger Tools wie Visual Studio Code. Diese Chance sollte man auch nutzen.
Kann man Flutter-Applikationen auch mit der Eclipse IDE entwickeln?
Android-Entwickler neigen dazu, ihre Werkzeuge nicht zu mögen, da die Android Tools leider immer recht langsam und instabil waren. Da die Eclipse IDE außerhalb der Android-Entwicklung immer noch sehr populär ist und in jedem Release schneller und besser wird, stellt sich für den einen oder anderen die Frage, ob sie auch für die Flutter-Entwicklung verwendet werden kann. Hier arbeitet Jonas, einer der Autoren dieses Artikels, zurzeit daran, den technischen Unterbau von Visual Studio Code auch in die Eclipse IDE zu integrieren. Sobald das gelungen ist, sollte man die gleiche Editorfunktionalität von Visual Studio Code auch in Eclipse nutzen können. Interessierte Leser können dem Fortschritt unter [2] folgen.
Eine erste Flutter-Applikation
Sind die Tools installiert, kann man am schnellsten mit flutter create meinerstesfluttterproject sein erstes Flutter-Projekt über die Kommandozeile anlegen. Danach wechselt man in das entsprechende Verzeichnis, und mit code öffnet man seine Entwicklungsumgebung.
Schaut man sich die generierte Applikation an, erkennt man eventuell bekannte Strukturen aus Android- und macOS-Entwicklungen. Im Ordner android bzw. ios liegen generierte Anwendungsgerüste, die für die nativen Applikationen verwendet werden. Für die Entwicklung mit Flutter ist der Ordner lib der wichtigste. Hier liegen die .dart-Dateien, die den gesamten Flutter-Quellcode enthalten. Die Datei main.dart definiert in der main-Methode den Einstiegspunkt für unsere Applikation: void main() => runApp(MyApp());
Eine weitere wichtige Datei ist puspec.yaml. Sie beschreibt im Prinzip Applikationsmetadaten wie die verwendete SDK-Version, externe Dependencies sowie interne Ressourcen wie Bilder oder Schriftarten. Hat man, wie in der Anleitung zur Installation beschrieben, einen Android-Emulator angelegt bzw. sein physikalisches Gerät verbunden, kann man die Applikation mit F5 starten. Man sieht dann das generierte UI mit einem Zähler und einem Button, der den Zähler hochtreibt (Abb. 1).
Abb. 1: Ein erstes Flutter-Projekt
Sucht man in main.dart nach primarySwatch, kann man die generierte Farbe auf Colors.red setzen und nach dem Speichern den Hot Reload sowie die neue Farbe im Emulator bewundern.
Alles ist ein Widget
In Flutter wird das UI über Widgets aufgebaut. Ein Widget kann wiederum ein oder mehrere Children Widgets haben und Applikationslogik über Methoden definieren. Jedes Element auf dem Bildschirm ist ein Widget – von einfachen Buttons über komplexere Scrolling Lists bis hin zur ganzen Seite. Das hat den Vorteil, dass auch aufwendige UI-Komponenten recht einfach überall wieder genutzt werden können.
Das Flutter SDK kommt schon mit einer großen Menge an Widgets daher, die einem den Einstieg leichter machen. Gestylte Buttons, Dialoge und Navigationsleisten lassen sich einfach in die App einbauen. Hierbei gibt es für die meisten Elemente sowohl eine Material-(Android-)Version als auch eine für die iOS-Plattform im Cupertino-Stil. Es lässt sich also für beide Plattformen recht einfach das jeweilige Look and Feel implementieren.
Selbstverständlich ist es auch möglich, eigene Widgets zu bauen. Ein Widget ist eine Klasse, die von StatelessWidget oder StatefulWidget erbt. Erstere sollte benutzt werden, wenn das Widget keine Variablen besitzt, die während der Laufzeit verändert werden, wie zum Beispiel eine Informationskarte. Das Stateless Widget hat eine abstrakte build-Methode, die immer implementiert werden muss. Sie wird genutzt, um andere Widgets zu definieren, und bekommt zusätzlich einen BuildContext, über den viele Informationen zur aktuellen Applikationsinstanz oder dem Gerät verfügbar sind. Zusätzlich müssen alle Variablen der Klasse als final markiert werden, da Veränderungen an ihnen unangenehme Nebeneffekte zur Folge haben können. Dart hat hier leider keine Möglichkeit, dies zur Kompilierzeit zu erzwingen, und daher wird nur eine Warnung in VS Code angezeigt (Listing 1).
Listing 1: Dart
class Homepage extends StatelessWidget {
final int count;
Homepage({this.count});
@override
Widget build(BuildContext context) {
return Text(‘Pressed: $count’);
}
}
Ein Stateful Widget bietet hingegen die Möglichkeit, die eigenen Variablen zu verändern. Das Framework kümmert sich dann automatisch darum, die betroffenen Widgets neu zu rendern. Im Gegensatz zum Stateless Widget benötigt das Stateful Widget noch eine weitere Klasse, die den aktuellen Zustand des Widgets beinhaltet, denn auch die Variablen dieses Widgets müssen final sein. Eine State-Instanz wird in der Methode createState des StatefulWidgets zurückgegeben und muss dabei eine Klasse sein, die von State erbt, wobei „T“ das zugehörige Stateful Widget ist. Diese Klasse hat wiederum die abstrakte Methode build, die die Widgets zurückgibt.
Außerdem kann diese Klasse nicht finale Felder aufweisen, die während der Laufzeit verändert werden können. Hierfür steht die Methode setState(() {}) zur Verfügung. In der Funktion, die ihr übergeben wird, können Instanzvariablen der Klasse verändert werden, und sie löst daraufhin ein Re-render des aktuellen Widgets aus. Hierbei sollte darauf geachtet werden, dass nur die Änderung der Variable selber in der Methode aufgerufen wird und nicht etwa die Berechnung oder gar eine Netzwerkabfrage (Listing 2).
Listing 2
class Homepage extends StatefulWidget {
@override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialButton(
child: Text(‘Pressed: $count’),
onPressed: () => setState(() => count++),
);
}
}
Da jedes Element in einer Flutter-Applikation ein Widget ist, gibt es auch das Konzept von Activities oder Pages nicht mehr. Jede Seite der App ist nur ein weiteres Widget, das sich ebenso gut in anderen Widgets unterbringen lässt. So ist es zum Beispiel recht einfach, eine App sowohl auf dem Smartphone als auch auf dem Tablet gut aussehen zu lassen. Hier genügt meist nur eine Abfrage der Orientation und der Bildschirmabmessungen, und schon lassen sich zwei Seiten nebeneinander rendern. Beispielsweise ist der Screenshot in Abbildung 2 einfach eine Ansammlung von Center, Row, Column, Text, Button und ListWidgets in weniger als 140 Zeilen Code. Das Beispiel findet sich bei GitHub unter [3] im Verzeichnis layoutsexample.
Fazit: Mobile Entwicklung ohne Rattenschwänze
Während den iOS-Entwicklern mit Swift eine relativ moderne Entwicklungsumgebung zur Verfügung steht, wirkt die native Android-Entwicklung mit Java oder Kotlin etwas veraltet. XML Layouts werden geschrieben, externe Libraries stellen Lösungen bereit, damit Daten beim Drehen des Geräts nicht verloren gehen, und die Verwendung von Activities und Fragments fühlt sich immer noch nicht elegant an. Darüber hinaus ist und bleibt Android Studio buggy und träge. Man merkt eben, dass Android inzwischen auch schon mehr als 10 Jahre auf dem Buckel hat und viele Altlasten mitschleppt.
Daneben strahlt Flutter wie eine aufgehende Sonne. Durchdachte Konzepte mit Stateful Widgets und Stateless Widgets inklusive Ablauflogik, ein einheitliches UI-Design und eine moderne Programmiersprache wie Dart machen in Verbindung mit effizienten Tools einfach Spaß. Für neue Android-Projekte empfehlen wir deshalb definitiv Flutter – und als Nebeneffekt bekommt man auch noch eine solide iOS-Applikation dazu.