Smartphone-Anwendungen mit Flutter erstellen

von | 18.01.2020 | Softwareentwicklung | 0 Kommentare

Frameworks, die eine gemeinsame Code-Basis für die Entwicklung mobiler Applikationen sowohl für Android als auch für iOS anbieten, findet man heutzutage leicht. Bspw. gibt es React Native von Facebook, Microsoft hat Xamarin im Angebot und auch Google bietet mit Flutter ein entsprechendes Framework. Da Flutter jedoch erst seit Mai 2017 auf dem Markt ist, gibt es noch keine gängigen Architektur-Patterns oder einen Guide to app architecture, wie man ihn etwa aus dem Android-Umfeld kennt.

Gerne möchte ich Ihnen einen Weg vorstellen, der sich meiner Meinung nach gut eignet, um mittlere bis große Smartphone-Anwendungen zu erstellen. Dieser Weg oder besser gesagt dieser Architekturstil nennt sich BLoC.

Was ist eine BloC?

Eine BLoC ist eine Business Logic Component, und davon kann es mitunter mehrere pro Anwendung geben. Die Kernaussage des BloC-Patterns ist, dass alles in der App als Stream von Events abgebildet wird. Beispiel: In Widget 1 wird ein Button gedrückt, in Widget 2 soll sich die UI verändern, also stellt die BLoC in der Mitte einen Stream bereit, den Widget 2 abhören („listen“) und Widget 1 befüllen kann.

Ein großer Vorteil dieser Vorgehensweise ist, dass Dart bereits eine eigene Syntax zum Arbeiten mit Streams bereitstellt, und keine zusätzlichen Plugins benötigt werden, um BLoCs in Flutter zu implementieren.

Die praktische Anwendung mit Beispiel

Die meisten Business-Apps müssen in irgendeiner Form immer folgende Aufgaben erfüllen:

  1. Daten von einem Server laden.
  2. Die geladenen Daten verarbeiten.
  3. Die verarbeiteten Daten in der UI anzeigen.

Auch in meinem Beispiel gilt es, diese drei Aufgaben zu erledigen. Meine Anwendung nutzt die API der OpenLigaDb¹, um ein beliebiges Fußballspiel mithilfe eines http-Get-Requests zu laden. Die Daten des Spiels liegen im JSON-Format vor und müssen auf eine passende Datenklasse gemapped werden. Die konvertierten Daten werden anschließend an der Oberfläche angezeigt.

Entsprechend der drei genannten Aufgaben verfügen die Apps meist über drei Architekturschichten. Zu Beginn meiner Arbeit mit Flutter hatte ich jedoch Schwierigkeiten, meine Dateien logisch zu strukturieren. Letztendlich habe ich mich dafür entschieden, das Projekt nach den einzelnen Architekturschichten nämlich der UI, den BloCs und den Services zu schneiden. Zusätzlich verwende ich noch einen „models“-Ordner, in dem alle Datenklassen abgelegt werden.

Ablagestruktur im Flutter-Beispiel

Die Implementierung der Datenschicht

Für den Service erstelle ich eine neue Klasse FootballService in der football_service.dart-Datei. Diese Klasse besteht im Wesentlichen aus der Methode getMatchData(id) mit der Spiel-Id als Parameter. Um einen http-Get-Request abzusetzen, wird das Dart-Package „http“ verwendet, wobei dieses zu Beginn in der pubspec.yaml-Datei bekannt gemacht wird.

Der Request wird auf die URL „https://www.openligadb.de/api/getmatchdata/$id“ abgesetzt und liefert das Fußballspiel mit der angegebenen Id zurück.

Aufruf des Services

Den FootballService nutzt man nun in der darunterliegenden BLoC-Schicht. Jede BloC muss die abstrakte Klasse bloc erweitern und deren dispose-Methode überschreiben.

Die FootballBloc hat eine öffentliche Methode getMatch(), die von der UI aufgerufen werden kann, sowie zwei private Methoden, die für das Erstellen einer zufälligen Zahl und das Mapping der http-Response auf ein Game-Objekt zuständig sind. Das Game-Datenobjekt sieht wie folgt aus:

