Hochschule Darmstadt - Fachbereich Informatik- opus4.kobv.de

Die Seite wird erstellt Toni Kroll
 
WEITER LESEN
Hochschule Darmstadt - Fachbereich Informatik- opus4.kobv.de
Hochschule Darmstadt
            – Fachbereich Informatik –

Evaluation von Zustandsverwaltungssystemen
 für das mobile Cross-Plattform-Framework
                   Flutter

Abschlussarbeit zur Erlangung des akademischen Grades
                Bachelor of Science (B.Sc.)

                        vorgelegt von
                            Jonas Franz
                     Matrikelnummer: 760233

          Referent      :    Prof. Dr. Kai Renz
          Korreferent   :    Prof. Dr. Hans-Peter Wiedling
Hochschule Darmstadt - Fachbereich Informatik- opus4.kobv.de
Jonas Franz: Evaluation von Zustandsverwaltungssystemen für das mobile Cross-
Plattform-Framework Flutter, © 03. März 2022
Hochschule Darmstadt - Fachbereich Informatik- opus4.kobv.de
ERKLÄRUNG

Ich versichere hiermit, dass ich die vorliegende Arbeit selbstständig verfasst
und keine anderen als die im Literaturverzeichnis angegebenen Quellen be-
nutzt habe.

Alle Stellen, die wörtlich oder sinngemäß aus veröffentlichten oder noch
nicht veröffentlichten Quellen entnommen sind, sind als solche kenntlich
gemacht.

Die Zeichnungen oder Abbildungen in dieser Arbeit sind von mir selbst
erstellt worden oder mit einem entsprechenden Quellennachweis versehen.

Diese Arbeit ist in gleicher oder ähnlicher Form noch bei keiner anderen
Prüfungsbehörde eingereicht worden.

Darmstadt, 03. März 2022

                                                       Jonas Franz
ABSTRACT

Flutter has multiple approaches in managing the state of an application.
Flutter is a popular cross-platform framework for the development of ap-
plications for iOS, Android, Web, Windows, Linux and macOS. It offers the
advantage to develop one codebase for multiple platforms at once resulting
in reduced costs and complexity. Flutter uses a declarative UI that renders
widgets based on a given state. The state management of a Flutter appli-
cation is one of the most important parts of an application influencing the
architecture and structure of an application.

This thesis describes multiple already existing approaches for state manage-
ment in Flutter. The objective is to determine which approach is the most
suitable approach for managing state in Flutter. In order to meet this objec-
tive, an evaluation will be carried out by evaluating the state management
approaches setState, InheritedWidget, BLoC, Provider, Riverpod, Redux and
MobX. An example application will be implemented for every approach.
Those applications will be assessed by qualitative and quantitative criteria
based on the requirements of state management systems for Flutter.

The result, in addition of the evaluation assessments, is also a recommenda-
tion which approach to use for a specific use case.
Z U S A M M E N FA S S U N G

In Flutter gibt es diverse Ansätze und Lösungsmöglichkeiten, den Zustand
einer mobilen Anwendung zu verwalten. Flutter ist ein beliebtes Cross-Platt-
form-Framework, mit dem sich Anwendungen für iOS, Android, Web, Win-
dows, Linux und macOS erstellen lassen, die den gleichen Quelltext verwen-
den. Damit sollen in der Entwicklung Aufwände gespart werden können
und die Komplexität reduziert werden. Flutter baut auf eine deklarative Be-
nutzeroberfläche, welche anhand eines Zustands erstellt wird. Die Verwal-
tung dieses Zustands ist entscheidend für die Architektur und die Funktion
einer Anwendung.

In dieser Ausarbeitung werden verschieden bereits etablierte Ansätze zur
Verwaltung des Zustands einer Flutter-Anwendung dargestellt und unter-
sucht. Dabei ist das Ziel, herauszufinden, welcher Ansatz am besten zum
Verwalten des Zustands einer Flutter-Anwendung geeignet ist. Dafür wird
eine Evaluation für die Zustandsverwaltungssysteme setState, InheritedWid-
get, BLoC, Provider, Riverpod, Redux und MobX durchgeführt. Grundlage
dieser Evaluation ist die Entwicklung einer Beispielanwendung für jedes Zu-
standsverwaltungssystem und die Bewertung dieser anhand von qualitative
und quantitativen Bewertungskriterien, die anhand der Anforderungen an
Zustandsverwaltungssysteme definiert werden.

Das Ergebnis der Arbeit stellt neben den Resultaten der Evaluation auch
eine Empfehlung dar, welches Zustandsverwaltungssystem für welchen An-
wendungsfall am besten genutzt werden sollte.
I N H A LT S V E R Z E I C H N I S

i thesis
1 einleitung                                                                           2
  1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    .   .    3
  1.2 Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   .   .    3
  1.3 Gliederung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    .   .    4
  1.4 Methodik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    .   .    5
2 grundlagen                                                                           6
  2.1 Flutter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   .   .    6
      2.1.1 Einordnung der Rolle für die Entwicklung von Apps                 .   .    6
      2.1.2 Technischer Überblick . . . . . . . . . . . . . . . . . .         .   .    7
      2.1.3 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . .       .   .    8
  2.2 Automatisiertes Testen . . . . . . . . . . . . . . . . . . . . . .      .   .   10
  2.3 Zustandsverwaltung . . . . . . . . . . . . . . . . . . . . . . .        .   .   11
      2.3.1 Auswahl . . . . . . . . . . . . . . . . . . . . . . . . . .       .   .   12
      2.3.2 Mitgelieferte Werkzeuge . . . . . . . . . . . . . . . . .         .   .   13
      2.3.3 Business Logic Components (BLoC) . . . . . . . . . .              .   .   15
      2.3.4 Provider . . . . . . . . . . . . . . . . . . . . . . . . . .      .   .   16
      2.3.5 Riverpod . . . . . . . . . . . . . . . . . . . . . . . . . .      .   .   18
      2.3.6 Redux . . . . . . . . . . . . . . . . . . . . . . . . . . .       .   .   19
      2.3.7 MobX . . . . . . . . . . . . . . . . . . . . . . . . . . . .      .   .   21
3 analyse                                                                             22
  3.1 Anforderungsanalyse . . . . . . . . . . . . . . . . . . . . . . .       .   .   22
      3.1.1 Allgemeine Anforderungen . . . . . . . . . . . . . . .            .   .   22
      3.1.2 Spezifische Anforderungen . . . . . . . . . . . . . . .           .   .   24
      3.1.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . .           .   .   24
  3.2 Bewertungskriterien . . . . . . . . . . . . . . . . . . . . . . .       .   .   25
      3.2.1 Änderbarkeit/Skalierbarkeit . . . . . . . . . . . . . .           .   .   25
      3.2.2 Testbarkeit . . . . . . . . . . . . . . . . . . . . . . . . .     .   .   25
      3.2.3 Effizienz . . . . . . . . . . . . . . . . . . . . . . . . . .     .   .   26
      3.2.4 Komplexität / Wartbarkeit . . . . . . . . . . . . . . .           .   .   26
      3.2.5 Verständlichkeit / Lesbarkeit . . . . . . . . . . . . . .         .   .   27
      3.2.6 Dokumentierung . . . . . . . . . . . . . . . . . . . . .          .   .   28
      3.2.7 Strukturbestimmung . . . . . . . . . . . . . . . . . . .          .   .   28
  3.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . .         .   .   29
4 umsetzung und realisierung                                                          30
  4.1 Anforderungen an die Beispielanwendung . . . . . . . . . .              .   .   30
      4.1.1 Implizierte Anforderungen . . . . . . . . . . . . . . .           .   .   30
      4.1.2 Explizite Anforderungen . . . . . . . . . . . . . . . .           .   .   31
  4.2 Konzeption einer Beispielanwendung . . . . . . . . . . . . .            .   .   32
