Von 0 auf MVP in 3 Monaten mit Flutter
Vor einiger Zeit bin ich von einem jungen Start-Up kontaktiert worden, weil sie eine App für ihren neuen Lieferservice benötigten. Die App sollte so schnell wie möglich marktreif und gleich von Anfang an sowohl für Android als auch für iOS verfügbar sein. Nachdem ich in der Vergangenheit bereits Apps für beide Plattformen nativ entwickelt habe, war relativ schnell klar, dass hier nur eine Cross-Platform-Entwickling in Frage kommt — zumindest wenn die Kosten im Rahmen bleiben sollen.
Cross-Platform
Für Cross-Platform-Entwicklungen gibt es verschiedene Technologien. Ich habe bereits Apps mit Ionic erstellt, was sich einerseits sehr gut für das Rapid Prototyping eignet und andererseits auch für ausgereifte Apps funktioniert. Allerdings läuft dabei die gesamte App in einem WebView, was Nachteile mit sich bringt und mir persönlich nicht sonderlich gefällt. Eine andere mögliche Technologie wäre React Native. Da ich im Web-Bereich oft und gerne mit React arbeite, ist es für mich eigentlich naheliegend, React auch für die App-Entwicklung zu nutzen. App ist aber nicht gleich Web und irgendwie fühlte sich React Native für mich nie ganz richtig an. Das mag vielleicht daran liegen, dass ich damit bisher nicht viel Erfahrung gesammelt habe.
Flutter weckte meine Aufmerksamkeit, indem es unter anderem den Vorteil bietet, in Maschinencode zu kompilieren und dabei eine eigene Render-Engine mitbringt. Das verspricht eine konsistente User Experience über alle Plattformen hinweg sowie gute Performance auf mobilen Geräten. Zumindest in der Theorie klingt das besser, als das was Ionic und React Native bieten können.
Dart vs. Swift, Kotlin und TypeScript
Apps auf iOS werden mit Swift geschrieben und für Android-Apps empfehle ich Kotlin. Obwohl die beiden Sprachen unterschiedlich scheinen, haben sie einige Gemeinsamkeiten. Ionic-Projekte werden üblicherweise in TypeScript geschrieben und auch für React Native bietet sich TypeScript an. Ich mag alle drei Sprachen, denn sie sind modern und ausgreift genug, um damit effizient robuste Applikationen zu schreiben.
Flutter-Projekte werden in Dart geschrieben — eine Sprache, die fast ausschließlich im Zusammenhang mit Flutter Verwendung findet. Dart ist ebenfalls eine moderne Sprache und ist gut an die Entwicklung für mobile Applikationen angepasst. Als einzig wichtiges Feature fehlte zum Zeitpunkt des Projektstarts aber die Null-Safety. Diese war erst im Beta-Channel von Flutter verfügbar und wurde von vielen Drittanbieter-Paketen noch nicht unterstützt. Mittlerweile ist die Null-Safety aber offiziell verfügbar und wurde von vielen Paketanbietern auch implementiert.
UI und UX
Was mir an Flutter vermutlich am besten gefällt, ist die Umsetzung von User Interface und User Experience. Beide Punkte sind mir, und oftmals auch meinen Kunden, wichtig. Mit Flutter kommt das Material Design in Form von Material Widgets direkt mitgeliefert. Damit lässt sich schnell und ohne viel Aufwand eine gute User Experience erstellen. Natürlich lässt sich das auch individuell anpassen mit eigenen Schriftarten, Farben und vielem mehr.
Hier zwei Screenshots vom MVP:
Man sieht hier eine angepasste App Bar, Buttons, Text, Bilder, eine Navigationsleiste und eine Google-Maps-Integration mit Markern im eigenen Design.
Eine Sache, die mir zu Beginn undurchdacht schien und an die ich mich erst gewöhnen musste, ist, dass bei Flutter alles aus Widgets besteht. Es gibt Widgets für Transformationen, für Animationen, für Benutzer-Interaktionen und natürlich für das Layout und die Material Komponenten. Das führt dazu, dass alles in einem stark hierarchischen Widget-Baum untergebracht werden muss. Diese Baumstruktur kann schnell unübersichtlich werden und ist meiner Meinung nach nur mit guten Tools im Griff zu behalten. Glücklicherweise bietet Flutter sehr gute Integrationen für gängige Entwicklungsumgebungen an, und diese helfen erstaunlich gut dabei, mit dem Widget-Baum zu arbeiten.
Obwohl ich noch immer nicht sicher bin, ob mir dieser Widget-Ansatz grundsätzlich gefällt, hat er sich in der Praxis doch als sehr zielführend und problemlos erwiesen. Ich hatte nie das Gefühl, groß Kompromisse eingehen zu müssen bei der Umsetzung von Layouts. Das hat am Ende dazu geführt, dass sich unser MVP bereits sehr gut ausgearbeitet anfühlte.
Geschäftslogik
Die Geschäftslogik umfasst in diesem Fall hauptsächlich State Management, Datenmodellierung und der Anbindung externer Schnittstellen.
Die Liste an möglichen State-Management-Ansätzen für Flutter ist lang und enthält auch bekannte Namen wie Redux. Nach einiger Überlegung habe ich mich für BLoC entschieden. Für BLoC Code zu schreiben kann etwas umständlich sein und da hilft es, Pakete wie freezed zur automatischen Code-Generierung zu verwenden. Damit lassen sich dann mühelos unveränderbare (immutable) Klassen für states und events erstellen. Die Geschäftslogik auf diese Weise in sogenannten Business Logic Components unterzubringen hilft dabei, den Code vorhersehbar und testbar zu halten.
Datenmodellierung ist relativ trivial und kann noch verbessert werden, wenn ebenfalls das freezed-Paket genutzt wird, um unveränderbare Klassen zu generieren. Falls mit JSON-Daten gearbeitet werden soll, kann zusätzlich ein Paket wie json_serializable verwendet werden.
Für die Implementierung von REST-Schnittstellen eignet sich das retrofit-Paket sehr gut, da es einen Großteil des Codes generiert, den man ansonsten von Hand schreiben müsste.
Wie man sieht, werden im Bereich der Geschäftslogik häufig Code-Generatoren eingesetzt, die einiges an repetitiver und aufwändiger Arbeit übernehmen.
Qualitätssicherung
Ein wichtiger Teil der Software-Entwicklung ist die Qualitätssicherung und vor allem auch Regressionstests. Man möchte nicht vor der Situation stehen, dass ein getestetes Feature auf einmal nicht mehr funktioniert und man weiß nicht einmal wie lange schon. Zudem möchte man vielleicht auch sicher sein, dass die Applikation mit Grenzfällen klarkommt, die nicht häufig getestet werden.
Auf der einen Seite kann man sehr viel Zeit mit dem Schreiben von automatisierten Tests verbringen, und diese Zeit ist in der Regel auch gut investiert. Auf der anderen Seite ist es für den Kunden nicht immer einleuchtend, weshalb Geld für das Schreiben von Tests anstelle von neuen Features ausgegeben werden soll. Die Integration von Tests in Flutter war einfach, so dass ein Maß an Testabdeckung erreicht werden konnte, mit dem ich mich wohl fühle, ohne dass die Kosten dafür aus dem Ruder gelaufen wären.
BLoCs können mit Hilfe des Pakets bloc_test sehr gut getestet werden. Im besten Fall testet man alle BLoCs vollständig und hat damit die komplette Geschäftslogik abgedeckt.
Neben dem Testen von BLoCs kann mit wenig Aufwand auch das User Interface getestet werden. Dabei hilft das Paket golden_toolkit. Es erlaubt, Teile des Widget-Baums in bestimmten Zuständen in Form von sogenannten Goldens zu rendern. Sollte sich etwas am UI ändern, so wird der Golden Test das feststellen. Ist die Änderung beabsichtigt, aktualisiert man einfach die entsprechenden Goldens. Auf diese Weise wird es keine unbemerkten Anpassungen am User Interface geben.
BLoC-Tests und Golden-Tests sind zusammen sehr umfassend in Sachen Regressionstests.
Kommunikation mit der Host-Plattform
Trotz dem Ziel, eine Codebase für alle Plattformen zu haben, führt manchmal kein Weg an plattformspezifischen Implementierungen vorbei. In unserem Fall musste eine Bibliothek eingebunden werden, die nur nativ für Android und iOS verfügbar ist. Flutter bietet dazu sogenannte Method Channels an. Diese ermöglichen die Kommunikation zwischen der Flutter-Applikation und nativem Code. Deren Verwendung ist unkompliziert und bereitet keine Probleme, wenn man sich auf der nativen Seite von Android- und iOS-Apps auskennt. Falls gewünscht, können Flutter-Views und native Views nebeneinander existieren und miteinander kommunizieren.
Build und Deployment
Es existieren ein paar gute Lösungen für das Erstellen von Builds und deren Deployment. Ich habe bei diesem Projekt eine halbautomatische Variante gewählt und nutze appcenter für das Erstellen der Builds und die interne Verteilung. Die so erstellten Builds werden dann manuell im Apple App Store und Google Play Store hochgeladen, um die volle Kontrolle über diesen Prozess zu behalten.
Das hat sich soweit als effizient herausgestellt, da die automatisch generierten Builds auch immer gleich Tests ausführen und neue Versionen sehr schnell an interne Tester verteilt werden können. Für neue App Releases müssen die gewünschten Builds dann einfach noch in die Stores hochgeladen werden.
Fazit
Flutter hat meine Erwartungen bezüglich App-Entwicklung eindeutig übertroffen. Über die Jahre habe ich gelernt, bei jedem Projekt mindestens eine unvorhergesehene Hürde zu erwarten. Flutter hat bei der Entwicklung unseres MVPs keine einzige solche Hürde gestellt. Nicht nur war es meinem Kunden und mir möglich, die App zeitgerecht in die Stores zu bringen, wir konnten in der selben Zeit sogar noch ein Backend und eine zweite App für die Lieferanten entwickeln.
Natürlich ist nicht alles nur Sonnenschein. Um möglichst zügig voranzukommen, habe ich einiges an externen Paketen eingesetzt. Dieser Umstand hat bis jetzt verhindert, dass der Code auf die eingangs erwähnte Null-Safety umgestellt werden konnte. All diese Abhängigkeiten im Auge zu behalten, während sich die App weiterentwickelt, wird sich zunehmend als Herausforderung präsentieren.
Für alle, die sich die App gerne ansehen möchten: Sie heißt easi.delivery und ist im Apple App Store und im Google Play Store zum Download erhältlich.