Die sieben Aufgaben von 7GUIs sollen typische Problemstellungen bei der Anwendungsentwicklung widerspiegeln. Dann können die gebauten Lösungen dafür benutzt werden, verschiedene Programmiersprachen und Toolkits miteinander zu vergleichen.
Ich dachte, das ist eine gute Gelegenheit hier nochmal Flutter zu zeigen. Die Vorstellung letzten Monat zeigte relativ wenig von den damit baubaren Oberflächen.
Flutter ist ja deklarativ aufgebaut, das heißt die Oberfläche baut sich immer wieder neu und reagiert dann auf die neuen Variablenwerte. Man kann dafür StatefulWidgets
benutzen, die Variablen im Widget-Stateobjekt speichern und dann immer mit setState
anzeigen, dass die Oberfläche sich doch bitte neubauen soll. Ich werde stattdessen mit GetX der Oberfläche einen Controller zur Seite stellen, in dem die Variablen leben und der dafür sorgt, dass auch Widgets ohne reguläres Zustandsobjekt interaktiv sein können. Aber seht selbst:
1. Counter
Die erste Aufgabe ist ein Klickzähler. Doch einen Counter zu erstellen ist keine Herausforderung, das ist das Standardbeispiel auf der Flutterhomepage und bei Modulen wie GetX, die das Statemanagement übernehmen wollen. Entsprechend habe ich hier nur das GetX-Beispiel genommen und die Oberfläche angepasst.
So sieht es aus:
Die Oberfläche ist eine Row
, in der ein Textfeld und ein Button sind.
Das ist der Code:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Counter', theme: ThemeData( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), ); } } class MyHomePage 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( appBar: AppBar(title: Text('Counter')), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // Use Obx(()=> to update Text() whenever count is changed. Obx(() => Text("Clicks: ${c.count}")), RaisedButton(child: Text("Count"), onPressed: () => c.increment()), ], ))); } class Controller extends GetxController { var count = 0.obs; increment() => count++; }
Das Textfeld zeigt, da mit Obx
umschlossen, immer den aktuellen Wert der Zählvariable im Controller an. Ein Druck auf den Button erhöht diesen Wert.
Außer der Zählvariable im Controller und der Funktion increment()
hat die App keine weitere Funktionalität.
So funktioniert das dann in Bewegung:
Beachte auch, dass die Elemente wie die einer typischen Android-Anwendung aussehen. Das liegt schlicht daran, dass hier eine MaterialApp
gestartet wird.
2. Temperaturconverter
Die zweite Aufgabe ist eine Oberfläche zum Unwandeln von Celsius zu Fahrenheit und umgekehrt.
So sieht meine Lösung aus:
Das ist eine einzelne Row
, in der nacheinander je ein Texteingabefeld und ein Textanzeigefeld aufgereiht sind. Und ja, 0.0 bei beiden Feldern war nicht der richtige Defaultwert, wäre aber einfach änderbar.
Der Code:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; // ... main() und MyApp sind weggekürzt class MyHomePage extends StatelessWidget { final Controller c = Get.put(Controller()); @override Widget build(context) => Scaffold( appBar: AppBar(title: Text('Temperature Converter')), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ SizedBox( width: 80, child: Obx(() => TextFormField( // to force Obx reload when value changes key: Key("C" + c.celsius.string), keyboardType: TextInputType.number, initialValue: c.celsius.value.toStringAsFixed(1), onChanged: (value) => c.fahrenheit.value = c.ctof(double.tryParse(value)), ))), Text("Celsius ="), SizedBox( width: 80, child: Obx(() => TextFormField( key: Key("F" + c.fahrenheit.string), keyboardType: TextInputType.number, initialValue: c.fahrenheit.value.toStringAsFixed(1), onChanged: (value) => c.celsius.value = c.ftoc(double.tryParse(value)), ))), Text("Fahrenheit"), ], ), )); } class Controller extends GetxController { var celsius = 0.0.obs; var fahrenheit = 0.0.obs; double ctof(double c) { return c * (9 / 5) + 32; } double ftoc(double f) { return (f - 32) * (5 / 9); } }
Hier passiert jetzt schon ein bisschen mehr. Der GetX-Controller hat zwei Variablen, celsius
und fahrenheit
, und zwei Funktionen zum Umwandeln der Werte. In der UI wird immer, wenn im Texteingabefeld etwas eingeben wird, mittels onChanged
im Controller der Wert des anderen Texteingabefeld geändert. Weil die Eingabefelder wieder mit Obx
umschlossen sind baut dann Flutter direkt die Oberfläche neu, mit dem neuen Celsius/Fahrenheit-Wert als initialValue
der Eingabefelder. Einen key
zu setzen hilft flutter bzw Obx dabei, zu erkennen wann die Oberfläche neu gebaut werden muss.
So sieht es in Bewegung aus:
Ich habe den Code auch auf Github hochgeladen, so kann jeder ihn direkt importieren und abändern. Es gibt noch fünf weitere Aufgaben, die immer komplizierter werden. Vielleicht mache eine Serie hierdraus, dann folgen sie später.
- Unverstelltes Routing in Flutter: NamedRoutes mit Animationen
- Ein Jahr mit Flutter
- Flutter: Ein tolles Framework für mobile Apps