inhaltsverzeichnis                               vii

      4.2.1 Anwendung der impliziten Anforderungen                                                 .   .   .   .   .   .   .   32
      4.2.2 Wireframes . . . . . . . . . . . . . . . . . . .                                       .   .   .   .   .   .   .   33
  4.3 Vorgehen bei der Implementierung . . . . . . . . . .                                         .   .   .   .   .   .   .   33
  4.4 Ergebnis der Beispielanwendung . . . . . . . . . . .                                         .   .   .   .   .   .   .   35
      4.4.1 Versuchsaufbau . . . . . . . . . . . . . . . . .                                       .   .   .   .   .   .   .   35
5 evaluation                                                                                                                   37
  5.1 setState . . . . . . . . . . . . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   37
  5.2 InheritedWidget . . . . . . . . . . . . . . . . . . . . .                                    .   .   .   .   .   .   .   37
      5.2.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   38
      5.2.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   38
  5.3 BLoC . . . . . . . . . . . . . . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   41
      5.3.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   41
      5.3.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   42
  5.4 Provider . . . . . . . . . . . . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   45
      5.4.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   45
      5.4.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   46
  5.5 Riverpod . . . . . . . . . . . . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   49
      5.5.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   49
      5.5.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   50
  5.6 Redux . . . . . . . . . . . . . . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   53
      5.6.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   53
      5.6.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   54
  5.7 MobX . . . . . . . . . . . . . . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   57
      5.7.1 Implementierung . . . . . . . . . . . . . . . .                                        .   .   .   .   .   .   .   57
      5.7.2 Bewertung . . . . . . . . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   57
  5.8 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   60
6 fazit                                                                                                                        61
  6.1 Empfehlungen . . . . . . . . . . . . . . . . . . . . . .                                     . . . . . . .               62
  6.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . .                                   . . . . . . .               62

ii appendix
a testergebnisse                                                                                                               64
   a.1 Metriken: main . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   64
   a.2 Metriken: inheritedwidget       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   65
   a.3 Metriken: bloc . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   66
   a.4 Metriken: provider . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   67
   a.5 Metriken: riverpod . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   68
   a.6 Metriken: redux . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   69
   a.7 Metriken: mobx . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   70

   literatur                                                                                                                   71
ABBILDUNGSVERZEICHNIS

Abbildung 2.1   Architekturdiagramm von Flutter [Goo21a] . . .          .   .   .   .    7
Abbildung 2.2   Flutter Widget Tree . . . . . . . . . . . . . . . . .   .   .   .   .   10
Abbildung 2.3   Flutter Widget Tree bei setState . . . . . . . . . .    .   .   .   .   13
Abbildung 2.4   Datenfluss in Redux [GF18, Kap.1.3.3] . . . . . .       .   .   .   .   20
Abbildung 4.1   Wireframes der Beispielanwendung . . . . . . .          .   .   .   .   33
Abbildung 4.2   Umsetzung der Wireframes in Flutter unter iOS           .   .   .   .   35
TA B E L L E N V E R Z E I C H N I S

Tabelle 3.1      Bewertungsmatrix . . . . . . . . . . . . . . . . . . . . . . 29
Tabelle 5.1      Ergebnisse der Evaluation . . . . . . . . . . . . . . . . . 60
LISTINGS

Listing 2.1    Aufbau eines einfachen Flutter-Widgets in Dart . . . .             7
Listing 2.2    Vereinfachte Darstellung eines Widgets als Methode
               [Win20] . . . . . . . . . . . . . . . . . . . . . . . . . . . .    8
Listing 2.3    StatefulWidget . . . . . . . . . . . . . . . . . . . . . . . .     9
Listing 2.4    Aufbau eines InheritedWidget . . . . . . . . . . . . . . .        14
Listing 2.5    Aufbau einer ChangeNotifier-Klasse für Provider . . .             16
Listing 2.6    Injektion und Abruf von Provider-Klassen . . . . . . . .          17
Listing 2.7    Zustandsklasse in Riverpod . . . . . . . . . . . . . . . .        18
Listing 2.8    ConsumerWidget . . . . . . . . . . . . . . . . . . . . . .        19
Listing 4.1    Anzahl der Rendervorgänge . . . . . . . . . . . . . . . .         36
Listing 5.1    Aufbau eines einfachen Flutter-Widgets in Dart . . . .            39
Listing 5.2    Anzahl der Rendervorgänge bei InheritedWidget . . .               40
Listing 5.3    Nesting bei InheritedWidgets in app.dart [Fra22] . . .            40
Listing 5.4    Verwendung eines StreamBuilder in cart_button.dart
               [Fra22] . . . . . . . . . . . . . . . . . . . . . . . . . . . .   42
Listing 5.5    Test des Cart BLoC in cart_bloc_test.dart [Fra22] . . . .         43
Listing 5.6    Anzahl der Rendervorgänge bei BLoC . . . . . . . . . .            44
Listing 5.7    Widget-Test bei Provider in total_price_test.dart [Fra22]         47
Listing 5.8    Anzahl der Rendervorgänge bei Provider . . . . . . . .            48
Listing 5.9    Vergleich des Abrufs von Zuständen zwischen Flutter
               und Provider . . . . . . . . . . . . . . . . . . . . . . . . .    48
Listing 5.10   Widget-Test bei Riverpod in total_price_test.dart [Fra22]         51
Listing 5.11   Anzahl der Rendervorgänge bei Riverpod . . . . . . . .            52
Listing 5.12   Anzahl der Rendervorgänge bei Redux . . . . . . . . .             55
Listing 5.13   Anzahl der Rendervorgänge bei MobX . . . . . . . . . .            58
ABKÜRZUNGSVERZEICHNIS

BLoC Business Logic Components

MI   Maintainability Index

CI   Continous-Integration-System
Teil I

THESIS
EINLEITUNG
                                                                               1
In der mobilen Software-Entwicklung dominieren seit Jahren die beiden mo-
bilen Betriebssysteme iOS und Android. |[Osd] Dies macht es für die Ent-
wicklung von mobilen Anwendungen erforderlich, ein Programm für beide
Plattformen zu entwickeln. Da durch die Entwicklung von zwei ähnlichen
Anwendungen aus rein technischen Gründen größere Aufwände bei der Ent-
wicklung entsteht, sind seit mehreren Jahren sogenannte Cross-Plattform
Technologien in Verwendung. Diese ermöglichen es, im Optimalfall mit der
Entwicklung einer Anwendung mehrere Plattformen auf einmal abdecken
zu können.

In dieser Ausarbeitung wird dabei die Cross-Plattform Technologie Flutter
betrachtet. Die erste Version von Flutter wurde am 12. Mai 2017 [Bra17] ver-
öffentlicht und unterstützt mit der Version 1.20 die Möglichkeit eine Anwen-
dung sowohl als App auf Android und iOS, als Desktop-Anwendung für
macOS, Linux und Windows sowie als Website auszuspielen. [Goo22a] Wei-
tere Details zur Flutter-Technologie werden im entsprechenden Grundlagen-
Kapitel behandelt.

Dabei wird mit dieser Ausarbeitung eine konkrete Problemstellung, die sich
aus der Architektur von Flutter und ähnlichen Werkzeugen wie React behan-
delt. Die Problemstellung umgreift die Frage, welche Software-Architektur
und Werkzeuge gewählt werden müssen, um den Zustand der Anwendung
oder einzelnen Bestandteilen der Anwendung effizient und architekturell
sinnvoll zu verwalten.

Anhand von entsprechend der Problemstellung definierten Bewertungskri-
terien wird durch eine Evaluation verschiedener bestehender Lösungsan-
sätze untersucht, welcher sich am besten zur Lösung der Problemstellung
eignet.

Im Folgenden wird in diesem Einleitungskapitel näher erläutert, welche Mo-
tivation zur Wahl dieses Thema führt, welche konkrete Zielsetzung verfolgt
wird, welche Gliederung gewählt wird und anhand welcher Methodik vor-
gegangen wird.
1.1 motivation       3

1.1   motivation