Das vielleicht Wichtigste an FootballBloc ist der gameStream, den sie als Getter öffentlich bereitstellt. Auf diesen kann sich nämlich im nächsten Schritt die UI registrieren, um ein neu geladenes Spiel anzuzeigen.

Die Verwaltung der Abhängigkeiten

Jetzt stellt sich natürlich die Frage, wie man die BloC der UI bekannt macht? Für dieses einfache Beispiel wäre es möglich, eine Instanz der FootballBlocs in der UI zu erstellen, und darüber die Aufrufe abzuwickeln. Sobald das Projekt jedoch minimal komplexer wird, und man dieselbe BloC in mehreren Widgets benutzen möchte, ist dieses Vorgehen nicht mehr adäquat. Deshalb nutzt man vorzugsweise Provider-Widgets, um seine einzelnen BLoCs der ganzen Anwendung zur Verfügung zu stellen.

Im Ordner blocs sollte dazu ein weiterer Ordner essentials angelegt werden. Darin befinden sich die bloc_provider.dart- und die app_bloc.dart-Datei.

Die AppBloc ist dabei eine Klasse, die jede BloC einmalig instanziiert und über einen Getter zugänglich macht. In AppBloc könnten bei Wunsch auch die BloCs untereinander kommunizieren. Bei einer App mit Authorisierungsfunktion kann dies beispielsweise hilfreich sein. Hier im Beispiel wird die AppBloc als Konstruktor-Argument in das BlocProvider Widget übergeben. Der Ansatz, dies über eine AppBloc zu realisieren, die alle BLoCs enthält, muss aber nicht unbedingt für jeden Anwendungsfall der Beste sein.

Der BlocProvider ist übrigens ein Widget. Über die of()-Methode wird anderen Kind-Widgets erlaubt, sich über den BlocProvider alle BloCs der AppBlocs zu holen. Damit dies funktioniert, muss das BlocProvider-Widget in der main.dart-Datei um das MaterialApp-Widget herum gewrappt werden.

Nach diesem kleinen, aber sinnvollen Umweg können wir nun zum Erstellen der UI übergehen. Diese besteht aus einem Column-Widget, das untereinander den Text des aktuell geladenen Spiels und einen Button anzeigt.

Besonders bemerkenswert sind hier die Interaktionen mit der FootballBloc, auf deren Stream man sich registriert (Zeilen 11 bis 15), und die onPressed-Methode des Buttons (Zeilen 26 bis 28).

Und so sieht das Ergebnis aus: 

Fazit

Mir macht das Arbeiten mit Flutter Spaß. Aus meiner Sicht handelt es sich um ein sehr nützliches Framework. Besonders beeindruckt mich, dass Flutter im Gegensatz zu React Native oder Xamarin nicht die nativen UI-Komponenten von Android und iOS verwendet, sondern den Screen-Inhalt mit einer 2D-Render-Engine komplett selbst zeichnet und die Flutter-Apps dennoch wie native Apps wirken. Natürlich fehlen wie gesagt die Architektur-Patterns und ein Guide zur App-Architektur, und auch die Nutzung der verschiedenen Widgets erfordert zu Beginn etwas Übung, aber sobald man den Dreh raus hat, erleichtert es die Implementierung signifikant. Hätte man die Aufgabenstellung auch anders lösen können? Vermutlich. Wie wären Sie denn vorgegangen? Über einen Austausch würde ich mich freuen.

 

Hinweise:

[1] API der OpenLigaDB: https://www.openligadb.de/
Vor allem der Quellcode und die Schichtarchitektur ist inspiriert von https://www.raywenderlich.com/4074597-getting-started-with-the-bloc-pattern und David Anaya.

Mark Heimer
Mark Heimer
Mark Heimer ist dualer Student bei t2informatik in Kooperation mit der Hochschule für Wirtschaft und Recht Berlin, Fachrichtung Informatik. Hackathons und die Programmierung von Computerspielen sind seine Leidenschaft. In seinen Praxisphasen entwickelt er derzeit eine t2informatik-interne Software für Zeiterfassung.
Share This