Flutter ist ein Framework zur Anwendungsentwicklung. Fokus sind Android und iOS-Apps, aber man kann stattdessen auch für das Web oder mittlerweile sogar den Desktop entwickeln. Es ist ein Google-Projekt, was aber nicht wirklich ein Nachteil ist wenn man mit Android sowieso für eine Google-Plattform entwickelt.
Das Framework ist schon wegen der Konzepte eine ziemliche Umstellung zur gewöhnlichen Android-Entwicklung, zusätzlich schreibt man die Anwendung dann auch noch in einer neuen Sprache, Dart. Doch Dart entpuppt sich als großer Vorteil, dazu unten mehr.
Flutter-Code und Konzepte
Flutter mag insgesamt eine große Umstellung sein, doch der Einstieg ist super simpel. Alles ist ein Widget, jede Oberfläche ist ein Widgetbaum, im Baum kombiniert man die Widgets frei. Zum Beispiel ist das hier das mitgelieferte Hello-World-Beispiel:
import 'package:flutter/widgets.dart'; void main() => runApp( const Center( child: Text('Hello, world!', key: Key('title'), textDirection: TextDirection.ltr ) ) );
So sieht das aus:
Auch als Anfänger ist das problemlos lesbar, die Elemente machen genau genau was man erwartet. main()
ist der Startpunkt der Anwendung, runApp
startet die App, das Center-Widget
zentriert, Text
zeigt Text an. Würde man den Code in eine Material-App packen, würde die App auch direkt aussehen wie eine typische Android-Anwendung.
Aber es ist purer Code, während man bei der Entwicklung mit Android-Studio Oberflächen mit XML-Layouts zusammenbauen konnte. Die Trennung nicht zu haben birgt die Gefahr, UI-Code und Programmlogik zu verschmelzen – was ja aber auch bei regulären Androidanwendungen nur zu schnell passiert. Flutter, und mehr noch die Community drumrum, kompensiert diesen Nachteil über mit einem exzessiven Fokus auf Zustandsmanagement.
Zustandmanagement
Zustandsmanagement ist die Verwaltung des States, das sind einfach die lokalen und globalen Variablen, die Einfluss auf die Oberfläche haben. Flutter versucht generell eine deklarative UI-Entwicklung vorzugeben: Die UI wird immer wieder neu gezeichnet, und wenn der Zustand sich verändert hat sieht die Oberfläche entsprechend anders aus.
Zuerst aber gibt es StatelessWidgets, die keinen eigenen Zustand haben. Holen sie sich die Variablen nicht von außen sehen sie immer gleich aus.
class GreenFrog extends StatelessWidget { const GreenFrog({ Key key }) : super(key: key); @override Widget build(BuildContext context) { return Container(color: const Color(0xFF2DBD3A)); } }
Das hier wäre einfach ein grünes Fenster.
Dann gibt es StatefulWidgets, die ihre eigenen änderbaren Variablen haben können.
class YellowBird extends StatefulWidget { const YellowBird({ Key key }) : super(key: key); @override _YellowBirdState createState() => _YellowBirdState(); } class _YellowBirdState extends State<YellowBird> { @override Widget build(BuildContext context) { return Container(color: const Color(0xFFFFE306)); } }
Das zeigt jetzt auch nur einen farbigen Bereich an. Aber der State-Klasse könnte man jetzt Variablen hinzufügen. Die Doku zeigt dieses Beispiel:
class Bird extends StatefulWidget { const Bird({ Key key, this.color = const Color(0xFFFFE306), this.child, }) : super(key: key); final Color color; final Widget child; _BirdState createState() => _BirdState(); } class _BirdState extends State<Bird> { double _size = 1.0; void grow() { setState(() { _size += 0.1; }); } @override Widget build(BuildContext context) { return Container( color: widget.color, transform: Matrix4.diagonal3Values(_size, _size, 1.0), child: widget.child, ); } }
Das ist also ein Widget mit einer festen Größe, das eine Funktion mitbringt um zu wachsen. setState(() { });
muss immer dann aufgerufen werden, wenn die Oberfläche die Zustandsvariablen neu evaluieren soll.
GetX
Es gibt Unmengen Lösungsansätze für die Zustandsverwaltung. Zum einen weil sie wichtig ist, denn mit ihr steuert man ja direkt das Verhalten der App. Aber auch, weil da die Einflüsse der Entwicklergruppen kollidieren: Da kommt das übliche mentale Chaos aus dem Javascript-Land angeschwemmt, vermischt mit Enterprise-Architekturen – passt ja zu einem Google-Framework. GetX ist anders. Es ist ein pragmatisches Werkzeugset, das nur unter anderem Statemanagment löst. Mit dem Flutter-Modul kann man jeder Oberfläche einen Controller zur Seite stellen, und mit dem Obx
-Widget die Oberfläche immer neu zeichnen lassen wenn sich eine der Variablen ändert. Im Flutter-Kontext ist das Magie, das Resultat ist einfach sauberer Code. So ist das Counter-Beispiel aus der Readme elegant und verständlich:
Du hast einen Controller, in der die Variable und eine optionale Manipulierfunktion definiert wird:
class Controller extends GetxController{ var count = 0.obs; increment() => count++; }
Und dazu das Widget, bei dem ein Druck auf den Button die Variable erhöht, wodurch direkt die Anzeige aktualisiert wird:
class Home extends StatelessWidget { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); @override Widget build(context) => Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: RaisedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } }
Das Beispiel zeigt noch zusätzlich die Navigationslösung, die man nicht nutzen muss. Trotzdem: Klarer geht es nicht.
GetX hat Nachteile. So sind die Details nicht ganz so simpel wie es zuerst scheint - wie zum Beispiel umsetzen, dass auf eine Objektvariable eines Listenelements reagiert wird, wenn doch List.obs
nur auf das Vergrößern und Verkleinern der Liste reagiert? Vor allem aber ist es ein junges Projekt, bei dem noch zu viele Commits hereinkommen, von zu wenigen Entwicklern, das Ding ist noch im Flux. Trotzdem ist es ein supermächtiges Werkzeug und erleichtert die Arbeit mit Flutter deutlich.
Paketverwaltung
Richtig nett an Flutter ist auch das darum aufgebaute Entwickleruniversum. Man findet Erklärvideos und -artikel. Am wichtigsten aber sind die Pakete. Auf https://pub.dev/ gibt es eine Suche, Module (für Flutter wie Dart) werden dort sauber vorgestellt, gewonnene Likes dienen als Orientierungshilfe. Bräuchte meine App beispielsweise eine animierbare Navigationszeile unten, ich könnte sie mit convex_bottom_bar direkt von dort beziehen. Eintragen in die pubspec.yaml, flutter pub get
ausführen, den Code einbauen, fertig. Eine solche Paketübersicht habe ich bei der direkten Androidentwicklung mit Java schmerzlich vermisst.
Dart
Dass Flutter direkt eine neue Programmiersprache erfordert ist erstmal total abschreckend. Doch Dart macht das ganz schnell wieder wett, denn die Sprache ist gelungen. Dart ist eine wilde Mischung, für mich fühlt es sich aber vor allem nach Ruby an. Was toll ist. Es fehlen allerdings die Blöcke. Dafür hat es async-Funktionen direkt mit dabei, samt await und Future. Wikipedia zählt Smalltalk und Erlang als weitere Einflüsse. Bestimmt ist da auch Python mit drin, Java und Javascript.
Dart hat mich bisher noch in keiner Situation enttäuscht. Die Sprache hat für mich als Ruby-Programmierer immer eine gute Lösung parat gehabt und mich seltenst negativ überrascht.
Die folgenden Beispiele kann man auch alle auf der Webseite ausprobieren.
Hallo-Welt:
main() { print("Hello, World!"); }
Zahlen:
int i = 1 + 2; print(i); // => 3
Strings:
final s = "abc" + "def"; print(s); // => abcdef
Es gibt Listen und Maps (Hashes):
var testliste = [1, 2, 3]; print(testliste[1]); // => 2 var testmap = {1: 'a', 2: 'b', 3: 'c'}; print(testmap[2]); // => b
Und map und fold:
var testliste = [1, 2, 3]; var result = testliste.map((element) => [element]); print(result); // => [[1], [2], [3]] var folded = result.fold(0, (prev, element) => prev + element.first); print(folded); // => 6
Dr ganze Bereich um async war ungewohnt und ist nicht einfach, aber das simple Beispiel ist klar:
Future<void> fetchUserOrder() { // Imagine that this function is fetching user info from another service or database. return Future.delayed(Duration(seconds: 2), () => print('Das hier folgt 2 Sekunden später')); } void main() { fetchUserOrder(); print('Das ist die erste Ausgabe.'); }
Und natürlich Objekte mit Funktionen:
class Sword { int damage = 5; use() => print("$this dealt $damage damage."); } main() { var sword = Sword(); sword.use(); // => Instance of 'Sword' dealt 5 damage. }
Dazu kommt noch viel mehr: Generic, Mixins, Named Parameters, Lambda hatte ich erwähnt, die Standardklassen haben hilfreiche Funktionen definiert, chaining mittels ..
– also das Aneinanderreihen von Funktionen an ein Objekt, obwohl die Funktionen eigentlich void
zurückgeben. Dart ist wirklich erstaunlich angenehm.
Und die Flutter-Programme, die mit Dart gebaut werden, laufen nicht etwa in einem Wrapper, sondern werden kompiliert und dann nativ auf dem Gerät ausgeführt. Dart kann auch nach Javascript transpilieren, was dann die Web-Unterstützung von Flutter erlaubt, aber das ist gefährlich – die so erstellten Webseiten sind bestimmt Javascript-Monster. Für einzelne Spezialfälle könnte aber auch das eine gute Lösung sein.
Fazit: Beachtenswert
Flutter verspricht, mobile Anwendungen viel schneller entwickelbar zu machen als wenn man sie mit den Bordmitteln schreibt. Wenn man das Framework bereits beherrscht mag das stimmen. Ist man neu, ist es mit der Fixierung auf indirekte Interaktivität nicht so einfach zu beherrschen und dieser Lernprozess dann auch nicht schnell. Aber: Dann ist es mächtig, und ich habe definitiv den Eindruck, dass man hiermit bessere Oberflächen und somit auch Apps bauen kann. Dass die dann auf beiden großen mobilen Betriebssystemen laufen können kommt noch dazu.
Dart ist dann die Krönung. Erwartet hatte ich ein alternatives Javascript – etwas, womit man arbeiten kann, aber woran man eher keine Freude hat. Stattdessen ist es nahe genug an Ruby und so ausgereift, dass ich fast frei meinen Programmcode schreiben kann. Sicher, mit der Zeit werden sich die Schwachstellen offenbaren, und eine so junge Sprache kann nicht an die Modulvielfalt von Ruby herankommen. Eine positive Überraschung war sie aber allemal.
Wenn Flutter die entsprechende Entwicklung stabilisiert (noch ist es alpha) könnte ich mir das glatt auch für Linuxanwendungen vorstellen. Die Entwicklung mit wxWidgets, GTK und Qt ist deutlich komplizierter. Derzeit greifen Entwickler dann stattdessen oft zu Electron und anderen Javascript-Desktopwrappern. Flutter mit Dart könnte die bessere Lösung sein. Ich bin gespannt, ob sich das bewahrheitet. Derzeit hat sich die Alpha an snap gekoppelt, was komplett inakzeptabel ist solange es die einzige Lösung bleibt. Aber Flutter-Appimages oder schlicht Binaries? Könnten eine tolle Sache sein.
Wer jetzt für Android entwickeln will, für den ist Flutter mit Dart auch jetzt schon einen Versuch wert.
onli blogging am : 7GUIs in Flutter (2/7)
Vorschau anzeigen
onli blogging am : Ein Jahr mit Flutter
Vorschau anzeigen