Bei der Entwicklung von mobilen Anwendungen wie beispielsweise im Agen-
turgeschäft stellen sich immer wieder Architektur-Fragen, welche den Erfolg
eines Projektes maßgeblich mitentscheiden können. Bei Flutter stellt sich hier
beispielsweise die Frage, wie man am besten den Zustand der Anwendung
verwalten kann. Auf diese Frage eröffnet sich den Entwickler*innen verschie-
dene Lösungsmöglichkeiten. Dabei wird aktuell weder seitens der offiziellen
Entwicklungsdokumentation von Flutter noch in den Online-Publikationen
eine eindeutige Empfehlung getroffen. Erschwerend bei der Entscheidungs-
findung kommt hinzu, dass es im Moment eine große Anzahl an verschie-
denen Ansätzen zur Lösung des Problems gibt. Alleine die offizielle Flut-
ter Dokumentation umfasst nach aktuellen Stand eine Aufzählung von 13
verschiedenen Ansätzen, das Problem der Zustandsverwaltung (engl. state
management) zu lösen. [Goo21d]

Die Entscheidung für einen bestimmten Architekturentwurf für die Zustands-
verwaltung ist dabei eine schwierig umkehrbare Entscheidung, da sie Aus-
wirkungen auf diverse Komponenten einer Anwendung hat. Dies kann un-
ter anderem dazu führen, dass bei einer späteren Änderung der Zustands-
verwaltung größere Änderungen an dem Quelltext der Anwendung nötig
sind.

Dies zeigt auf, dass die Entscheidung für die dem Anwendungsfall am bes-
ten entsprechende Zustandsverwaltung eine signifikant wichtige Entschei-
dung im Entwicklungsablauf einer Flutter-Anwendung ist. Daher soll diese
Ausarbeitung dabei unterstützen, auf Grundlage einer wissenschaftlichen
Evaluation verschiedener Lösungsansätze die für die Entwicklung beste Op-
tion wählen zu können.

1.2   zielsetzung

Das übergeordnete Ziel dieser Ausarbeitung ist es, den für die Entwicklung
von Flutter-Anwendungen oder bestimmten Anwendungsfällen am besten
geeignete Ansatz für die Zustandsverwaltung zu finden. Im Detail soll dabei
gezeigt werden, welche Lösungsansätze für welchen Anwendungsfall am
besten, begründet geeignet sind.

Zur Erreichung dieses übergeordneten Zieles ist es erforderlich, einige Zwi-
schenziele zu definieren. Das Erste Ziele ist dabei, zu analysieren, welche
Anforderungen aktuell an die Zustandsverwaltung in Flutter gestellt wer-
den, um im nächsten Schritt geeignete Kriterien für die Bewertung von Lö-
sungsansätzen zu finden.
1.3 gliederung      4

Dabei ist es auch ein Ziel, diese Bewertungsansätze möglichst objektiv de-
finieren zu können, da die Auswahl von geeigneten Kriterien entscheidend
ist für die Aussagekraft der Evaluation.

1.3   gliederung

Für die Strukturierung der Arbeit, wurde die Arbeit in verschiedene Kapitel
und Unterkapitel gegliedert. Zur Erreichung der Ziele und Unterziele bietet
sich folgende Struktur an.

Zu Beginn wird mit der Einleitung ein grober Überblick über die Ausarbei-
tung sowie Informationen zur Methodik vermittelt.

Im Anschluss wird im Grundlagenkapitel anhand von Literaturrecherchen
und Quelltext-Beispielen die für die Arbeit grundlegenden Konzepte darge-
stellt. Dabei insbesondere die Cross-Plattform-Technologie Flutter näher dar-
gestellt und Architekturentwürfe für diese vertieft. Außerdem wird erläutert,
welche Probleme die Entwicklung mit einem deklarativen Komponentensys-
tem wie bei Flutter mit sich bringen und mögliche Lösungsansätze aus der
Literatur diskutiert.

Darauf aufbauend werden im Analyse-Kapitel die Bewertungskriterien und
Richtlinien für die spätere Evaluation erarbeitet.

Für die Durchführung der Evaluation wird im Entwurf- und Realisierungs-
Kapitel ein möglicher Versuchsaufbau konstruiert, welcher geeignet ist, die
beschriebenen Lösungsansätze anhand der im Analyse-Kapitel erarbeiteten
Kriterien zu evaluieren. Dabei werden auch Aspekte der Realisierung proto-
kolliert.

Im Evaluationskapitel werden die Ergebnisse aus dem Entwurfs- und Real-
isierung-Kapitel anhand der im Analyse-Kapitel gewonnen Kriterien bewer-
tet und die Ergebnisse analysiert. Zum Schluss findet eine Gesamtbewertung
der Lösungsansätze statt.

Abschließend wird die Bewertung in einem Gesamtzusammenhang gesetzt
und eine Empfehlung abgegeben, sowie mögliche weitere Forschungsfragen
diskutiert.
1.4 methodik      5

1.4   methodik

Für die Beschreibung der möglichen Lösungsansätze für die Zustandsver-
waltung, wird per Literaturrecherche auf verschiedene Ansätze eingegangen
und diese näher erläutert.

Im Anschluss wird mittels Literaturrecherche mögliche Anforderungen an
Zustandsverwaltungssysteme zusammengefasst. Darauf aufbauend, werden
Bewertungskriterien konstruiert, die auf die einzelnen Anforderungen ein-
zahlen sollen.

Für die Umsetzung und Realisierung werden aus diesen Bewertungskriteri-
en und Anforderungen neue implizite und explizite Anforderungen an ei-
ne Beispielanwendung konstruiert. Die Beispielanwendung soll später als
Untersuchungsobjekt für die Evaluation dienen. Zusätzlich wird ein Ver-
suchsaufbau konstruiert und beschrieben.

Für die Evaluation wird für jedes untersuchte Zustandsverwaltungssystem
eine eigene Anwendung implementiert und anhand der definierten Kriteri-
en untersucht. Dabei kommen hauptsächlich qualitative Bewertungsmetho-
den zum Einsatz. Zusätzlich dazu werden als quantitative Bewertungsme-
thoden der Maintainability Index eingesetzt und eine automatisierte Teststre-
cke zur Durchführung von Leistungstests eingerichtet. Alle Messungen und
Bewertungen erfolgen in einer nachvollziehbaren, reproduzierbaren Docker-
Umgebung.
GRUNDLAGEN
                                                                               2
Dieses Kapitel befasst sich mit den Grundlagen für die nachfolgende Eva-
luation. Dabei wird sowohl Literatur zum Thema ausgewertet, als auch Kon-
zepte an Hand von Beispielen erläutert.

2.1     flutter

Um die konkrete Problemstellung im Detail verstehen zu können, ist es erfor-
derlich, die grundlegende Plattform näher zu betrachten. Dabei wird zu Be-
ginn erst ein Überblick über das Cross-Plattform-Werkzeug Flutter gegeben
und im Anschluss detaillierter auf einzelne Aspekte, die für die Erfassung
der Problemstellung von Relevanz sind, eingegangen.

2.1.1    Einordnung der Rolle für die Entwicklung von Apps

In diesem Abschnitt wird ein grober Überblick über die aktuelle Situati-
on bei der Entwicklung von Apps gegeben. Flutter ist ein von dem US-
amerikanischen Digitalunternehmen Google entwickeltes Cross-Plattform-
Werkzeug, welches es ermöglichen soll, mobile Anwendungen (Apps) für
die Smartphone-Betriebssysteme iOS und Android mit einer gemeinsamen
Code-Basis zu entwickeln. [Ama18] In der etablierten App-Entwicklung ist
es weit verbreitet, zwei separate Anwendungen für die beiden dominieren-
den Betriebssysteme iOS und Android zu entwickeln. Eine Analyse von Ap-
pfigures zeigt dabei, dass im Apple App Store 55 % der analysierten Apps
auf Swift basieren, welches für die sogenannte native iOS-Entwicklung ge-
nutzt wird, und im Google Play Store 38 % der analysierten Apps auf Kotlin
basieren, welches dem Pendant zu Swift für Android entspricht.[App22] Das
Entwickeln von zwei getrennten Anwendungen bringt dabei das Erforder-
nis mit sich, Quelltext zu duplizieren, da für Android entwickelter Software
nicht mit iOS kompatibel ist und umgekehrt.

