Wenn eine React-App anfängt zu fluttern
Als sich im März dieses Jahres der Beginn der Corona-Krise abzeichnete, hatten wir uns im Business Development Gedanken zu Chancen gemacht, die sich für tarent daraus ergeben. Da tarent sowieso einiges an Erfahrung im Bereich E-Commerce und damit auch mit den derzeit so stark nachgefragten Lösungen wie Click & Reserve, Click & Collect oder Click & Deliver hat, ergab sich hier die Möglichkeit für ein spannendes neues Projekt mit dem hoch-inoffiziellen Namen Corona-Bäckerei.
In der Corona-Bäckerei… ♫
Damit wir unseren Kunden für jede Situation passende Beratungs- und Entwicklungsleistungen anbieten können, bewerten wir laufend, welche Tools und Frameworks sich am Markt und in der Community so entwickeln. Die Corona-Bäckerei folgt der Idee, Backwaren so bequem wie möglich und mit so wenig Menschenkontakt wie möglich bestellen und abholen zu können. Neben Hardware-, IoT- und KI-Themen brauchte es am besten auch ein Web-Frontend sowie native Apps.
Die Entscheidung beim Frontend war leicht, hier hat sich React als das Go-To-Tool herauskristallisiert. Da tarent in letzter Zeit auch einige native Apps für Kunden umgesetzt hat, wollten wir uns für dieses Projekt ein Cross-Plattform-Framework näher anschauen. Ziel war es, möglichst ähnliche Apps für Web und Native zu entwickeln, um die Dev Experience möglichst gut vergleichen zu können.
Flutter Run
Flutter verspricht “beautiful native apps in record time” und unterstützt iOS-, Android- und Web-Apps mit einer einzigen Codebasis. Ein wichtiger Faktor ist auch, dass Flutter das Backing von Google hat, was sicher zu einer längeren Lebensdauer des Tools beiträgt.
Mobil-, Web- und Desktop-Apps mit einem Framework. Quelle: https://flutter.dev/
What’s in it?
Native Appentwicklung, vor allem auf iOS, ist oft ein sehr langsamer Prozess. Während das Tooling in der JavaScript-Welt für das Web immer besser geworden ist, hat sich beispielsweise bei Xcode weniger getan. Wer sich als React-Entwickler an Hot Reloading, Styled Components, Hooks und Props gewöhnt hat, dem kommt native App-Entwicklung manchmal etwas unmodern und langsam vor. Manchmal möchte man eben nicht 2 Minuten auf den Build und den Simulator warten, nur um beurteilen zu können, ob der Button mit 30 oder mit 34 Pixeln Höhe besser aussieht. Deswegen ist es schön, dass Flutter out of the box mit Hot Reloading kommt und dabei sogar den aktuellen State einer Komponente (read: Widget) beibehält. Fühlt sich schon sehr nach 2020 an!
Um dem nativen Look-and-Feel von Android und iOS gerecht zu werden, kommt Flutter mit allen möglichen vorgefertigten UI-Komponenten wie Buttons, AppBars, Icons und Co. in zwei Varianten: Material und Cupertino. Material sind Widgets nach Googles Material-Design-Spezifikation und Cupertino sind solche im Stile des Silicon-Valley-Konkurrenten aus… nunja, Cupertino.
Hello World
Flutter braucht einiges an Dependencies, so zum Beispiel Xcode und SDKs für Android. Das Flutter Command-Line-Tool ist zwar gut, aber aufgrund von deren Größe dauert es ein bisschen bis man richtig loslegen kann. Es gibt Plugins für Dart (die Programmiersprache, in der Flutter geschrieben wurde und mit der entwickelt wird) und Flutter im Visual Studio Code Marketplace. Damit lassen sich unter anderem Simulatoren für Android und iOS direkt aus VS Code heraus starten.
Nach ein paar kurzen Tests mit den Beispielen in den Docs war es an der Zeit, loszulegen. Sehr hilfreich sind die Guides Flutter for Web Developers oder Flutter for React Native Developers. Auch, wenn ich bisher wenige Berührungspunkte mit React Native hatte, war es doch hilfreich zu sehen, wie bekannte Konzepte wie Props und State in Flutter umgesetzt werden. Sie sind zwar nicht gänzlich anders, aber dann doch anders genug, um sich erstmal ein paar Minuten einfinden zu müssen.
Die eigentliche App
Die Vorlage für das User Interface der Flutter-App sollte unsere bestehende React-Web-App sein. In React nutzten wir die Library Material UI für UI-Komponenten. Das User Interface grob nachzubauen, ging sehr leicht und schnell von der Hand. Lediglich eine (zugegeben experimentelle) Autocomplete-Komponente aus Material UI gab es so nicht in Flutter, weswegen wir dort auf etwas anderes ausweichen mussten.
Selbst die Struktur des React-Projekts ließ sich relativ leicht in Flutter replizieren. Die Komponenten bzw. Widgets konnten genau so ineinandergreifen, wie sie das auch in der Web-App taten und auch der State konnte ziemlich identisch gehandhabt werden. So diente der Code der React-App fast schon als Vorlage und musste nur noch in Dart transformiert werden – unter Berücksichtigung von ein paar Besonderheiten.
Relativ früh fällt als Unterschied auf, dass jedes Widget in Flutter nur bestimmte Parameter bei der Initialisierung entgegennimmt. Dadurch ergibt sich eine feste Struktur, die auf den ersten Blick zwar sehr ähnlich zu dem Nesting in JSX aussieht, aber weniger flexibel ist. So akzeptiert bspw. ein Scaffold-Widget ein einziges anderes Widget als body-Parameter. Wenn mehrere UI-Elemente innerhalb des Bodys dargestellt werden sollen, muss also zunächst ein Widget übergeben werden, das genau das unterstützt – das kann zum Beispiel eine Column sein, die mehrere Widgets untereinander anzeigen kann (ähnlich einer Flexbox mit flex-direction: column).
Diese Mechanik ist definitiv erstmal etwas ungewohnt. Der große Vorteil ist, dass dadurch eine wirklich sehr vorgegebene Struktur und Hierarchie entsteht, die, auch wenn sie zunächst weniger flexibel wirkt, auch weniger Fehler zulässt. Diese Struktur gibt also eine Art Leitfaden vor, der die Entwicklungsgeschwindigkeit erhöht.
Spätestens bei der Vereinheitlichung des Layouts und des Stylings über beide Plattformen hinweg werden die Unterschiede allerdings größer. Statt einer einzelnen Komponente oder einem Element innerhalb einer Komponente beliebige Styles zuzuweisen, wie das bei CSS der Fall ist, müssen entweder feste Parameter übergeben werden oder spezielle Widgets benutzt werden. Um zum Beispiel den Abstand zum Bildschirmrand für eine Komponente festzulegen, muss ein Padding-Widget verwendet werden, das erstere Komponente als child entgegennimmt. Auch diese Mechanik ist erstmal etwas ungewohnt und führt zu etwas verschachteltem Code.
Die Unterschiede in der Handhabung von State und Props sind großteils syntaktischer Natur, es gibt eine Unterscheidung von StatefulWidgets und StatelessWidgets analog zu Class Based Components und Functional Components in React. Bei StatefulWidgets ist der State jedoch eine eigene Klasse, weswegen sich das Handling etwas anders gestaltet.
Da in unserer App mit einer Rest-API interagiert werden muss, wird asynchroner Code ausgeführt. Ähnlich zu Promises in ES6 gibt es in Dart sogenannte Futures. Das Abfragen der Daten ist auf den ersten Blick relativ ähnlich in Dart implementiert wie async/await in ES7 und heißt sogar so. Das parsen der JSON-Response wiederum wirkt etwas umständlich und auch für das Bauen von Widgets basierend auf den zurückgelieferten Daten wird ein sogenannter FutureBuilder verwendet. Im Vergleich zu einem einfachen Iterieren über die Daten und Conditional Rendering für die Fehlerhandhabung in React wirkt das etwas umständlicher und auch weniger leserlich.
Geringe Einstiegshürde
Wer React-Erfahrung mitbringt, dem wird einiges an Flutter bekannt vorkommen. Flutter kommt mit Hot Reloading für schnelles visuelles Feedback und angenehmes schnelles Entwickeln – genau wie die meisten Webpack-basierten Build-Pipelines für React auch. Das Prinzip mit State und Props fühlt sich ebenfalls fast wie ein Heimspiel an, jedoch gibt es natürlich auch einige Unterschiede. Auch wenn der Vergleich nicht immer aussagekräftig sein muss ist die Menge an notwendigem Code bei beiden Projekten, die eine möglichst identische App abbilden sollen, gefühlt in etwa gleich.
Für unseren Anwendungsfall waren keine komplexeren APIs wie z.B. Kamerazugriff oder Geolocation notwendig, die Entwicklung einer App mit einem vergleichsweise einfachen User Interface ging jedoch zügig und vor allem leicht.
Wir werden Flutter bei zukünftigen Projekten zu nativen Apps definitiv mit berücksichtigen.