Neben Flutter existieren auch andere Cross-Plattform Werkzeuge, die das
gleiche Problem lösen möchten. Diese werden jedoch nicht in dieser Aus-
arbeitung näher betrachtet. Erwähnenswert jedoch sollte sein, dass das von
2.1 flutter   7

Meta entwickelte React Native SDK vom Design ähnlich ist und Flutter die-
ses “well preserved” ([Wu18]) hat, sodass auch dieses SDK von den hier
in der Ausarbeitung beschriebenen Problemen betroffen sein kann und die
Ergebnisse somit als Basis für eine ähnliche Evaluation verwendet werden
könnten.

2.1.2   Technischer Überblick

Dieser Abschnitt vermittelt die technischen Grundlagen für die Flutter Tech-
nologie. Flutter und die damit entwickelten Anwendungen werden mit der
Dart-Programmiersprache entwickelt. Dart lässt sich dabei mit den bereits
etablierten Programmiersprachen wie Java oder JavaScript vergleichen, wie
in Listing 2.1 zu sehen ist, und ist damit eine objektorientierte, optional sta-
tisch typisierte Programmiersprache[Goo21f].

            Listing 2.1: Aufbau eines einfachen Flutter-Widgets in Dart

import ’package:flutter/material.dart’;

class ExampleWidget extends StatelessWidget {
    const ExampleWidget({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return const Row(children: [Text(’Hello World’), Text(’123’)];
    }
}

Von der Architektur her ist Flutter in einem Schichtentwurf aufgebaut, wie
in Abbildung 2.1 zu sehen ist.

            Abbildung 2.1: Architekturdiagramm von Flutter [Goo21a]
2.1 flutter   8

Die unterste Embedder-Schicht ist dabei für jede der unterstützten Plattfor-
men einzeln implementiert und stellt den oberen Schichten Ressourcen zur
Verfügung, wie beispielsweise ein Zeichnungsbereich, indem die Benutzero-
berfläche gerendert werden kann. Zudem können hier native Erweiterungen
eingebunden werden, die es der App später ermöglicht, von Flutter / Dart
aus auf native Betriebssystemfunktionen wie die Kamera zuzugreifen.

Die mittlere Engine-Schicht beinhaltet die meisten Performance-kritischen
Komponenten und stellt eine universelle Plattform für das Framework zur
Verfügung so beinhaltet sie beispielsweise die Rendering-Engine. Anders als
bei anderen Cross-Plattform-Frameworks wird in Flutter die komplette Be-
nutzeroberfläche nicht mit nativen Design-Elementen dargestellt, sondern
mit dieser eigenen Rendering-Engine gezeichnet. Dies bietet dabei den Vor-
teil, dass Design-Konzeptionen auf allen Plattformen gleich aussehen.

Die obere Framework-Schicht setzt auf den beiden unteren Schichten auf und
bietet die Schnittstellen, die zur Entwicklung von Apps benötigt werden.
Zudem beinhaltet sie einen umfangreichen Katalog an Standard-Widgets,
die sich am Design der beiden Betriebssysteme iOS und Android orientieren.
Diese Widgets sind in den Cupertino-Katalog für das iOS-ähnliche Design
und in den Material-Katalog für das besonders auf Android oft genutzte
Material-Design eingeordnet.

Das Konzept des Widgets, welches bereits öfters bisher erwähnt worden ist,
wird in dem folgenden Kapitel näher beleuchtet.

2.1.3    Widgets

Eines der wichtigsten Bestandteile des Flutter-Frameworks sind die soge-
nannten Widgets. Der Satz “Everything is a widget” [Win20, Kap.1.9] wird
in der Literatur oft verwendet, und bringt zum Ausdruck, dass Flutter das
Konzept des Widgets für viele Anwendungsfälle nutzt. So kann ein Wid-
get diverse Aufgaben übernehmen wie beispielsweise das Rendern einer UI-
Komponente, Animationen oder das Anordnen von anderen Widgets. Zur
Darstellung der Benutzeroberfläche benutzen also Widgets Kompositionen
von diversen Widgets. So lassen sich beispielsweise mit einer Row, wie im
Listing 2.1 zu sehen ist, mehrere Widgets nebeneinander anzeigen.

        Listing 2.2: Vereinfachte Darstellung eines Widgets als Methode [Win20]

UI Widget(state)

Ein wichtiger Unterschied zu klassischen deklarativen UI-Frameworks ist,
dass Widgets nur die Anleitung zum Bauen einer Benutzeroberfläche bein-
halten, jedoch nicht den aktuellen Zustand des Widgets. Im Detail bedeutet
2.1 flutter     9

dies, dass die build()-Methode eines Widgets keinerlei Nebeneffekte haben
soll und daher eine schnelle Ausführungszeit zu erwarten ist. Bildlich lässt
sich ein Widget als Funktion darstellen, welche den aktuellen Zustand (engl.
State) erhält und daraus die entsprechende Benutzeroberfläche generiert wie
in Listing 2.2 vereinfacht dargestellt. Ein Widget erhält die darzustellenden
Daten und generiert daraus die entsprechende Benutzeroberfläche.

In Flutter wird bei Widgets grundsätzlich zwischen StatelessWidget und
StatefulWidget unterschieden.

Ein StatelessWidget hat grundsätzlich keinen veränderbaren Zustand. Dies
heißt, dass alle Klassenvariablen unveränderlich sein sollen. In Dart wird
dies mit dem Modifikator final gekennzeichnet. Daraus wird impliziert,
dass alle Informationen zum Zustand des Widgets aus den im Konstruk-
tor übergebenen Werten stammen müssen. Somit wird das Widget nur neu
gebaut, wenn sich Änderungen an den Werten des Konstruktors durch Än-
derungen in der Hierarchie darüber liegenden Widgets ergeben.

Ein StatefulWidget auf der anderen Hand besteht aus zwei Klassen. Zum
einen dem StatefulWidget zum anderen dem State dieses Widgets. Diese
Widgets vereinen die Möglichkeiten eines StatelessWidget mit der Mög-
lichkeit, den Zustand des Widgets selbstständig zu verändern. Dies wäre
beispielsweise bei einem Zähler relevant, wenn der Zähler erhöht werden
soll. Zustandsänderungen werden dabei über die, wie in Listing 2.3 gezeig-
te, setState Methode vorgenommen, damit auch die Änderung an das Fra-
mework kommuniziert wird. Intern wird dabei das Widget als dirty mar-
kiert, welches dazu führt, dass die Build-Methode des States erneut aufge-
rufen wird durch das Framework. Entscheidend ist dabei, dass der State
persistent bleibt, auch wenn sich beispielsweise Konstruktor-Parameter der
StatefulWidget-Klasse ändern.

                          Listing 2.3: StatefulWidget

class Counter extends StatefulWidget {
    const Counter({Key? key}) : super(key: key);

    @override
    State createState() => _CounterState();
}

class _CounterState extends State {
    int _counter = 0;

    void _incrementCounter() {
        setState(() {
            _counter++;
        });
    }
2.2 automatisiertes testen         10

      @override
      Widget build(BuildContext context) {
          return Column(
              children: [
                  Text("Count: $_counter"),
                  IconButton(icon: Icon(Icons.add),
                    onPressed: _incrementCounter),
              ],
          );
      }
}

Durch die Kombination von diversen Widgets
entsteht so ein Widget-Baum, wie in Abbil-
dung 2.2 zu sehen ist. Dieser Baum lässt sich
auch mittels Hilfswerkzeugen traversieren, ist
aber von der Grundstruktur für einen unidirek-
tionalen Datenfluss ausgelegt. Dies bedeutete,
dass Widgets nur untergeordnete Widgets durch
Änderung derer Zustände verändern können sol-
len. Übergeordnete Widgets können somit - je-
                                               Abbildung 2.2: Flutter Wid-
denfalls nicht direkt - verändert werden.
                                                                    get Tree

Anders als bei der nativen Entwicklung wird hier
also die Benutzeroberfläche deklarativ program-
miert. Bei der Entwicklung wird also festgelegt, wie welches Element bei
welchem Zustand auszusehen hat, ohne dabei den Kontrollfluss beschreiben
zu müssen.

Zusammenfassend wird festgehalten, dass der Zustand und die Verwal-
tung des Zustands von Widgets einen wichtigen Teil im Lebenszyklus einer
Flutter-Anwendung ist. Dies gibt schon einen Vorgriff darauf, dass hier eine
Lösung gefunden werden muss, welche auf diverse Szenarien anwendbar
sein muss.

2.2   automatisiertes testen

Um Flutter-Anwendungen automatisiert testen zu können, bestehen in Flut-
ter die drei Testkategorien “unit testing, widget testing [...][und] integration
testing” [AA21, S. 21].

Unit-Tests werden zum automatisierten Testen einzelner Funktionen oder
Klassen eingesetzt. Tests werden dabei als Lambda-Funktion konstruiert.
Mit mitgelieferten Werkzeugen, lassen sich Werte darauf überprüfen, ob sie
den erwarteten Wert entsprechen.
2.3 zustandsverwaltung          11

Widget-Tests testen einzelne oder mehrere Widgets darauf, ob sie dem ge-
wünschten Verhalten entsprechen. Die Widget-Tests können dabei ohne die
Verwendung eines iOS- oder Android-Simulator innerhalb von Flutter ge-
testet werden. [Goo21e] Dafür existieren diverse Werkzeuge in der Flutter-
Testing-Bibliothek, womit sich Widgets erstellt werden können oder bestimm-
te Eigenschaften von Widgets überprüft werden können.

Integration-Tests testen die Anwendung als ganzes und prüfen somit, ob die
jeweiligen Komponenten auch korrekt untereinander funktionieren. [AA21,
S. 21] Diese Art von Tests sind allerdings für die Evaluation nicht relevant
und werden daher nicht näher behandelt.

2.3   zustandsverwaltung

Nachdem nun die Grundlagen des Flutter-Frameworks und die Details der
Zustandsverwaltung der Widgets im letzten Kapitel erläutert wurden, kann
jetzt die Zustandsverwaltung im Detail betrachtet werden.

Windmill fasst den Komplex der Zustandsverwaltung in Flutter in seinem
Standardwerk Flutter in Action wie folgt zusammen:

“State management is a combination of passing data around the app, but also
re-rendering pieces of your app at the right time. All the re-rendering in Flut-
ter is dependent on the State object and its lifecycle.” [Win20, Kap.8.1.2]

Daraus ergibt sich, dass die State-Klasse in der Zustandsverwaltung von
Flutter eine wichtige Rolle spielt, und alle Ansätze dieses Konzept benut-
zen müssen, um in das Flutter-Framework integrierbar zu sein. Windmill
beschreibt dabei die Aufgabe der Zustandsverwaltung eher auf einer Ebene
des Datenflusses und des Ablaufs der Neu-Erstellung der Benutzeroberflä-
che.

Arshad sieht die Zustandsverwaltung dabei eher auf einer eher ablaufszen-
trierten Sichtweise indem er die Aufgabe der Zustandsverwaltung wie folgt
analysiert:

“State management is simply a technique, or multiple techniques, used to
take care of the changes that occur in your application.” [Ars21, Kap.1]

Als Beispiele nennt er dabei, das Reagieren auf Interaktionen mit der An-
wendung oder die Beibehaltung des Datenflusses über mehrere Screens hin-
weg.
2.3 zustandsverwaltung          12

Beide Sichtweisen haben gemein, dass die Grundaufgabe der Zustandsver-
waltung die Sicherstellung eines korrekten Zustands der Anwendung, ein-
zelner Screens oder einzelner Widgets sein muss, sowie die mögliche Über-
führung dieses Zustands in einen neuen Zustand als Reaktion auf Verände-
rungen.

Nachdem nun eingeführt wurde, was unter einer Zustandsverwaltung in
Flutter zu verstehen ist, werden nun mögliche bestehende Ansätze für eine
Zustandsverwaltung skizziert, um im weiteren Verlauf der Ausarbeitung im
Analyse- und Evaluationskapitel diese eingehender zu untersuchen.

2.3.1   Auswahl

Zur Auswahl der zu evaluierenden Lösungsansätze wird die Aufzählung
von Zustandsverwaltungs-Ansätzen aus der Flutter-Dokumentation [Goo21d]
als Grundlage verwendet. In dieser Aufzählung werden die Zustandsverwal-
tungssysteme Provider, Riverpod, setState, InheritedWidget & -Model, Re-
dux, Fish-Redux, BLoC / RX, GetIt, MobX, Flutter Commands, Binder, GetX,
states_rebuilder und Triple Pattern genannt.

Da eine Evaluation aller aufgezählten Zustandsverwaltungssysteme, den Um-
fang dieser Ausarbeitung überschreiten würde, wurde die Auswahl einge-
schränkt.

Die Ansätze setState und InheritedWidget werden in die Evaluation mit auf-
genommen, da sie zu der Grundausstattung der Flutter-Standardbibliothek
gehören, und somit einen Basiswert für Zustandsverwaltungssysteme bilden
und somit relevant sind. Das BLoC-Pattern wird aufgenommen, da es auf-
grund der großen Verbreitung in der Literatur relevant ist. Die Bibliothek
Provider wird ebenfalls aufgenommen, da es sich laut der Dokumentati-
on um den empfohlenen Ansatz für Zustandsverwaltung in Flutter handelt,
und die Bibliothek mit 6132 Like-Angaben [Goo22b] zu einer der beliebtes-
ten Flutter-Pakete [Goo] auf der Plattform handelt. Riverpod wird ebenfalls
aufgenommen, da dies den Ansatz von Provider weiterentwickelt und somit
geprüft werden kann, ob diese Bibliothek tatsächlich besser abschneidet als
die Original-Bibliothek. Zuletzt werden die Bibliotheken MobX und Redux
aufgenommen, da sie besonders aufgrund ihrer Herkunft aus dem React-
Ökosystem eine besondere Relevanz für die Evaluation haben, um ebenfalls
feststellen zu können, ob bereits in React verbreitete Ansätze auch in Flutter
sinnvoll einsetzbar sind.
2.3 zustandsverwaltung          13

2.3.2     Mitgelieferte Werkzeuge

Die erste Kategorie der Zustandsverwaltungssysteme umfasst jene, welche
ohne eine zusätzliche Bibliothek auskommen und somit de facto im Flutter
Framework mitgeliefert werden. Zunächst werden einfacheren Konzepten
und Werkzeugen vorgestellt, anschließend folgen die komplexeren Konzepte
und Werkzeuge vorgestellt.

2.3.2.1    setState

Die wohl grundlegendste Möglichkeit, den Zustand in einer Flutter An-
wendung zu verwalten stellt das ausschließliche Benutzen der setState-
Methode dar. Ein Beispiel zur Verwendung wurde bereits in Listing 2.3
in der incrementCounter-Methode eingeführt. Hier findet die Speicherung
des Zustands also durch die direkte Manipulation des States von Stateful-
Widgets statt.

                            Wie vorausgehend beschrieben, muss ein Zu-
                            standsverwaltungssystem aber nicht nur den Zu-
                            stand einzelner Widgets verwalten können, son-
                            dern auch von größeren Ordnungen wie bei-
                            spielsweise von Screens oder der ganzen Anwen-
                            dung. Um dies bei diesem Ansatz erreichen zu
Abbildung 2.3: Flutter Wid- können, wir der Zustand oder Teile des Zustands
               get Tree bei über die Konstruktor innerhalb des Widget-Trees
               setState
                            weiter nach unten gegeben.

                             Anschaulich lässt sich dies durch das Beispiel Ab-
bildung 2.3 darstellen, welches eine Anwendung, die global speichern muss,
welche Person aktuell angemeldet ist, zeigt. Da diese Information in diesem
Beispiel an diversen Stellen innerhalb der Anwendung benötigt wird, ergibt
es Sinn, diese Information weit oben im Baum in Form eines StatefulWidget
namens LoginStateWidget zu speichern, da der Datenfluss innerhalb des
Baums ausschließlich unidirektional von oben nach unten stattfindet. Um
diese Information nun an die Widgets zu kommunizieren, die es benötigen -
in diesem Fall InformationConsumer - muss LoginStateWidget die Informa-
tion per Konstruktor an das nachgelagerte Widget weitergeben. Diese nach-
gelagerten Widgets (A, B, C) müssen dies ebenfalls tun, bis die Information
am Ziel InformationConsumer angekommen ist. Dieses Anwendungsmuster
wird in der Literatur als “lifting state up” [Win20, Kap.8.2] bezeichnet.
2.3 zustandsverwaltung   14

2.3.2.2   InheritedWidget

Neben der Möglichkeit, den Zustand über den Widget-Baum nach unten
weiter zu propagieren, bietet Flutter noch das Konzept InheritedWidget
an. Diese Widgets bilden eine eigene Widget-Gruppe und sind weder den
StatefulWidgets noch den StatelessWidgets zuzuordnen. [Win20, Kap.8.2.1]
InheritedWidgets ermöglichen es nachgeordneten Widgets, auf den Zustand
des Widgets direkt zuzugreifen. Hier muss allerdings beachtet werden, dass
das InheritedWidget immer unveränderlich ist. Dies bedeutet, dass ande-
re Widgets über die Veränderung von Konstruktor-Parametern neue Instan-
zen des Widgets erstellen müssen, um eine Zustandsänderung zu bewirken.
Daher lassen sich diese Widgets oft in Kombination mit StatefulWidgets,
welche für die Manipulation des Zustands zuständig sind, vorfinden.

Im Beispiel Listing 2.4 kann man erkennen, dass das Widget lediglich die
Daten lagert, welche zur Verfügung gestellt werden sollen. In diesem Fall
ist dies der aktuelle Benutzende currentUser. Diese*r kann nicht vom In-
heritedWidget selbst geändert werden, sondern hängt von der Eingabe im
Konstruktor ab. Das InheritedWidget wird dabei gemäß dem Motto “Eve-
rything is a widget” in den Widget-Baum integriert. Der Konstruktor-Para-
meter child gibt dabei das im Baum untergeordnete Widget an. Über die
updateShouldNotify-Methode wird dem Framework kommuniziert, ob sich
der Zustand im Vergleich zum vorherigen geändert hat, und somit ein Neu-
bauen der Widgets, die dieses InheritedWidget referenzieren, notwendig
ist.

                    Listing 2.4: Aufbau eines InheritedWidget

import ’package:flutter/widgets.dart’;

class UserStore extends InheritedWidget {
  const UserStore({
    Key? key,
    required this.currentUser,
    required Widget child,
  }) : super(key: key, child: child);

    final User currentUser;

    static UserStore? of(BuildContext context) {
      return context.dependOnInheritedWidgetOfExactType();
    }

    @override
    bool updateShouldNotify(UserStore old) {
      return currentUser != old.currentUser;
    }
}
2.3 zustandsverwaltung         15

Anders als bei dem setState-Konzept kann hier direkt der Zustand durch
andere nachgelagerte Widgets referenziert werden. Dafür werden oft Hilfs-
methoden wie in diesem Fall die of-Methode verwendet. Diese greifen auf
Werkzeuge des Frameworks zu, die das InheritedWidget des angebenden
Typen zurückgibt und sicherstellt, dass bei einer Veränderung des das refe-
renzierende Widget auch neu gebaut wird und so die Änderung beachtet
wird.

Dieser Mechanismus wird auch von diversen anderen Zustandsverwaltungs-
systemen verwendet.

Als Erweiterung des InheritedWidget kann man InheritedModel sehen. Da-
bei ist die Funktionsweise äquivalent mit einer Besonderheit. Es besteht hier
die Möglichkeit, Zugriffe und Änderungen nach sogenannten aspects zu
kategorisieren. Somit kann bei komplexeren Zuständen, es ermöglicht wer-
den, dass die Widgets nur dann neu gebaut werden, wenn der betreffende
aspect sich ändert. [Goo21c]

2.3.3   Business Logic Components (BLoC)

Das 2018 auf der Entwicklerkonferenz DartConf vorgestellte BLoC-Pattern
ist im Vergleich zu den bisher vorgestellten Ansätzen ein Design-Pattern zur
Verwaltung von Zuständen und nicht nur ein Werkzeug des Frameworks.
Das Ziel von BLoC ist es, die komplette Logik von der Benutzeroberfläche
zu trennen. [Fau20, S. 17] Die Logik und der Zustand wird dabei in den na-
mensgebenden Business Logic Components (BLoC) verwaltet. Die Kompo-
nenten haben die Aufgabe, Zustands-Ereignisse von Widgets zu empfange
und Widgets zu aktualisieren, wenn sich der Zustand ändert. Diese Kompo-
nenten unterliegen grundlegenden, nicht-verhandelbaren Regeln, welche im
Vortrag von Soares definiert worden sind:

“

    1. Inputs and outputs are simple Streams/Sink only

    2. Dependencies must be injectable and platform agnostic

    3. No platform branching allowed [...]

” [Soa18, 24:04]

Die erste Regel bedeutet, dass BLoC weder Methoden noch Variablen nach
außen freigeben dürfen, sondern nur über Streams und Sinks mit Widgets
kommunizieren. Ein Stream ist dabei in Flutter ein asynchroner Fluss von
2.3 zustandsverwaltung         16

Daten oder Ereignissen. Widgets können diesen Ereignisfluss abonnieren
und werden dann aktualisiert, wenn sich dieser ändert. Ein Sink ist intern
auch eine Art von Stream, welcher aber die Besonderheit hat, dass man von
außen neue Ereignisse hinzufügen kann. Über diesen Sink lassen sich also
Daten und Ereignisse an das BLoC übergeben.

Die zweite Regel sagt aus, dass BLoC keine Abhängigkeiten zur Benutzero-
berfläche haben dürfen. Selbst das Importieren von Flutter-Bibliotheken in
diese Dateien ist verboten. Damit wird erreicht, dass BLoC komplett plattfor-
munabhängig sind, und somit die komplette Benutzeroberfläche theoretisch
ersetzt werden könnte, ohne die Logik ändern zu müssen.

Die dritte Regel legt fest, dass innerhalb von BLoCs keine Unterscheidungen
zwischen Betriebssystemen oder Plattformen vorgenommen werden darf.

Der innere Aufbau der BLoC ist explizit nicht vorgeschrieben, dient aber da-
zu die über die Sinks eingehenden Daten und Ereignisse zu verarbeiten und
anschließend den neuen Zustand über die Streams zurück an die Widgets
zu propagieren. Technisch kommen hier oft Techniken und Werkzeuge aus
der reaktiven Programmierung wie RxDart zum Einsatz.

Jede Seite (engl. Screen) sollte dabei exakt einem BLoC zugeordnet sein. Da-
mit die Widgets auf diesen BLoC zugreifen können, müssen diese injectable
sein - also zwischen mehreren Widgets geteilt. Dies kann unter anderem mit
den bereits in Unterabschnitt 2.3.2 vorgestellten Ansätzen umgesetzt wer-
den.

2.3.4   Provider

Provider ist eine Bibliothek, die auf dem InheritedWidget-Ansatz (siehe
Unterunterabschnitt 2.3.2.2) beruht. Dabei vereinfacht die Bibliothek die Be-
nutzung und Erstellung von Klassen, die Zustände verwalten, und kombi-
niert das InheritedWidget-Konzept unter anderem mit der ChangeNotifer-
Klasse. [Sle20]

Die abstrakte Klasse ChangeNotifier bietet die Möglichkeit Event-Listener
für die erbende Klasse zu registrieren und bei Veränderung über den Aufruf
einer entsprechenden Methode diese zu benachrichtigen, wie in Listing 2.5
in der Methode updateUser gezeigt wird. [Goo21b] Diese Kombinationsmög-
lichkeit wird auch für andere Konzepte und Klassen angeboten. So lassen
sich Provider mit Listnables, also Objekte, die Events emittieren können,
ValueListanble, also Listnables, die nur über Änderungen einer Variable
benachrichtigen, Streams, welche bereits in Unterabschnitt 2.3.3 vorgestellt
worden sind oder Futures kombinieren.
2.3 zustandsverwaltung        17

           Listing 2.5: Aufbau einer ChangeNotifier-Klasse für Provider

class UserStore extends ChangeNotifier {
  var user;
  UserStore(this.user);

    void updateUser(newUser) {
      this.user = newUser;
      notifyListeners();
    }
}

Es kann festgehalten werden, dass Provider im ersten Schritt eine Form von
Dependency Injection zur Verfügung stellt und im zweiten Schritt mit einer
Klasse verknüpft wird, welche über Änderungen informiert, und somit zu
einer Zustandsverwaltungslösung ausgebaut werden kann.

              Listing 2.6: Injektion und Abruf von Provider-Klassen

class ProvidingWidget extends StatelessWidget {
  final user;
  const ProvidingWidget({Key? key, this.user}) : super(key: key);

    @override
    Widget build(BuildContext context) {
      return ChangeNotifierProvider(create: (context) => UserStore(user),
          child: ConsumingWidget());
    }
}

class ConsumingWidget extends StatelessWidget {
  const ConsumingWidget({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
      final user = Provider.of(context).user;
      return Text(user.firstName);
    }
}

Um eine ChangeNotifier als Zustandsverwaltungslösung mit Provider zu
nutzen, ist es erforderlich, die jeweiligen Zustandsklassen (oft Store genannt)
in den Widget-Baum zu integrieren. Dies erfolgt wie in der Klasse Providing-
Widget in Listing 2.6 gezeigt, durch das Erstellen eines Widgets, welche kei-
ne eigene Benutzeroberfläche darstellen, sondern nur Widgets “nach unten”
durchreichen. Nun können die Daten aus der Zustandsklasse von allen Wid-
gets abgerufen werden, die sich im Widget-Baum an einen der nachgelager-
ten Äste der injizierenden Widgets befindet.
2.3 zustandsverwaltung      18

Die nachgelagerten Widgets können dabei, die Zustandsklasse durch eine
einfachen, generischen Methodenaufruf der Provider-Bibliothek abrufen.
Durch die Verwendung von InheritedWidget innerhalb der Bibliothek wird
damit sichergestellt, dass wenn sich Daten in den Zustandsklassen ändern,
referenzierende Widgets über diese Änderung informiert werden und gege-
benenfalls neu gebaut werden.

2.3.5   Riverpod

Riverpod baut auf dem bereits vorgestellten Provider-Konzept auf, ergänzt
es aber mit weiteren Funktionalitäten. Ein großer Unterschied liegt auch dar-
in, dass hier Provider nicht in den Widget-Baum integriert werden müssen.
Zudem ist es hier anders als bei der Provider-Bibliothek möglich, mehre-
re Provider vom gleichen Klassentyp zu unterscheiden. Hier wird nämlich
nicht der Klassentyp zum Abruf der Zustandsklasse im Widget verwendet,
sondern eine Variable. Wie diese im Widget abgerufen wird, ist dabei von
den Entwickler*innen zu entscheiden. Denkbar sind hier beispielsweise das
Verwenden einer globalen Variable oder einer Dependency-Injection-Lösung
wie get_it. Damit wird auch ermöglicht, dass Riverpod im Vergleich zu Pro-
vider keine Abhängigkeit zum Flutter-Framework hat und somit eine reine
Dart-Bibliothek ist. [Gre+21, S. 8]

Durch diese Unabhängigkeit vom Widget-Tree und dem Flutter-Framework
kann hier auf das sogenannte Nesting verzichtet werden. Dieses wird bei
Provider benötigt, um Provider zu initialisieren, die von anderen Providern
abhängen. Riverpod nutzt hier stattdessen eine Methode, mit der bei der
Erstellung eines Providers andere Provider gelesen werden können.

                     Listing 2.7: Zustandsklasse in Riverpod

class UserStore extends StateNotifier {
  UserStore(User initialState) : super(initialState);

    changeFirstName(String newName) {
      state = User(newName);
    }
}

Für die Zustandsklassen selber gibt Riverpod eine eigene Struktur vor. Die
Zustandsklassen erben die generische Klasse StateNotifier. Dabei wird
über das Generic festgelegt, welchen Datentyp der Zustand haben soll. Die-
ser Zustand wird dann der Klasse als vererbte Variable zur Verfügung ge-
stellt. Bei Änderung des Zustands reicht es somit aus, den Inhalt dieser Va-
riable neu zu setzen wie in Listing 2.7 gezeigt wird. Die Benutzeroberfläche
greift dabei ausschließlich auf die Zustands-Variable direkt zu.
2.3 zustandsverwaltung          19

Die Zustandsklasse ist nur dafür zuständig, Methoden zur Änderung dieses
Zustands zur Verfügung zu stellen.

Riverpod akzeptiert neben diesen Zustandsklassen auch die im Provider-
Kapitel (siehe Unterabschnitt 2.3.4) vorgestellen Event-Emitter wie beispiels-
weise ChangeNotifier.

Da wie bereits erwähnt, Riverpod keine Flutter-Bibliothek ist und somit
nicht wie Provider InheritedWidget zum Aktualisieren von referenzieren-
den Widgets benutzt, unterstützt sie von Haus aus noch nicht die Nutzung
innerhalb eines Widgets. Um dies jedoch zu ermöglichen, besteht die Wahl
zwischen zwei Ansätzen, die sich nur semantisch unterscheiden.

                         Listing 2.8: ConsumerWidget

final userStoreProvider = StateNotifierProvider((ref) => UserStore());

class ConsumingWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userStore = ref.watch(userStoreProvider);
    return Text(userStore?.state.firstName);
  }
}

Bei flutter_riverpod werden die bereits eingeführten Widget-Typen State-
fulWidget und StatelessWidget durch neue Typen ergänzt beziehungswei-
se ersetzt. Einer dieser neuen Typen ist ConsumerWidget. Durch das Erben
von dieser Klasse, wird der build-Methode des Widgets eine neue Variable
ergänzt, über die der Zustand von Riverpod-Providern abgefragt werden
kann, wie in Listing 2.8 zu sehen ist. Die hier verwendete watch-Methode
bewirkt, dass das Widget neu gebaut wird, wenn sich der betreffende Provi-
der ändert.

2.3.6   Redux

Redux ist ein Zustandsverwaltungssystem, welches ursprünglich für React
entwickelt worden ist. React ist eine JavaScript-Bibliothek zum Bauen von
Benutzeroberflächen. Da React und Flutter ähnliche Techniken wie Widgets
oder das State-Prinzip verwenden, lässt sich dieser Ansatz auf Flutter und
Dart übertragen.
2.3 zustandsverwaltung        20

Redux basiert auf drei grundlegenden Prinzipien:

     Single source of truth [...]

     State is read-only [...]

     Changes are made with pure functions [...] [GF18, Kap.1.3.2]

Das Prinzip der “Single source of truth” [GF18, Kap.1.3.2] wird dadurch
umgesetzt, dass es für die komplette Anwendung nur einen zusammenge-
fassten Zustand gibt. Es findet hier also keine Aufteilung in diverse Unter-
zustände beispielsweise für einzelne Seiten statt.

             Abbildung 2.4: Datenfluss in Redux [GF18, Kap.1.3.3]

Dieser globale Zustand ist, wie im zweiten Prinzip dargestellt wird, unver-
änderlich. Um den Zustand zu ändern beziehungsweise in diesem Fall zu
ersetzen, ist es erforderlich, sogenannte actions zu emittieren. Actions sind
dabei in Flutter einfache Objekte, die auch Parameter in Form von Variablen
beinhalten können. Diese Action wird dann wie in Abbildung 2.4 anschau-
lich zu sehen ist, über einen Reducer auf den globalen Zustand (engl. store)
angewendet.

Damit wird dann auch das dritte Prinzip umgesetzt. Ein Reducer stellt eine
einfache Funktion dar, welche als Parameter den aktuellen Zustand und die
Action erhält und als Rückgabewert einen neuen Zustand angibt, welcher
den globalen Zustand ersetzt.

Für Dart wurde dies mit der redux Bibliothek umgesetzt. Ähnlich wie bei
Riverpod wird hier noch eine zusätzliche Bibliothek benötigt, die Redux für
das Flutter-Widget-System anwendbar macht. Dafür wird flutter_redux
verwendet. Diese adaptiert das Prinzip der Provider-Bibliothek für Redux-
Stores. So wird der Store mittels eines Provider-Widgets über den Widget-
Tree für nachgelagerte Widgets zur Verfügung gestellt. Auf den Store zu-
gegriffen wird über Selektoren, welche den benötigten Teil des globalen Zu-
stands eingrenzen und per Callback-Funktion zurückgeben. Somit wird über
diese Selektoren auch die Veränderungen der Widgets ausgelöst, wenn sich
der entsprechende Teil des globalen Zustands ändert.
2.3 zustandsverwaltung          21

2.3.7   MobX

MobX ist wie Redux ebenfalls ein Ansatz, der ursprünglich aus dem React-
Ökosystem stammt. MobX stellt sich dabei die Aufgabe, es zu vereinfachen
automatisch diverse Informationen aus dem Anwendungs-Zustand abzulei-
ten. Um dies zu erreichen, gibt es bei MobX fünf grundlegende Konzepte:

“

    • Observables [...]

    • Computed Values [...]

    • Reactions [...]

    • Actions [...]

” [Mez18, S. 130]

Observables bilden die Grundlage eines Zustands. Ein Observable stellt einen
Wert dar, welcher beobachtet werden kann und somit auf Änderungen rea-
giert werden kann. Von Observables werden alle anderen Informationen ab-
geleitet.

Mit Computed Values lassen sich ein oder mehrere Observables kombi-
nieren oder einer weiteren Verarbeitung unterwerfen. Wenn sich eines der
zugrundeliegenden Observables ändert, wird auch dieser Wert neu berech-
net.

Reactions reagieren auf Änderungen von Observables oder Computed Va-
lues und lösen Seiteneffekte aus. Ein Beispiel hierfür ist das Observer-Widget,
welches es ermöglicht, Widgets neu zu bauen, wenn sich die referenzierten
Observables ändern.

Actions werden wie bei Redux hier ebenfalls verwendet, um Änderungen
am Zustand - hier also Observables - durchzuführen. Allerdings wird hier
als Action eine Funktion verstanden, welche Observables abändert und nicht
ein Objekt, welche mithilfe eines Reducers den Zustand mutiert.

Observables, Computed Values und Actions werden dabei oft in Klassen
zu einem Store zusammengefasst. Die Instanzen dieser Stores müssen dabei
über einen Service Locator ähnlich wie bei Riverpod in die Widgets injiziert
werden, damit diese den Store mithilfe von Observern nutzen können.
A N A LY S E
                                                                                 3
Nachdem nun die diversen existierenden Ansätze für die Zustandsverwal-
tung in Flutter vorgestellt worden sind, muss im nächsten Schritt untersucht
werden, welche Anforderungen an solche Systeme gestellt werden. Zudem
soll daraus abgeleitet werden, welche Kriterien für die spätere Evaluation
entscheidend sind.

In “State management approaches in Flutter”[Sle20] wurden bereits Bewer-
tungskriterien für eine Evaluation von Zustandsverwaltungssystemen aufge-
stellt, allerdings kann die Herleitung der Bewertungskriterien nicht nachvoll-
zogen werden, da die verwendeten Quellen nicht die zitierten Bewertungs-
kriterien enthalten. Daher werden nun neue Bewertungskriterien definiert.

3.1     anforderungsanalyse

Die grundlegende Anforderung für Zustandsverwaltungssysteme ist es, dass
sie den Zustand einer Anwendung und bestimmter Teile einer Anwendung
wie eines Widgets oder einer ganzen Seite verwalten können. Alle bereits
vorgestellten Lösungsansätze können dies mit mehr oder weniger Aufwand
auch erfüllen. Allerdings ist es neben der Aussage, ob ein System diese An-
forderung umsetzen kann, auch wichtig wie und in welcher Qualität diese
umgesetzt werden.

3.1.1    Allgemeine Anforderungen

Die Wahl eines Zustandsverwaltungssystems bestimmt die Architektur einer
Anwendung signifikant mit. Daher sind die Anforderungen an eine gute Ar-
chitektur oder ein gutes Software-Design auch in Teilen auf Zustandsverwal-
tungssysteme übertragbar. Um nun daraus Anforderungen zu konstruieren,
ist es also erforderlich, sich anzuschauen, was ein gutes Software-Design
überhaupt ausmacht.

Zur Bewertung von Software-Qualität wurden diverse Anforderungen und
Kriterien entwickelt. Diese wurden mit der ISO-Norm 9126 standardisiert.
3.1 anforderungsanalyse         23

Eines der Qualitätsmerkmale ist die Wartbarkeit von Software, die wie folgt,
beschrieben wird:

     Fähigkeit des Softwareprodukts änderungsfähig zu sein. Ände-
     rungen können Korrekturen, Verbesserungen oder Anpassungen
     der Software an Änderungen der Umgebung, der Anforderun-
     gen und der funktionalen Spezifikationen einschließen [Bal09,
     S. 470]

Daraus ergeben sich auch Anforderungen an die Quelltext-Qualität. Darun-
ter wird beispielsweise die Anforderungen der “Änderbarkeit[...] [und] Test-
barkeit” [Bal09, S. 470] verstanden. Diese Anforderungen können auch für
Zustandsverwaltungssysteme übernommen werden. Zustandsverwaltungs-
systeme und die Anwendungen, die diese verwenden, müssen effizient ver-
änderbar und erweiterbar sein. Zusätzlich ist eine wichtige Anforderung,
dass die resultierende Anwendung auch automatisiert testbar sein muss, um
zu prüfen, ob sie den gewünschten Anforderungen entspricht.

Hinzukommend spieln auch noch weitere Anforderungen zur Sicherstel-
lung von Software-Qualität eine Rolle wie in “Software quality metrics for
object-oriented environments” beschrieben wird. So seien die Attribute der
Effizienz, Komplexität, Verständlichkeit, Wiederverwendbarkeit und Testbar-
keit/Wartbarkeit von Bedeutung. [vgl. RH97, S. 1] Diese Anforderungen
lassen sich auf den beschriebenen Evaluationsfall übertragen, müssen aller-
dings noch auf diesen Anwendungsfall hin angewandt werden.

Angewendet auf Zustandsverwaltungssysteme könnte die Effizienz dadurch
beschrieben werden, wie effizient das Aktualisieren der Widgets im Widget-
Baum erfolgt. Beispielsweise könnte hier untersucht werden, ob nur die Wid-
gets neu erstellt werden, die von einer Änderung wirklich betroffen sind.

Die Verständlichkeit kann angewendet so gedeutet werden, ob die durch
die Verwendung des Systems neu geschaffenen Strukturen auch einfach zu
verstehen - also verständlich sind.

Die Wiederverwendbarkeit lässt sich nicht gut auf Zustandsverwaltungssys-
teme übertragen, da die geschaffenen Strukturen immer auf den spezifischen
Anwendungsfall ausgerichtet sind, und somit eine Wiederverwendbarkeit
keine hohe Relevanz hat.

Die Testbarkeit/Wartbarkeit wurde bereits bei den Erläuterungen zum ISO-
Standard berücksichtigt und lässt sich auch auf den Anwendungsfall wie
beschrieben anwenden.
Sie können auch lesen