@ -12,31 +12,31 @@ Die grundlegende Anforderung für Zustandsverwaltungssysteme ist es, dass sie de
Die Wahl eines Zustandsverwaltungssystems bestimmt die Architektur einer Anwendung signifikant mit. Daher sind die Anforderungen an eine gute Architektur oder ein gutes Software-Design auch in Teilen auf Zustandsverwaltungssysteme ü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. Eines der Qualitätsmerkmale ist die Wartbarkeit von Software, die wie folgt beschrieben wird:
Zur Bewertung von Software-Qualität wurden diverse Anforderungen und Kriterien entwickelt. Diese wurden mit der ISO-Norm 9126 standardisiert. Eines der Qualitätsmerkmale ist die Wartbarkeit von Software, die wie folgt, beschrieben wird:
\blockcquote[470]{Balzert2009}{Fähigkeit des Softwareprodukts änderungsfähig zu sein. Änderungen können Korrekturen, Verbesserungen oder Anpassungen der Software an Änderungen der Umgebung, der Anforderungen und der funktionalen Spezifikationen einschließen}
Daraus ergeben sich auch Anforderungen an die Quelltext-Qualität. Darunter wird beispielsweise die Anforderungen der \textcquote[470]{Balzert2009}{Änderbarkeit[...] [und] Testbarkeit} verstanden. Diese Anforderungen können auch für Zustandsverwaltungssysteme übernommen werden. Zustandsverwaltungssysteme und die Anwendungen, die diese verwenden, müssen effizient veränderbar und erweiterbar sein. Zusätzlich ist eine wichtige Anforderung diese, dass die resultierende Anwendung auch automatisiert testbar sein muss, um zu prüfen, ob sie den gewünschten Anforderungen entspricht.
Zusätzlich zu diesen beiden Anforderungen spielen auch noch weitere Anforderungen zur Sicherstellung von Software-Qualität eine Rolle wie in \citetitle{rosenberg1997software} beschrieben wird. So seien die Attribute der Effizienz, Komplexität, Verständlichkeit, Wiederverwendbarkeit und Testbarkeit/Wartbarkeit von Bedeutung. \Autocite[vgl.][1]{rosenberg1997software} Diese Anforderungen lassen sich auf den beschriebenen Evaluationsfall übertragen, müssen allerdings noch auf diesen Anwendungsfall hin angewandt werden.
Hinzukommend spieln auch noch weitere Anforderungen zur Sicherstellung von Software-Qualität eine Rolle wie in \citetitle{rosenberg1997software} beschrieben wird. So seien die Attribute der Effizienz, Komplexität, Verständlichkeit, Wiederverwendbarkeit und Testbarkeit/Wartbarkeit von Bedeutung. \Autocite[vgl.][1]{rosenberg1997software} Diese Anforderungen lassen sich auf den beschriebenen Evaluationsfall übertragen, müssen allerdings noch auf diesen Anwendungsfall hin angewandt werden.
Angewendet auf Zustandsverwaltungssysteme könnte die Effizienz dadurch beschrieben werden, wie effizient das Aktualisieren der Widgets im Widgetbaum erfolgt. Beispielsweise könnte hier untersucht werden, ob nur die Widgets neu erstellt werden, die von einer Änderung wirklich betroffen sind.
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 Widgets 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 Zustandsverwaltungssysteme übertragen, da die geschaffenen Strukturen immer auf den spezifischen Anwendungsfall ausgerichtet sind, und somit eine Wiederverwendbarkeit keine hohe Relevanz hat.
Die Testbarkeit/Wartbarkeit wurden bereits bei der Erläutungen zum ISO-Standard berücksichtigt und lassen sich auch auf den Anwendungsfall wie beschrieben anwenden.
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.
\subsection{Spezifische Anforderungen}
Neben diesen allgemeinen Anforderungen, stellen sich noch spezifische Anforderungen an die Zustandsverwaltungssysteme.
Neben diesen allgemeinen Anforderungen stellen sich noch spezifische Anforderungen an die Zustandsverwaltungssysteme.
Für die Integration und Einbindung der Systeme durch Entwickler*innnen, ist es erforderlich, dass das System hinreichend dokumentiert ist, damit die Einbindung und Umsetzung erleichtert wird.
Für die Integration und Einbindung der Systeme durch Entwickler*innen, ist es erforderlich, dass das System hinreichend dokumentiert ist, damit die Einbindung und Umsetzung erleichtert wird.
Gerade bei Anwendungen, die mit agilen Methoden entwickelt werden, wird oft iterativ vorgegangen und die Anwendung so schrittweise erweitert. Daraus ergibt sich die Anforderung, dass die Systeme mit den Wachstum der Anwendung mithalten können - also skalierbar sind. Dies erweitert die bereits beschriebene Anforderung der Änderbarkeit.
Gerade bei Anwendungen, die mit agilen Methoden entwickelt werden, wird oft iterativ vorgegangen und die Anwendung so schrittweise erweitert. Daraus ergibt sich die Anforderung, dass die Systeme mit dem Wachstum der Anwendung mithalten können - also skalierbar sind. Dies erweitert die bereits beschriebene Anforderung der Änderbarkeit.
Da wie bereits erwähnt, die Zustandsverwaltung ein integraler und architekturbestimmender Teil der Anwendung ist, ist es bei der Entwicklung ebenfalls hilfreich, wenn durch dieses System bereits eine gewisse Struktur vorgegeben wird. Dies hilft dabei, auch bei größeren Projekte einheitliche Vorgehensweisen durchzusetzen und somit einen homogenen Quelltextstil zu forcieren.
Da wie bereits erwähnt, die Zustandsverwaltung ein integraler und Architektur-bestimmender Teil der Anwendung ist, ist es bei der Entwicklung ebenfalls hilfreich, wenn durch dieses System bereits eine gewisse Struktur vorgegeben wird. Dies hilft dabei, auch bei größeren Projekte einheitliche Vorgehensweisen durchzusetzen und somit einen homogenen Quelltextstil zu forcieren.
\subsection{Zusammenfassung}
@ -75,14 +75,14 @@ Ausgehend von der Prüfung dieser Eigenschaften, wird eine Bewertung mit den Wer
\subsection{Effizienz}
\label{sec:efficiency}
Die Effizienz eines Zustandsverwaltungssystem lässt sich auch dadurch messen, wie effizient es das Neubauen von Widgets nach Veränderungen orchestriert. So sollte ein Widget nur dann neugebaut werden, wenn dies durch eine das Widget betreffende Änderung des Zustands es verlangt. Andere Betrachtungen der Performance sind nur schwer zu messen, da sich andere Variablen wie die Architektur oder Verwendung innerhalb der Anwendung einen großen Einfluss darauf haben.
Die Effizienz eines Zustandsverwaltungssystemes lässt sich auch dadurch messen, wie effizient es das Neubauen von Widgets nach Veränderungen orchestriert. So sollte ein Widget nur dann neugebaut werden, wenn dies durch eine das Widget betreffende Änderung des Zustands es verlangt. Andere Betrachtungen der Performance sind nur schwer zu messen, da sich andere Variablen wie die Architektur oder Verwendung innerhalb der Anwendung einen großen Einfluss darauf haben.
Geprüft werden soll dieses Verhalten an x verschiedenen Prüfstellen in der Anwendung, indem an diesen stellen Zähler eingesetzt werden, die zählen, wann das Widget neu gebaut wird. Dabei wird eine für alle Zustandsverwaltungssysteme gleiche automatische Teststrecke erstellt, welche sicherstellen soll, dass immer die exakt gleichen Bedienhandlungen vorgenommen werden. Damit lässt sich dann abschätzen, wie effizient die Zustandsverwaltungssysteme die Zustandsänderungen an die Widgets propagieren. Erstrebenswert hierbei ist es, ein möglichst geringen Wert bei den Zählern zu erreichen, wobei sichergestellt werden muss, dass dabei alle Systeme die Funktionalität richtig implementieren.
\subsection{Komplexität / Wartbarkeit}
\label{sec:complexity}
Zur Messung der Komplexität ist es auch möglich, die Komplexität eine Anwendung quantiativ zu messen. Für diesen Anwendungsfall werden in der Literatur diverse Verfahren und Metriken beschrieben. Die Metrik \ac{mi} kombiniert dabei diverse Metriken und gewichtet sie. Im Detail wird wie in \autoref{eqn:mi} dargestellt, das durchschnittliche Halstead-Volumen ($aveV$), die durchschnittliche zyklomatische Komplexität ($aveVG2$) und die durchschnittliche Anzahl der Quelltextzeilen ($aveLOC$) verwendet. \autocite[133]{mi} Aufbauend auf dieser Kombination von Metriken soll eine ein-wertige Metrik geschaffen werden, die zum Ausdruck bringt, wie wartbar eine Software ist. \autocite[129]{mi}
Zur Messung der Komplexität ist es auch möglich, die Komplexität eine Anwendung quantitativ zu messen. Für diesen Anwendungsfall werden in der Literatur diverse Verfahren und Metriken beschrieben. Die Metrik \ac{mi} kombiniert dabei diverse Metriken und gewichtet sie. Im Detail wird wie in \autoref{eqn:mi} dargestellt, das durchschnittliche Hal\-stead-Volumen ($aveV$), die durchschnittliche zyklo\-matische Komplex\-ität \\($aveVG2$) und die durchschnittliche Anzahl der Quelltextzeilen ($aveLOC$) verwendet. \autocite[133]{mi} Aufbauend auf dieser Kombination von Metriken soll eine ein-wertige Metrik geschaffen werden, die zum Ausdruck bringt, wie wartbar eine Software ist. \autocite[129]{mi}
\begin{equation}
\begin{split}
@ -91,7 +91,7 @@ Zur Messung der Komplexität ist es auch möglich, die Komplexität eine Anwendu
\end{split}
\end{equation}
Um diese Metrik für die einzelnen Systeme zu bestimmen, wird das Werkzeug \textit{Dart Code Metrics} eingesetzt, welches diverse Metriken für Dart-Quelltext bestimmen kann. Der von dem Werkzeug berechnete Wert wird leicht abweichend , wie in \autoref{eqn:miImproved} zu sehen, von der Original-Formel auf eine Skala von 0-100 abgebildet, wobei 100 den besten erzielbaren Wert darstellt. \autocite{miDart}
Um diese Metrik für die einzelnen Systeme zu bestimmen, wird das Werkzeug \textit{Dart Code Metrics} eingesetzt, welches diverse Metriken für Dart-Quell\-text bestimmen kann. Der von dem Werkzeug berechnete Wert wird leicht abweichend , wie in \autoref{eqn:miImproved} zu sehen, von der Original-Formel auf eine Skala von 0 bis 100 abgebildet, wobei 100 den besten erzielbaren Wert darstellt. \autocite{miDart}
\begin{equation}
\begin{split}
@ -109,11 +109,11 @@ Diese Metrik wird dabei als Bewertungsmaßstab verwendet.% und mit erklärenden
\subsection{Verständlichkeit / Lesbarkeit}
\label{sec:readability}
Die Lesbarkeit und Verständlichkeit eines Quelltext lassen sich nur schwer quantifizieren. Daher erfolgt hier eine begründete qualitative Bewertung anhand von mehreren Fragestellungen.
Die Lesbarkeit und Verständlichkeit eines Quelltextes lassen sich nur schwer quantifizieren. Daher erfolgt hier eine begründete qualitative Bewertung anhand von mehreren Fragestellungen.
So ist beispielsweise für die Verständlichkeit dienlich, wenn bereits in Flutter eingeführte Konzepte verwendet werden, sodass Entwickler*innen, die noch keine Erfahrungen mit dem Zustandsverwaltungssystem haben, ihre bereits existierenden Kenntnisse anwenden können.
Eine weitere Eigenschaft von lesbaren und verständlichen Quelltext ist es, dass die Struktur nachvollziehbar und klar ist. Bei der Einschätzung muss berücksichtigt werden, dass der Einfluss des Zustandsverwaltungssystem auf diese Eigenschaft unterschiedlich stark ausgeprägt ist.
Eine weitere Eigenschaft von lesbaren und verständlichen Quelltext ist es, dass die Struktur nachvollziehbar und klar ist. Bei der Einschätzung muss berücksichtigt werden, dass der Einfluss des Zustandsverwaltungssystems auf diese Eigenschaft unterschiedlich stark ausgeprägt ist.
In Flutter werden Widget-Bäume wie in \autoref{sec:widgets} gezeigt durch das Instanziieren von Widgets durch Konstruktor-Aufrufe erzeugt. Dabei kann es zu einer tiefen Verschachtlung (engl. nesting) kommen, die die Lesbarkeit erschwert. Daher sollten die Zustandsverwaltungssysteme diesem Problem mit geeigneten Maßnahmen entgegenwirken.
@ -124,7 +124,7 @@ Abschließend wird auch hier eine Skala von \textquote{nicht erfüllt}, \textquo
Bei der Dokumentation ist es entscheidend, dass sie für die Entwicklung hilfreich ist. Dabei sollte zuerst geprüft werden, ob es überhaupt eine entsprechende Dokumentation gibt und falls ja, ob die Grundkonzepte des Zustandsverwaltungssystems beschrieben sind. Zusätzlich stellt ein weiteres Kriterium das Zurverfügungstellen von umfangreichen Beispielen dar.
Von diesen Kriterien ausgehend wird eine qualitative Bewertung anhand einer Skala von \textquote{nicht erfüllt}, \textquote{teilweise erfüllt} bist\textquote{vollständig erfüllt} verwendet.
Von diesen Kriterien ausgehend wird eine qualitative Bewertung anhand einer Skala von \textquote{nicht erfüllt}, \textquote{teilweise erfüllt} bis \textquote{vollständig erfüllt} verwendet.
In diesem Kapitel werden für die weitere Evaluation benötigte Grundlagen vermittelt. Dabei wird sowohl auf bestehende Literatur eingegangen als auch an Hand von Beispielen Konzepte erläutert.
Dieses Kapitel befasst sich mit den Grundlagen für die nachfolgende Evaluation. Dabei wird sowohl Literatur zum Thema ausgewertet, als auch Konzepte an Hand von Beispielen erläutert.
@ -4,13 +4,13 @@ Um die konkrete Problemstellung im Detail verstehen zu können, ist es erforderl
\subsection{Einordnung der Rolle für die Entwicklung von Apps}
In diesem Abschnitt wird ein grober Überblick über die aktuelle Situation 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. \autocite{flutterFirstBeta} In der etablierten App-Entwicklung ist es weit verbreitet, zwei separate Anwendungen für die beiden dominierenden Betriebssysteme iOS und Android zu entwickeln. Eine Analyse von Appfigures zeigt dabei, dass im Apple App Store 55 \% der analysierten Apps auf Swift basieren, welches für die sogenannte native iOS-Entwicklung genutzt wird, und im Google Play Store 38 \% der analysierten Apps auf Kotlin basieren, welches dem Pendant zu Swift für Android entspricht.\autocite{sdkPopular} Das Entwickeln von zwei getrennten Anwendungen bringt dabei die Erfordnis mit sich, Quelltext zu duplizieren, da für Android entwickelter Software nicht mit iOS kompatibel ist und umgekehrt.
In diesem Abschnitt wird ein grober Überblick über die aktuelle Situation 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. \autocite{flutterFirstBeta} In der etablierten App-Entwicklung ist es weit verbreitet, zwei separate Anwendungen für die beiden dominierenden Betriebssysteme iOS und Android zu entwickeln. Eine Analyse von Appfigures zeigt dabei, dass im Apple App Store 55 \% der analysierten Apps auf Swift basieren, welches für die sogenannte native iOS-Entwicklung genutzt wird, und im Google Play Store 38 \% der analysierten Apps auf Kotlin basieren, welches dem Pendant zu Swift für Android entspricht.\autocite{sdkPopular} Das Entwickeln von zwei getrennten Anwendungen bringt dabei das Erfordernis 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 Ausarbeitung näher betrachtet. Erwähnenswert jedoch sollte sein, dass das von Meta entwickelte React Native SDK vom Design ähnlich ist und Flutter dieses \blockquote[\cite{reactNativeVsFlutter}]{well preserved} 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.
\subsection{Technischer Überblick}
Dieser Abschnitt vermittelt die technischen Grundlagen für die Flutter Technologie. Flutter und die damit entwickelten Anwendungen werden mit der Dart-Programmiersprache entwickelt. Dart lässt sich dabei mir den bereits etablierten Programmiersprachen wie Java oder JavaScript vergleichen wie in \autoref{lst:dartExample} zu sehen ist und ist objektorientierte, optional statisch typisierte \autocite{dartTypes}.
Dieser Abschnitt vermittelt die technischen Grundlagen für die Flutter Technologie. 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 \autoref{lst:dartExample} zu sehen ist und ist objektorientierte, optional statisch typisierte \autocite{dartTypes}.
\begin{lstlisting}[caption={Aufbau eines einfachen Flutter-Widgets in Dart}, label={lst:dartExample}]
import 'package:flutter/material.dart';
@ -34,9 +34,9 @@ Von der Architektur her ist Flutter in einem Schichtentwurf aufgebaut, wie in \a
\label{fig:flutterArch}
\end{figure}
Die unterste \texttt{Embedder}-Schicht ist dabei für jede der unterstützten Plattformen einzeln implementiert und stellt den oberen Schichten Resourcen zur Verfügung, wie beispielsweise ein Zeichnungsbereich, indem die Benutzeroberflä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 unterste \texttt{Embedder}-Schicht ist dabei für jede der unterstützten Plattformen einzeln implementiert und stellt den oberen Schichten Ressourcen zur Verfügung, wie beispielsweise ein Zeichnungsbereich, indem die Benutzeroberflä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 \texttt{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 Benutzeroberfläche nicht mit nativen Design-Elementen dargestellt, sondern mit dieser eigenen Rendering-Engine gezeichnet. Dies bietet dabei den Vorteil, dass Design-Konzeptionen auf allen Plattformen gleich aussehen.
Die mittlere \texttt{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 Benutzeroberfläche nicht mit nativen Design-Elementen dargestellt, sondern mit dieser eigenen Rendering-Engine gezeichnet. Dies bietet dabei den Vorteil, dass Design-Konzeptionen auf allen Plattformen gleich aussehen.
Die obere \texttt{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.
@ -44,13 +44,13 @@ Das Konzept des Widgets, welches bereits öfters bisher erwähnt worden ist, wir
\subsection{Widgets}
\label{sec:widgets}
Eines der wichtigsten Bestandteile des Flutter-Frameworks sind die sogenannten Widgets. Der Satz \textcquote[Kap.1.9]{flutterinaction}{Everything is a widget} wird in der Literatur oft verwendet, und bringt gut zum Ausdruck, dass Flutter das Konzept des Widgets für viele Anwendungsfälle nutzt. So kann ein Widget diverser 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 \texttt{Row}, wie im \autoref{lst:dartExample} zu sehen ist, mehrere Widgets nebeneinander anzeigen.
Eines der wichtigsten Bestandteile des Flutter-Frameworks sind die sogenannten Widgets. Der Satz \textcquote[Kap.1.9]{flutterinaction}{Everything is a widget} 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 Widget diverser 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 \texttt{Row}, wie im \autoref{lst:dartExample} zu sehen ist, mehrere Widgets nebeneinander anzeigen.
\begin{lstlisting}[caption={Vereinfachte Darstellung eines Widgets als Methode \cite{flutterinaction}}, label={lst:widgetFunction}]
UI Widget(state)
\end{lstlisting}
Ein wichtiger Unterschied zu klassischen deklarativen UI-Frameworks ist, dass Widgets nur die Anleitung zum Bauen einer Benutzeroberfläche beinhalten, jedoch nicht den aktuellen Zustand des Widgets. Im Detail bedeutet dies, dass die \texttt{build()}-Methode eines Widgets keinerlei Nebeneffekte haben sollte und daher eine schnelle Ausführungzeit 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 \autoref{lst:widgetFunction} vereinfacht dargestellt. Ein Widget erhält die darzustellenden Daten und generiert daraus die entsprechende Benutzeroberfläche.
Ein wichtiger Unterschied zu klassischen deklarativen UI-Frameworks ist, dass Widgets nur die Anleitung zum Bauen einer Benutzeroberfläche beinhalten, jedoch nicht den aktuellen Zustand des Widgets. Im Detail bedeutet dies, dass die \texttt{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 \autoref{lst:widgetFunction} vereinfacht dargestellt. Ein Widget erhält die darzustellenden Daten und generiert daraus die entsprechende Benutzeroberfläche.
In Flutter wird bei Widgets grundsätzlich zwischen \texttt{StatelessWidget} und \texttt{StatefulWidget} unterschieden.
@ -96,8 +96,8 @@ class _CounterState extends State<Counter> {
\label{fig:flutterTree}
\end{wrapfigure}
Durch die Kombination von diversen Widgets entsteht so ein Widget-Baum, wie in \autoref{fig:flutterTree} zu sehen ist. Dieser Baum lässt sich auch mittels Hilfswerkzeugen traversieren, ist aber von der Grundstruktur für einen unidirektionalen Datenfluss ausgelegt. Dies bedeutete, dass Widgets nur untergeordnete Widgets durch Änderung derer Zustände verändern können sollen. Übergeordnete Widgets können somit - jedenfalls nicht direkt - verändert werden.
Durch die Kombi\-nation von diversen Widgets entsteht so ein Widget-Baum, wie in \autoref{fig:flutterTree} zu sehen ist. Dieser Baum lässt sich auch mittels Hilfs\-werkzeugen traversieren, ist aber von der Grund\-struktur für einen unidirektionalen Daten\-fluss ausgelegt. Dies bedeutete, dass Widgets nur untergeordnete Widgets durch Änderung derer Zustände verändern können sollen. Über\-geordnete Widgets können somit - jedenfalls nicht direkt - verändert werden.
Anders als bei der nativen Entwicklung, wird hier also die Benutzeroberfläche deklarativ programmiert. Bei der Entwicklung wird also festgelegt, wie welches Element bei welchem Zustands auszusehen hat, ohne dabei den Kontrollfluss beschreiben zu müssen.
Anders als bei der nativen Entwicklung wird hier also die Benutzeroberfläche deklarativ programmiert. 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 Verwaltung 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.
Nachdem nun die Grundlagen des Flutter-Frameworks und die Details der Zustandsverwaltung der Widgets im letzten Kapitel erläutert wurden, kann jetzt betrachtet werden, was überhaupt die Zustandsverwaltung umfasst.
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.
\citeauthor{flutterinaction} fasst den Komplex der Zustandsverwaltung in Flutter in seinem Standardwerk \textit{\citetitle{flutterinaction}} wie folgt zusammen:
@ -26,21 +26,21 @@ Nachdem nun eingeführt wurde, was unter einer Zustandsverwaltung in Flutter zu
\subsection{Auswahl}
\label{sec:section}
Zur Auswahl der zu evaluierenden Lösungsansätze wird die Aufzählung von Zustandsverwaltungs-Ansätzen aus der Flutter-Dokumentation \autocite{flutterStateManagement} als Grundlage verwendet. In dieser Aufzählung werden die Zustandsverwaltungssysteme Provider, Riverpod, setState, InheritedWidget \& -Model, Redux, Fish-Redux, BLoC / RX, GetIt, MobX, Flutter Commands, Binder, GetX, states\_rebuilder und Triple Pattern genannt.
Zur Auswahl der zu evaluierenden Lösungsansätze wird die Aufzählung von Zustandsverwaltungs-Ansätzen aus der Flutter-Dokumentation \autocite{flutterStateManagement} als Grundlage verwendet. In dieser Aufzählung werden die Zustandsverwaltungssysteme Provider, Riverpod, setState, \texttt{Inherited\-Widget}\& -Model, Redux, Fish-Redux, BLoC / RX, GetIt, MobX, Flutter Commands, Binder, GetX, states\_rebuilder und Triple Pattern genannt.
Da eine Evaluation aller aufgezählten Zustandsverwaltungssysteme, den Umfang dieser Ausarbeitung überschreiten würde, wurde die Auswahl eingeschränkt.
Die Ansätze setState und InheritedWidget werden in die Evaluation mit aufgenommen, da sie zu der Grundausstattung der Flutter-Standardbibliothek gehören, und somit einen Basiswert für Zustandsverwaltungssysteme bilden und somit relevant sind. Das \ac{bloc}-Pattern wird aufgenommen, da es aufgrund der großen Verbreitung in der Literatur relevant ist. Die Bibliothek Provider wird ebenfalls aufgenommen, da es sich laut der Dokumentation um den empfohlenen Ansatz für Zustandsverwaltung in Flutter handelt, und die Bibliothek mit 6132 Like-Angaben\autocite{providerPub} zu einer der beliebtesten Flutter-Pakete\autocite{pubRanking} auf der Plattform handelt. Riverpod wird ebenfalls aufgneommen, 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.
Die Ansätze setState und \texttt{Inherited\-Widget} werden in die Evaluation mit aufgenommen, da sie zu der Grundausstattung der Flutter-Standardbibliothek gehören, und somit einen Basiswert für Zustandsverwaltungssysteme bilden und somit relevant sind. Das \ac{bloc}-Pattern wird aufgenommen, da es aufgrund der großen Verbreitung in der Literatur relevant ist. Die Bibliothek Provider wird ebenfalls aufgenommen, da es sich laut der Dokumentation um den empfohlenen Ansatz für Zustandsverwaltung in Flutter handelt, und die Bibliothek mit 6132 Like-Angaben\autocite{providerPub} zu einer der beliebtesten Flutter-Pakete\autocite{pubRanking} 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.
\subsection{Mitgelieferte Werkzeuge}
\label{chap:included}
Die erste Kategorie der Zustandsverwaltungssysteme umfasst jene, welche ohne eine zusätzliche Bibliothek auskommen und somit de facto im Flutter Framework mitgeliefert werden. Hierbei wird mit den einfacheren Konzepten und Werkzeugen begonnen und anschließend die komplexeren Konzepte und Werkzeuge vorgestellt.
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.
\subsubsection{setState}
\label{sec:setState}
Die wohl grundlegendeste Möglichkeit, den Zustand in einer Flutter Anwendung zu verwalten stellt das ausschließliche Benutzen der \texttt{setState}-Methode dar. Ein Beispiel zur Verwendung wurde bereits in \autoref{lst:stateful} in der \texttt{incrementCounter}-Methode eingeführt. Hier findet die Speicherung des Zustands also durch die direkte Manipulation des States von StatefulWidgets statt.
Die wohl grundlegendste Möglichkeit, den Zustand in einer Flutter Anwendung zu verwalten stellt das ausschließliche Benutzen der \texttt{setState}-Methode dar. Ein Beispiel zur Verwendung wurde bereits in \autoref{lst:stateful} in der \texttt{incrementCounter}-Methode eingeführt. Hier findet die Speicherung des Zustands also durch die direkte Manipulation des States von \texttt{Stateful\-Widgets} statt.
\begin{wrapfigure}{l}{0.33\textwidth}
\centering
@ -51,14 +51,14 @@ Die wohl grundlegendeste Möglichkeit, den Zustand in einer Flutter Anwendung zu
Wie vorausgehend beschrieben, muss ein Zustandsverwaltungssystem aber nicht nur den Zustand einzelner Widgets verwalten können, sondern auch von größeren Ordnungen wie beispielsweise von Screens oder der ganzen Anwendung. Um dies bei diesem Ansatz erreichen zu können, wir der Zustand oder Teile des Zustands über die Konstruktor innerhalb des Widget-Trees weiter nach unten gegeben.
Anschaulich lässt sich dies durch das Beispiel \autoref{fig:flutterTreeSetState} 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, macht es Sinn, diese Information weit oben im Baum in Form eines \texttt{StatefulWidget} namens \texttt{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 \texttt{InformationConsumer} - muss \texttt{LoginStateWidget} die Information per Konstruktor an das nachgelagerte Widget weitergeben. Diese nachgelagerten Widgets (\texttt{A, B, C}) müssen dies ebenfalls tun, bis die Information am Ziel \texttt{InformationConsumer} angekommen ist. Diese Anwendungsmuster wird in der Literatur als \textcquote[Kap.8.2]{flutterinaction}{lifting state up} bezeichnet.
Anschaulich lässt sich dies durch das Beispiel \autoref{fig:flutterTreeSetState} 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 \texttt{StatefulWidget} namens \texttt{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 \texttt{InformationConsumer} - muss \texttt{LoginStateWidget} die Information per Konstruktor an das nachgelagerte Widget weitergeben. Diese nachgelagerten Widgets (\texttt{A, B, C}) müssen dies ebenfalls tun, bis die Information am Ziel \texttt{InformationConsumer} angekommen ist. Dieses Anwendungsmuster wird in der Literatur als \textcquote[Kap.8.2]{flutterinaction}{lifting state up} bezeichnet.
\subsubsection{InheritedWidget}
\label{sec:inheritedWidget}
Neben der Möglichkeit, den Zustand über den Widget-Baum nach unten weiter zu propagieren, bietet Flutter noch das Konzept \texttt{InheritedWidget} an. Diese Widgets bilden eine eigene Widget-Gruppe und sind weder den \texttt{StatefulWidgets} noch den \texttt{StatelessWidgets} zuzuordnen. \autocite[Kap.8.2.1]{flutterinaction}\texttt{InheritedWidgets} ermöglichen es nachgeordneten Widgets, auf den Zustand des Widgets direkt zuzugreifen. Hier muss allerdings beachtet werden, dass das \texttt{InheritedWidget} immer unveränderlich ist. Dies bedeutet, dass andere Widgets über die Veränderung von Konstruktor-Parametern neue Instanzen des Widgets erstellen müssen, um eine Zustandsänderung zu bewirken. Daher lassen sich diese Widgets oft in Kombination mit \texttt{StatefulWidgets}, welche für die Manipulation des Zustands zuständig sind, vorfinden.
Im Beispiel \autoref{lst:inheritedWidgetExample} 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 \texttt{currentUser}. Diese*r kann nicht vom \texttt{InheritedWidget} selbst geändert werden, sondern hängt von der Eingabe im Konstruktor ab. Das \texttt{InheritedWidget} wird dabei gemäß des Mottos\blockquote{Everything is a widget} in dem Widget-Baum integriert. Der Konstruktor-Parameter \texttt{child} gibt dabei das im Baum untergeordnete Widget an. Über die \texttt{updateShouldNotify}-Methode wird dem Framework kommuniziert, ob sich der Zustand im vergleich zum vorherigen geändert hat, und somit ein Neubauen der Widgets, die dieses \texttt{InheritedWidget} referenzieren, notwendig ist.
Im Beispiel \autoref{lst:inheritedWidgetExample} 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 \texttt{currentUser}. Diese*r kann nicht vom \texttt{In\-herited\-Widget} selbst geändert werden, sondern hängt von der Eingabe im Konstruktor ab. Das \texttt{InheritedWidget} wird dabei gemäß dem Motto\blockquote{Everything is a widget} in den Widget-Baum integriert. Der Kon\-struktor-Para\-meter \texttt{child} gibt dabei das im Baum untergeordnete Widget an. Über die \texttt{update\-Should\-Notify}-Methode wird dem Framework kommuniziert, ob sich der Zustand im Vergleich zum vorherigen geändert hat, und somit ein Neubauen der Widgets, die dieses \texttt{InheritedWidget} referenzieren, notwendig ist.
\begin{lstlisting}[caption={Aufbau eines InheritedWidget}, label={lst:inheritedWidgetExample}]
import 'package:flutter/widgets.dart';
@ -83,11 +83,11 @@ class UserStore extends InheritedWidget {
}
\end{lstlisting}
Anders als bei dem \texttt{setState}-Konzept kann hier direkt der Zustand durch andere nachgelagerte Widgets referenziert werden. Dafür werden oft Hilfsmethoden wie in diesem Fall die \texttt{of}-Methode verwendet. Diese greifen auf Werkzeuge des Frameworks zu, die das \texttt{InheritedWidget} des angebenden Typen zurückgibt und sicherstellt, dass bei einer Veränderung des das referenzierende Widget auch neugebaut wird und so die Änderung beachtet wird.
Anders als bei dem \texttt{setState}-Konzept kann hier direkt der Zustand durch andere nachgelagerte Widgets referenziert werden. Dafür werden oft Hilfsmethoden wie in diesem Fall die \texttt{of}-Methode verwendet. Diese greifen auf Werkzeuge des Frameworks zu, die das \texttt{InheritedWidget} des angebenden Typen zurückgibt und sicherstellt, dass bei einer Veränderung des das referenzierende Widget auch neugebaut wird und so die Änderung beachtet wird.
Dieser Mechanismus wird auch von diversen anderen Zustandsverwaltungssystemen verwendet.
Als Erweiterung des \texttt{InheritedWidget} kann man \texttt{InheritedModel} sehen. Dabei ist die Funktionsweise äquivalent mit einer Besonderheit. Es besteht hier nämlich die Möglichkeit Zugriffe und Änderungen nach sogenannten \texttt{aspects} zu kategorisieren. Somit kann bei komplexeren Zuständen es ermöglicht werden, dass Widgets nur dann neugebaut werden, wenn der betreffende \text{aspect} sich ändert. \autocite{inheritedModel}
Als Erweiterung des \texttt{InheritedWidget} kann man \texttt{InheritedModel} sehen. Dabei ist die Funktionsweise äquivalent mit einer Besonderheit. Es besteht hier die Möglichkeit, Zugriffe und Änderungen nach sogenannten \texttt{aspects} zu kategorisieren. Somit kann bei komplexeren Zuständen, es ermöglicht werden, dass die Widgets nur dann neugebaut werden, wenn der betreffende \text{aspect} sich ändert. \autocite{inheritedModel}
\subsection{Business Logic Components (BLoC)}
\label{sec:bloc}
@ -100,7 +100,7 @@ Das 2018 auf der Entwicklerkonferenz DartConf vorgestellte \ac{bloc}-Pattern ist
\item No platform branching allowed [...]
\end{enumerate}}
Die erste Regel bedeutet, dass \ac{bloc} weder Methoden noch Variablen nach außen freigeben dürfen, sondern nur über \texttt{Stream}s und \texttt{Sink}s mit Widgets kommunizieren. Ein \texttt{Stream} ist dabei in Flutter ein asynchroner Fluss von Daten oder Ereignissen. Widgets können diesen Ereignissfluss abonnieren und werden dann aktualisiert, wenn sich dieser ändert. Ein \texttt{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 \texttt{Sink} lassen sich also Daten und Ereignisse an das \ac{bloc} übergeben.
Die erste Regel bedeutet, dass \ac{bloc} weder Methoden noch Variablen nach außen freigeben dürfen, sondern nur über \texttt{Stream}s und \texttt{Sink}s mit Widgets kommunizieren. Ein \texttt{Stream} ist dabei in Flutter ein asynchroner Fluss von Daten oder Ereignissen. Widgets können diesen Ereignisfluss abonnieren und werden dann aktualisiert, wenn sich dieser ändert. Ein \texttt{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 \texttt{Sink} lassen sich also Daten und Ereignisse an das \ac{bloc} übergeben.
% TODO ggf noch beispiele einsetzen, wie die regeln anzuwenden sind
@ -132,7 +132,7 @@ class UserStore extends ChangeNotifier {
}
\end{lstlisting}
Also kann festgehalten werden, dass Provider im ersten Schritt eine Form von \textit{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.
Es kann festgehalten werden, dass Provider im ersten Schritt eine Form von \textit{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.
\begin{lstlisting}[caption={Injektion und Abruf von Provider-Klassen}, label={lst:providerExample}]
class ProvidingWidget extends StatelessWidget {
@ -157,14 +157,14 @@ class ConsumingWidget extends StatelessWidget {
\end{lstlisting}
Um eine \texttt{ChangeNotifier} als Zustandsverwaltungslösung mit Provider zu nutzen, ist es bei Provider erforderlich, die jeweiligen Zustands-Klassen (oft Store genannt) in den Widget-Baum zu integrieren. Dies erfolgt wie in der Klasse \texttt{ProvidingWidget} in \autoref{lst:providerExample} gezeigt, durch das Erstellen eines Widgets, welche keine eigene Benutzeroberfläche darstellen, sondern nur Widgets \blockquote{nach unten} durchreichen. Nun können die Daten aus der Zustands-Klasse von allen Widget abgerufen werden, die sich im Widget-Baum an einen der nachgelagerten Äste der injizierenden Widgets befindet.
Um eine \texttt{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 \texttt{Providing\-Widget} in \autoref{lst:providerExample} gezeigt, durch das Erstellen eines Widgets, welche keine eigene Benutzeroberfläche darstellen, sondern nur Widgets \blockquote{nach unten} durchreichen. Nun können die Daten aus der Zustandsklasse von allen Widgets abgerufen werden, die sich im Widget-Baum an einen der nachgelagerten Äste der injizierenden Widgets befindet.
Die nachgelagerten Widgets können dabei, die Zustands-Klasse durch eine einfachen, generischen Methodenaufruf der Provider-Bibliothek abrufen. Durch die Verwendung von \texttt{InheritedWidget} innerhalb der Bibliothek wird damit sichergestellt, dass wenn sich Daten in den Zustands-Klassen ändern, referenzierende Widgets über diese Änderung informiert werden und gegebenenfalls neu gebaut werden.
Die nachgelagerten Widgets können dabei, die Zustandsklasse durch eine einfachen, generischen Methoden\-aufruf der Provider-Bibliothek ab\-rufen.\\ Durch die Verwendung von \texttt{Inherited\-Widget} innerhalb der Bibliothek wird damit sichergestellt, dass wenn sich Daten in den Zustandsklassen ändern, referenzierende Widgets über diese Änderung informiert werden und gegebenenfalls neu gebaut werden.
\subsection{Riverpod}
\label{sec:riverpod}
Riverpod baut auf dem bereits vorgestellten Provider-Konzept auf, ergänzt es aber mit weiteren Funktionalitäten. Ein großer Unterschied liegt auch darin, dass hier Provider nicht in den Widget-Baum integriert werden müssen. Zudem ist es hier anders als bei der Provider-Bibliothek möglich, mehrere Provider vom gleichen Klassentyp zu unterscheiden. Hier wird nämlich nicht der Klassentyp zum Abruf der Zustands-Klasse im Widget verwendet, sondern eine Variable. Wie diese im Widget abgerufen wird, ist dabei von den Entwickelenden zu entscheiden. Denkbar sind hier beispielsweise das Verwenden einer globalen Variable oder einer Dependency-Injection-Lösung wie \texttt{get\_it}. Damit wird auch ermöglicht, dass Riverpod im Vergleich zu Provider keine Abhängigkeit zum Flutter-Framework hat und somit eine reine Dart-Bibliothek ist. \autocite[8]{riverpodArticle}
Riverpod baut auf dem bereits vorgestellten Provider-Konzept auf, ergänzt es aber mit weiteren Funktionalitäten. Ein großer Unterschied liegt auch darin, dass hier Provider nicht in den Widget-Baum integriert werden müssen. Zudem ist es hier anders als bei der Provider-Bibliothek möglich, mehrere 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 \texttt{get\_it}. Damit wird auch ermöglicht, dass Riverpod im Vergleich zu Provider keine Abhängigkeit zum Flutter-Framework hat und somit eine reine Dart-Bibliothek ist. \autocite[8]{riverpodArticle}
Durch diese Unabhängigkeit vom Widget-Tree und dem Flutter-Framework kann hier auf das sogenannte \textit{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.
@ -179,11 +179,11 @@ class UserStore extends StateNotifier<User> {
\end{lstlisting}
Für die Zustandsklassen selber gibt Riverpod eine eigene Strukur vor. Die Zustandsklassen erben die generische Klasse \texttt{StateNotifier}. Dabei wird über das Generic festgelegt, welchen Datentyp der Zustand haben soll. Dieser Zustand wird dann der Klasse als vererbte Variable zur Verfügung gestellt. Bei Änderung des Zustands reicht es somit aus, den Inhalt dieser Variable neu zu setzen wie in \autoref{lst:StateNotifier} gezeigt wird. Die Benutzeroberfläche greift dabei ausschließlich auf die Zustands-Variable direkt zu. Die Zustandsklasse ist nur dafür zuständig, Methoden zur Änderung dieses Zustands zur Verfügung zu stellen.
Für die Zustandsklassen selber gibt Riverpod eine eigene Struktur vor. Die Zustandsklassen erben die generische Klasse \texttt{StateNotifier}. Dabei wird über das Generic festgelegt, welchen Datentyp der Zustand haben soll. Dieser Zustand wird dann der Klasse als vererbte Variable zur Verfügung gestellt. Bei Änderung des Zustands reicht es somit aus, den Inhalt dieser Variable neu zu setzen wie in \autoref{lst:StateNotifier} gezeigt wird. Die Benutzeroberfläche greift dabei ausschließlich auf die Zustands-Variable direkt zu. 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 \autoref{sec:provider}) vorgestellen Event-Emitter wie beispielsweise \texttt{ChangeNotifier}.
Da wie bereits erwähnt, Riverpod keine Flutter-Bibliothek ist und somit nicht wie Provider \texttt{InheritedWidget} zum Aktualisiern von referenzierenden 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.
Da wie bereits erwähnt, Riverpod keine Flutter-Bibliothek ist und somit nicht wie Provider \texttt{InheritedWidget} zum Aktualisieren von referenzierenden 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.
final userStoreProvider = StateNotifierProvider((ref) => UserStore());
@ -198,11 +198,11 @@ class ConsumingWidget extends ConsumerWidget {
\end{lstlisting}
Bei \texttt{flutter\_riverpod} werden die bereits eingeführten Widget-Typen \texttt{StatefulWidget} und \texttt{StatelessWidget} durch neue Typen ergänzt beziehungsweise ersetzt. Einer dieser neuen Typen ist \texttt{ConsumerWidget}. Durch das Erben von dieser Klasse, wird der \texttt{build}-Methode des Widgets eine neue Variable ergänzt, über die der Zustand von Riverpod-Providern abgefragt werden kann, wie in \autoref{lst:ConsumerWidget} zu sehen ist. Die hier verwendete \texttt{watch}-Methode bewirkt, dass das Widget neu gebaut wird, wenn sich der betreffende Provider ändert.
Bei \texttt{flutter\_riverpod} werden die bereits eingeführten Widget-Typen \texttt{State\-ful\-Widget} und \texttt{StatelessWidget} durch neue Typen ergänzt beziehungsweise ersetzt. Einer dieser neuen Typen ist \texttt{ConsumerWidget}. Durch das Erben von dieser Klasse, wird der \texttt{build}-Methode des Widgets eine neue Variable ergänzt, über die der Zustand von Riverpod-Providern abgefragt werden kann, wie in \autoref{lst:ConsumerWidget} zu sehen ist. Die hier verwendete \texttt{watch}-Methode bewirkt, dass das Widget neu gebaut wird, wenn sich der betreffende Provider ändert.
\subsection{Redux}
\label{sec:redux}
Redux ist eine Zustandsverwaltungssystem, welches ursprüunglich für React enwickelt worden ist. React ist ein 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.
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.
Redux basiert auf drei grundlegenden Prinzipien:
@ -223,9 +223,9 @@ Das Prinzip der \blockcquote[Kap.1.3.2]{redux}{Single source of truth} wird dad
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 \textit{actions} zu emittieren. \textit{Actions} sind dabei in Flutter einfache Objekte, die auch Parameter in Form von Variablen beinhalten können. Diese \textit{Action} wird dann wie in \autoref{fig:reduxflow} anschaulich zu sehen ist, über einen \textit{Reducer} auf den globalen Zustand (engl. \textit{store}) angewendet.
Damit wird dann auch das dritte Prinzip umgesetzt. Ein \textit{Reducer} stellt nämlich eine einfache Funktion dar, welche als Parameter den aktuellen Zustand und die \textit{Action} erhält und als Rückgabewert einen neuen Zustand angibt, welcher den globalen Zustand ersetzt.
Damit wird dann auch das dritte Prinzip umgesetzt. Ein \textit{Reducer} stellt eine einfache Funktion dar, welche als Parameter den aktuellen Zustand und die \textit{Action} erhält und als Rückgabewert einen neuen Zustand angibt, welcher den globalen Zustand ersetzt.
Für Dart wurde dies mit der \texttt{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 \texttt{flutter\_redux} verwendet. Diese adaptiert das Prinzip der Provider-Bibliothek für Redux-Stores. So wird der Store mittels eines Provider-Widget über die Widget-Tree für nachgelagerte Widgets zur Verfügung gestellt. Auf den Store zugegriffen wird über Selektoren, welche den benötigten Teil des globalen Zustands eingrenzen und per Callback-Funktion zurückgeben. Somit wird über diese Selektoren auch die Verändunger der Widgets ausgelöst, wenn sich der entsprechende Teil des globalen Zustands ändert.
Für Dart wurde dies mit der \texttt{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 \texttt{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 zugegriffen wird über Selektoren, welche den benötigten Teil des globalen Zustands 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.
\subsection{MobX}
\label{sec:mobx}
@ -238,9 +238,9 @@ MobX ist wie Redux ebenfalls ein Ansatz, der ursprünglich aus dem React-Ökosys
\item Actions [...]
\end{itemize}}
Observables bilden die Grundlage eines Zustands. Ein Observable stellt einen Wert da, welcher beobachtet werden kann und somit auf Änderungen reagiert werden kann. Von Observables werden alle anderen Informationen abgeleitet.
Observables bilden die Grundlage eines Zustands. Ein Observable stellt einen Wert dar, welcher beobachtet werden kann und somit auf Änderungen reagiert werden kann. Von Observables werden alle anderen Informationen abgeleitet.
Mit Computed Values lassen sich ein oder mehrere Observables kombineren oder einer weiteren Verarbeitung unterwerfen. Wenn sich eines der zugrundeliegenden Observables ändert, wird auch dieser Wert neu berechnet.
Mit Computed Values lassen sich ein oder mehrere Observables kombinieren oder einer weiteren Verarbeitung unterwerfen. Wenn sich eines der zugrundeliegenden Observables ändert, wird auch dieser Wert neu berechnet.
Reactions reagieren auf Änderungen von Observables oder Computed Values 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.
@ -250,6 +250,6 @@ Observables, Computed Values und Actions werden dabei oft in Klassen zu einem St
%\subsection{GetIt}
%Der Ansatz mit GetIt kombiniert diverse bereits eingeführte Konzepte miteinander. GetIt ansich ist ein Service-Locator. Ein Service Locator bietet eine zentrale Stelle, an der alle benötigten Komponenten registriert werden und abgerufen werden können. Dieser Service Locator wird nun mit dem bereits eingeführten Konzept des ChangeNotifier und von StatefulWidgets verknüpft. \autocite[Kap.4]{managingstateinflutter} Dazu wird der Zustand mittels einer ChangeNotifier-Klasse modelliert und anschließend über GetIt an die Widgets übergeben, die den Zustand benötigen. Diese Widgets müssen StatefulWidgets sein und registrieren bei der ChangeNotifier-Instanz einen Listener, welcher aufgerufen werden soll, wenn sich der Zustand ändert. Bei einer solchen Zustandsänderung, wird im StatefulWidget die \texttt{setState}-Methode aufgerufen, welche zum Neubauen des Widgets führt.
%Der Ansatz mit GetIt kombiniert diverse bereits eingeführte Konzepte miteinander. GetIt ansich ist ein Service-Locator. Ein Service Locator bietet eine zentrale Stelle, an der alle benötigten Komponenten registriert werden und abgerufen werden können. Dieser Service Locator wird nun mit dem bereits eingeführten Konzept des ChangeNotifier und von \texttt{Stateful\-Widgets} verknüpft. \autocite[Kap.4]{managingstateinflutter} Dazu wird der Zustand mittels einer ChangeNotifier-Klasse modelliert und anschließend über GetIt an die Widgets übergeben, die den Zustand benötigen. Diese Widgets müssen \texttt{Stateful\-Widgets} sein und registrieren bei der ChangeNotifier-Instanz einen Listener, welcher aufgerufen werden soll, wenn sich der Zustand ändert. Bei einer solchen Zustandsänderung, wird im \texttt{Stateful\-Widget} die \texttt{setState}-Methode aufgerufen, welche zum Neubauen des Widgets führt.
@ -8,7 +8,7 @@ Bei den Zustandsverwaltungssystemen selbst haben sich in der Evaluation besonder
Somit hat sich auch gezeigt, dass Riverpod eine sinnvolle Weiterentwicklung von Provider ist, welches in der Evaluation mehrere Schwachstellen wie beispielsweise in der Skalierbarkeit aufwies.
BLoC und InheritedWidget konnten zwar die Anforderungen an die Beispielanwendung umsetzen, erwiesen sich allerdings ohne besondere Optimierung als wenig effizient und hatten Einschränkungen in der Lesbarkeit und Verständlichkeit.
BLoC und \texttt{Inherited\-Widget} konnten zwar die Anforderungen an die Beispielanwendung umsetzen, erwiesen sich allerdings ohne besondere Optimierung als wenig effizient und hatten Einschränkungen in der Lesbarkeit und Verständlichkeit.
ReduX als besonders im React-Ökosystem oft verwendeter Ansatz zur Zustandsverwaltung, konnte auf Flutter nicht überzeugen, da hier besonders der zentrale Zustand zu einem Problem wird, da dieser für die in der Evaluation schlechtesten Effizienz-Werte und eine negative Bewertung bei der Skalierbarkeit verantwortlich ist.
@ -24,4 +24,4 @@ Für kleine Anwendungen empfiehlt sich entweder die Nutzung von InheritedWidgets
\section{Ausblick}
Für weitergehende Untersuchungen bieten sich mehrere Aspekte an. Zum einen ist die Ersetzung der \ac{mi}-Metrik durch eine aussagekräftigere Metrik empfehlenswert, um so auch die Anforderung der Komplexität / Wartbarkeit in der Evaluation aussagekräftig bewerten zu können. Zum anderen bietet sich die Erweiterung der Evaluation um andere, alternative Zustandsverwaltungssysteme, die in dieser Ausarbeitung nicht untersucht werden konnten (vgl. \autoref{sec:section}), um ein vollständigeres Bild über die Zustandsverwaltungssysteme für Flutter zu bekommen. Ein weiterer untersuchentwerter Aspekt könnte der Vergleich mit der Zustandsverwaltung von React sein, um mögliche Unterschiede darzustellen.
Für weitergehende Untersuchungen bieten sich mehrere Aspekte an. Zum einen ist die Ersetzung der \ac{mi}-Metrik durch eine aussagekräftigere Metrik empfehlenswert, um so auch die Anforderung der Komplexität / Wartbarkeit in der Evaluation aussagekräftig bewerten zu können. Zum anderen bietet sich die Erweiterung der Evaluation um andere, alternative Zustandsverwaltungssysteme, die in dieser Ausarbeitung nicht untersucht werden konnten (vgl. \autoref{sec:section}), um ein vollständigeres Bild über die Zustandsverwaltungssysteme für Flutter zu bekommen. Ein weiterer untersuchenswerter Aspekt könnte der Vergleich mit der Zustandsverwaltung von React sein, um mögliche Unterschiede darzustellen.
Für die Implementierung der App mit \ac{bloc} wurden drei \acl{bloc} erstellt. Diese verwalten den den Anmeldezustand, den Inhalt des Warenkorbs sowie die verfügbaren Produkte. Die \ac{bloc}s bestehen dabei aus zu teils mehreren \texttt{Stream}s und \texttt{Sink}s, die Zustandsänderungen von außen entgegennehmen und nach außen kommunizieren. Zusätzlich dazu gibt es für die \texttt{Stream}s öffentliche Variablen, welche den aktuellen Zustand wiedergeben. Dies dient, wie auch im Beispiel \autoref{lst:streambuilder} anhand des Parameters \texttt{initialData} zu sehen ist, dazu, dass Widgets, die erst später zum Widget-Tree hinzugefügt werden den aktuellen Zustand zum Zeitpunkt des initialen Erstellens erhalten können, da die Streams immer nur Zustandsänderungen kommunizieren können.
Für die Implementierung der App mit \ac{bloc} wurden drei \acl{bloc} erstellt. Diese verwalten den Anmeldezustand, den Inhalt des Warenkorbs sowie die verfügbaren Produkte. Die \ac{bloc}s bestehen dabei aus zu teils mehreren \texttt{Stream}s und \texttt{Sink}s, die Zustandsänderungen von außen entgegennehmen und nach außen kommunizieren. Zusätzlich dazu gibt es für die \texttt{Stream}s öffentliche Variablen, welche den aktuellen Zustand wiedergeben. Dies dient, wie auch im Beispiel \autoref{lst:streambuilder} anhand des Parameters \texttt{initialData} zu sehen ist, dazu, dass Widgets, die erst später zum Widget-Tree hinzugefügt werden den aktuellen Zustand zum Zeitpunkt des initialen Erstellens erhalten können, da die Streams immer nur Zustandsänderungen kommunizieren können.
Alle drei \ac{bloc}s werden über ein Widget an die Benutzeroberfläche weitergegeben, welches als Dependency Injection fungiert. Hier wären auch andere Implementierungsvarianten beispielsweise mit dem Service Locator \texttt{get\_it} denkbar gewesen. Der gewählte Ansatz benötigt jedoch keine zusätzliche Bibliothek und verfälscht somit das Ergebnis am wenigsten. Die Widgets der Benutzeroberfläche verwenden dabei \texttt{StreamBuilder}, wie in \autoref{lst:streambuilder} zu sehen ist, um über aktualisierten Zuständen informiert zu werden.
@ -28,17 +28,17 @@ return StreamBuilder<int>(
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit BLoC \autocite[branch=bloc]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit BLoC \autocite[branch=\\bloc]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}}
Zu \ac{bloc} lässt sich sagen, dass diese sowohl skalierbar als auch änderbar sind. Diese Aussage stützt sich darauf, dass durch die Kommunikation ausschließlich über \texttt{Stream}s und \texttt{Sink}s sich eine einheitliche Schnittstelle schaffen lässt, an die beliege Komponenten sich andocken können. Anders als bei den Widget-basierten Ansätzen wie InheritedWidget lassen sich \ac{bloc} komplett plattformunabhängig einsetzen und sind somit für eine weitere Skalierung außerhalb des Flutter-Frameworks durchaus geeignet.
Zu \ac{bloc} lässt sich sagen, dass diese sowohl skalierbar als auch änderbar sind. Diese Aussage stützt sich darauf, dass durch die Kommunikation ausschließlich über \texttt{Stream}s und \texttt{Sink}s sich eine einheitliche Schnittstelle schaffen lässt, an die beliege Komponenten sich andocken können. Anders als bei den Widget-basierten Ansätzen wie \texttt{Inherited\-Widget} lassen sich \ac{bloc} komplett plattformunabhängig einsetzen und sind somit für eine weitere Skalierung außerhalb des Flutter-Frameworks durchaus geeignet.
Die Koppelung verschiedener Zustände lässt sich somit auch einfach lösen, indem andere \ac{bloc} beispielsweise über den Konstruktor oder ein Dependency-Injection Tool übergeben werden.
Die Koppelung verschiedener Zustände lässt sich somit auch einfach lösen, indem andere \ac{bloc} beispielsweise über den Konstruktor oder ein Dependen\-cy-Injection Tool übergeben werden.
Aufgrund dieser Eigenschaften wird die Bewertung \textquote{vollständig erfüllt} vergeben.
\paragraph{\nameref{sec:testability}} Zur Testbarkeit von \ac{bloc} lässt sich Folgendes sagen. Die Geschäftslogik der einzelnen \ac{bloc} lässt sich gut testen, da das Flutter-Testing-Framework bereits Werkzeuge zum Überprüfen von Streams bereithält, wie sie im Test in \autoref{lst:testbloc} verwendet werden. Hier lassen sich somit auch einfach Unit-Tests verwenden. Falls \ac{bloc} Abhängigkeiten zu anderen \ac{bloc} oder Komponenten haben, müssen diese allerdings gemockt werden. Ohne eine entsprechende Bibliothek entstehen hier zusätzliche Aufwände durch das Implementieren von Mock-Klassen.
\paragraph{\nameref{sec:testability}} Zur Testbarkeit von \ac{bloc} lässt sich Folgendes sagen. Die Geschäftslogik der einzelnen \ac{bloc} lässt sich gut testen, da das Flutter-Test\-ing-Framework bereits Werkzeuge zum Überprüfen von Streams bereithält, wie sie im Test in \autoref{lst:testbloc} verwendet werden. Hier lassen sich somit auch einfach Unit-Tests verwenden. Falls \ac{bloc} Abhängigkeiten zu anderen \ac{bloc} oder Komponenten haben, müssen diese allerdings gemockt werden. Ohne eine entsprechende Bibliothek entstehen hier zusätzliche Aufwände durch das Implementieren von Mock-Klassen.
\begin{lstlisting}[caption={Test des Cart BLoC in cart\_bloc\_test.dart \cite{repo}}, label={lst:testbloc}]
Ein weiterer betrachteter Aspekt beim Testen ist die Testbarkeit von Widgets, die auf \ac{bloc} zugreifen. Hierzu lässt sich sagen, dass das Ersetzen durch Platzhaltern ohne Probleme möglich war. Allerdings hängt dies auch von dem verwendeten Injection-System ab. In diesem Beispiel wurden InheritedWidgets verwendet. Daher kann dies äquivalent zu dem dort beschriebenen Beobachtungen (vgl. \autoref{par:ihtesting}) gewertet werden. Zusätzlich dazu lässt sich sagen, dass für das Erzeugen eines Platzhalters (engl. mock) für ein \ac{bloc} die Klasse dieses \ac{bloc} vollständig gemockt werden muss. Hier lassen sich wie im vorhergehenden Absatz beschrieben, mehrere Strategien nutzen.
Ein weiterer betrachteter Aspekt beim Testen ist die Testbarkeit von Widgets, die auf \ac{bloc} zugreifen. Hierzu lässt sich sagen, dass das Ersetzen durch Platzhaltern ohne Probleme möglich war. Allerdings hängt dies auch von dem verwendeten Injection-System ab. In diesem Beispiel wurden \texttt{Inherited\-Widgets} verwendet. Daher kann dies äquivalent zu dem dort beschriebenen Beobachtungen (vgl. \autoref{par:ihtesting}) gewertet werden. Zusätzlich dazu lässt sich sagen, dass für das Erzeugen eines Platzhalters (engl. mock) für ein \ac{bloc} die Klasse dieses \ac{bloc} vollständig gemockt werden muss. Hier lassen sich wie im vorhergehenden Absatz beschrieben, mehrere Strategien nutzen.
Aufgrund der einfachen Testbarkeit der Geschäftslogik sowie des möglichen Austausches mit Platzhaltern für Widget-Tests wird die Testbarkeit mit \textquote{vollständig erfüllt} bewertet.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei BLoC}]{results/bloc/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei BLoC}]{results/bloc/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:bloc}) ergab eine \ac{mi} von 82 für das gesamte Projekt.
@ -63,14 +63,14 @@ Aufgrund der einfachen Testbarkeit der Geschäftslogik sowie des möglichen Aust
Zur Verwendung von \ac{bloc} ist es erforderlich, dass man sich mit Konzepten der asynchronen Programmierung beschäftigt, um die Funktionsweise von \texttt{Stream}s und \texttt{Sink}s verstehen zu können. Diese werden in der Flutter-Programmierung sonst selten benötigt. Daher ist davon auszugehen, dass Entwickler*innen diese erst lernen müssen.
Die Struktur der BLoC selber ist schwer nachvollziehbar, da die Kombination von Streams,, Sinks und Variablen nicht auf den ersten Blick nachvollziehbar ist. Zudem sind die Schnittstellen zur Benutzeroberfläche nicht wie andere Schnittstellen den Sprachfluss angepasst, sondern werden bestimmt durch die jeweiligen Funktionen von \texttt{Stream}s und \texttt{Sink}s. Zudem benötigt dieser Ansatz eine Großzahl von Klassen, um beispielsweise Argumente von Ereignissen und Zustände abbilden zu können. Dies erschwert zusätzlich die Lesbarkeit, wie auch von \citeauthor{experienceofdevelopingflutter} festgestellt wurde. \autocite[573]{experienceofdevelopingflutter}
Die Struktur der BLoC selber ist schwer nachvollziehbar, da die Kombination von Streams, Sinks und Variablen nicht auf den ersten Blick nachvollziehbar ist. Zudem sind die Schnittstellen zur Benutzeroberfläche nicht wie andere Schnittstellen den Sprachfluss angepasst, sondern werden bestimmt durch die jeweiligen Funktionen von \texttt{Stream}s und \texttt{Sink}s. Außerdem benötigt dieser Ansatz eine Großzahl von Klassen, um beispielsweise Argumente von Ereignissen und Zustände abbilden zu können. Dies erschwert zusätzlich die Lesbarkeit, wie auch von \citeauthor{experienceofdevelopingflutter} festgestellt wurde. \autocite[573]{experienceofdevelopingflutter}
Die tiefe Verschachtlung von Widgets kann bei \ac{bloc} nicht beobachtet werden, da hier nicht das Widget-System als Grundlage zum Einsatz kommt. Allerdings verlangt die Verwendung von \texttt{Stream}s den Einsatz von \texttt{StreamBuilder}n in der Benutzeroberfläche, wie bereits in \autoref{lst:streambuilder} gezeigt wird. Dies gibt zwar auf der einen Seite große Kontrolle über die Verarbeitung von Zustandsänderungen, macht den Quelltext allerdings schwer lesbar, besonders bei der Verwendung von mehreren \texttt{StreamBuilder}n ineinander.
Die tiefe Verschachtlung von Widgets kann bei \ac{bloc} nicht beobachtet werden, da hier nicht das Widget-System als Grundlage zum Einsatz kommt. Allerdings verlangt die Verwendung von \texttt{Stream}s den Einsatz von \texttt{Stream\-Builder}n in der Benutzeroberfläche, wie bereits in \autoref{lst:streambuilder} gezeigt wird. Dies gibt zwar auf der einen Seite große Kontrolle über die Verarbeitung von Zustandsänderungen, macht den Quelltext allerdings schwer lesbar, besonders bei der Verwendung von mehreren \texttt{StreamBuilder}n ineinander.
Zusammenfassend wird die Verständlichkeit und Lesbarkeit mit der Bewertung \textquote{nicht erfüllt} bewertet.
\paragraph{\nameref{sec:documentation}} Die Dokumentation von \ac{bloc} ist schwierig zu bewerten, da es sich bei \ac{bloc} lediglich um ein Konzept handelt. Allerdings gibt es Bibliotheken, welche das Konzept mit Hilfs-Konstrukten versehen und so den Einsatz von \ac{bloc} einfacher machen. Die Dokumentation dieser Bibliotheken umfasst umfangreiche Erklärungen und Beispiele\autocite{blocLib}, die auch auf \ac{bloc} an sich übertragbar sind.
\paragraph{\nameref{sec:documentation}} Die Dokumentation von \ac{bloc} ist schwierig zu bewerten, da es sich bei \ac{bloc} lediglich um ein Konzept handelt. Allerdings gibt es Bibliotheken, welche das Konzept mit Hilfs-Konstrukten versehen und so den Einsatz von \ac{bloc} einfacher machen. Die Dokumentation dieser Bibliotheken umfasst umfangreiche Erklärungen und Beispiele\autocite{blocLib}, die auch auf \ac{bloc} an sich übertragbar sind.
Zusätzlich bestehen eine große Anzahl an Artikeln und anderen Veröffentlichungen zum Thema \ac{bloc}.
@ -9,7 +9,7 @@ Im darauf folgenden Kapitel werden dann die kumulierten Ergebnisse analysiert un
\section{setState}
\label{eval:setstate}
Das erste zu evaluierende Zustandsverwaltungssystem stellt die in \autoref{sec:setState} beschriebe Vorgehensweise zur Verwaltung des Zustands dar.
Das Erste zu evaluierende Zustandsverwaltungssystem stellt die in \autoref{sec:setState} beschriebe Vorgehensweise zur Verwaltung des Zustands dar.
Mit diesem Ansatz kann nicht die Mindestanforderungen an die Beispielanwendung umgesetzt werden, da es unmöglich ist, mit ihm einen konsistenten Zustand über mehre Seiten hinweg zu erzeugen. Dabei war es immer nur möglich, Zustandsänderungen auf einer Seite zu haben, sobald man auf eine andere Seite navigiert, wurden auf der alten Seite die Änderungen nicht übernommen.
@ -40,7 +40,7 @@ Die Ergebnisse der Evaluation sind in der \autoref{tbl:results} übersichtlich z
\endhead
ohne Zustandsverwaltung & n. a. & n. a. & 1;2 & 83 & n. a. & n. a. & n.a. \\
Wie in \autoref{sec:inheritedWidget} beschrieben, stellen InheritedWidgets einen Lösung für das Zustandsverwaltung dar, die ohne externe Bibliotheken auskommt und somit nur Bordmittel des Flutter-Frameworks benutzen.
Wie in \autoref{sec:inheritedWidget} beschrieben, stellen \texttt{Inherited\-Widgets} eine Option für die Zustandsverwaltung dar, die ohne externe Bibliotheken auskommt und somit nur Bordmittel des Flutter-Frameworks benutzen.
\subsection{Implementierung}
Für die Implementierung dieses Ansatzes wurden mehrere Stores konstruiert, welche den Anmeldezustand, die geladenen Produkte und die im Warenkorb befindliche Anzahl der Produkte modellieren. Ein Store besteht dabei immer aus einem InheritedWidget und einem StatefulWidget. Das InheritedWidget erhält dabei vom StatefulWidget die Daten und Methoden, welche nach außen abrufbar sein sollen. Zustandsänderungen und Verknüpfungen mit anderen Stores finden ausschließlich im StatefulWidget statt.
Für die Implementierung dieses Ansatzes wurden mehrere Stores konstruiert, welche den Anmeldezustand, die geladenen Produkte und die im Warenkorb befindliche Anzahl der Produkte modellieren. Ein Store besteht dabei immer aus einem \texttt{Inherited\-Widget} und einem \texttt{Stateful\-Widget}. Das \texttt{Inherited\-Widget} erhält dabei vom \texttt{Stateful\-Widget} die Daten und Methoden, welche nach außen abrufbar sein sollen. Zustandsänderungen und Verknüpfungen mit anderen Stores finden ausschließlich im \texttt{Stateful\-Widget} statt.
Die Benutzeroberfläche greift auf die Stores ausschließlich über die InheritedWidgets zu, wie bereits im \autoref{sec:inheritedWidget} beschrieben wurde.
Die Benutzeroberfläche greift auf die Stores ausschließlich über die \texttt{In\-heri\-ted\-Widgets} zu, wie bereits im \autoref{sec:inheritedWidget} beschrieben wurde.
Der Funktionsumfang konnte dabei ohne Einschränkungen vollständig implementiert werden.
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit InheritedWidget \autocite[branch=inheritedwidget]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit \texttt{Inherited\-Widget}\autocite[branch=inheritedwidget]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}} Bei der Änderbarkeit und Skalierbarkeit wird dieses Zustandsverwaltungssystem mit \textquote{teilweise erfüllt} bewertet.
Eines der Merkmale von skalierbaren und änderbaren Zustandsverwaltungssystemen ist es, dass sich mehrere Zustände untereinander verknüpfen lassen. Dies ist hier eingeschränkt möglich, da um auf einen anderen Zustand zuzugreifen es erforderlich ist, dass dieser im Widget-Baum oberhalb des zugreifenden Zustandswidgets angeordnet ist. Falls dies der Fall ist, lässt sich der Zustand aber wie bei der Benutzeroberfläche (vgl. \autoref{sec:inheritedWidget}) über einen Methoden-Aufruf abrufen.
Negativ bewertet wird hier aber die Tatsache, dass ein Zustand beziehungsweise Store immer aus mindestens drei Klassen bestehen muss. Dies macht es erforderlich, alle nach außen zugängliche Funktionen oder Variablen per Konstruktoraufruf von dem StatefulWidget zum InheritedWidget zu übergeben. Bei wachsender Größe eines Zustands wird dies unübersichtlich.
Negativ bewertet wird hier aber die Tatsache, dass ein Zustand beziehungsweise Store immer aus mindestens drei Klassen bestehen muss. Dies macht es erforderlich, alle nach außen zugängliche Funktionen oder Variablen per Konstruktoraufruf von dem \texttt{Stateful\-Widget} zum \texttt{Inherited\-Widget} zu übergeben. Bei wachsender Größe eines Zustands wird dies unübersichtlich.
Ein weiter Nachteil ist es, dass es erforderlich ist, im InheritedWidget eine Methode zu implementieren, die feststellt, ob sich der Zustand im Vergleich zu einem anderen Zustand verändert hat. Dies wird bei wachsender Größe oder Datenstrukturkomplexität eines Zustands unübersichtlich und auch komplex.
Ein weiter Nachteil ist es, dass es erforderlich ist, im \texttt{Inherited\-Widget} eine Methode zu implementieren, die feststellt, ob sich der Zustand im Vergleich zu einem anderen Zustand verändert hat. Dies wird bei wachsender Größe oder Datenstrukturkomplexität eines Zustands unübersichtlich und auch komplex.
\paragraph{\nameref{sec:testability}} Zur Bewertung der Testbarkeit wurden mehrere Tests entsprechend der Anforderungen implementiert.
Das Testen der Geschäftslogik der Stores stellte sich als komplex heraus, da hier keine Unit-Tests sondern Widget-Tests von Nöten waren. Dies hat auch zur Folge, dass man sich während der Tests auch um den Lebenszyklus der Widgets kümmern muss. So muss man im Test die Hilfs-Funktion \texttt{tester.pump()}, wie in \autoref{lst:testabilityInheritedWidget} zu sehen ist, zum richtigen Moment aufrufen, damit das Framework die Zustandsänderungen umsetzt.
Das Testen der Geschäftslogik der Stores stellte sich als komplex heraus, da hier keine Unit-Tests, sondern Widget-Tests vonnöten waren. Dies hat auch zur Folge, dass man sich während der Tests auch um den Lebenszyklus der Widgets kümmern muss. So muss man im Test die Hilfs-Funktion \texttt{tester.pump()}, wie in \autoref{lst:testabilityInheritedWidget} zu sehen ist, zum richtigen Moment aufrufen, damit das Framework die Zustandsänderungen umsetzt.
\begin{lstlisting}[caption={Aufbau eines einfachen Flutter-Widgets in Dart}, label={lst:testabilityInheritedWidget}]
Die Tests für Widgets, die den Zustand konsumieren auf der anderen Hand sind einfach umzusetzen, da die Geschäftslogik im besten Fall komplett von der Speicherung der emittierten Daten getrennt wird. So ist es möglich ein InheritedWidget ohne das dazugehörige StatefulWidget zu initialiseren und die benötigten Mock-Daten über den Konstruktor zu übergeben.
Die Tests für Widgets, die den Zustand konsumieren auf der anderen Hand sind einfach umzusetzen, da die Geschäftslogik im besten Fall komplett von der Speicherung der emittierten Daten getrennt wird. So ist es möglich ein \texttt{Inherited\-Widget} ohne das dazugehörige \texttt{Stateful\-Widget} zu initialiseren und die benötigten Mock-Daten über den Konstruktor zu übergeben.
Abschließend erfolgt hier die Bewertung mit \textquote{vollständig erfüllt}, da besonders die einfache Testbarkeit von konsumierenden Widgets die Nachteile bei dem Test für die Geschäftslogik ausgleicht.
Abschließend erfolgt hier die Bewertung mit \textquote{teilweise erfüllt}.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei InheritedWidget}]{results/inheritedwidget/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei InheritedWidget}]{results/inheritedwidget/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:inheritedwidget}) ergab eine \ac{mi} von 83 für das gesamte Projekt.
\paragraph{\nameref{sec:readability}} Bei der Bewertung der Lesbarkeit sind mehrere Faktoren wichtig.
Der erste Faktor ist die Frage, ob neue Konzepte eingeführt werden. Diese Frage kann mit nein beantwortet werden, da hier ausschließlich Komponenten aus dem Flutter-Framework in Form verschiedener Widgetes zum Einsatz kommen.
Der erste Faktor ist die Frage, ob neue Konzepte eingeführt werden. Diese Frage kann mit Nein beantwortet werden, da hier ausschließlich Komponenten aus dem Flutter-Framework in Form verschiedener Widgetes zum Einsatz kommen.
Der zweite Faktor hierbei ist die Frage, inwiefern die Struktur klar nachvollziehbar und verständlich ist. Dies ist bei InheritedWidgets nicht der Fall, da besonders die Konstruktion aus mehreren Widgets und Widget-Typen schwer nachvollziehbar ist. So gibt es keinen zentralen Ort, welcher die Geschäftslogik übersichtlich zusammenfasst. Im Gegenteil ist es hier notwendig, die Geschäftslogik als Teil eines StatefulWidgets zu implementieren, was die Lesbarkeit definitiv erschwert. Hinzu kommt der große Umfang an Boilerplate-Quelltext, da selbst für einfache Zustände wie das Speichern des Anmeldezustands drei Klassen benötigt werden.
Der zweite Faktor hierbei ist die Frage, inwiefern die Struktur klar nachvollziehbar und verständlich ist. Dies ist bei \texttt{Inherited\-Widgets} nicht der Fall, da besonders die Konstruktion aus mehreren Widgets und Widget-Typen schwer nachvollziehbar ist. So gibt es keinen zentralen Ort, welcher die Geschäftslogik übersichtlich zusammenfasst. Im Gegenteil ist es hier notwendig, die Geschäftslogik als Teil eines \texttt{Stateful\-Widgets} zu implementieren, was die Lesbarkeit definitiv erschwert. Hinzu kommt der große Umfang an Boilerplate-Quelltext, da selbst für einfache Zustände wie das Speichern des Anmeldezustands drei Klassen benötigt werden.
Der letzte Faktor befasst sich mit der tiefen Verschachtlung von Widgets (engl. Nesting). Nesting wirkt sich negativ auf die Lesbarkeit von Widgets aus. Auf Nesting kann allerdings bei diesem Ansatz nicht verzichtet werden, da InheritedWidgets in die Baumstruktur eingebaut werden, wie in \autoref{lst:nestingInheritedWidget} zu sehen ist, und somit immer ein Child-Widget übergeben bekommen.
Der letzte Faktor befasst sich mit der tiefen Verschachtlung von Widgets (engl. Nesting). Nesting wirkt sich negativ auf die Lesbarkeit von Widgets aus. Auf Nesting kann allerdings bei diesem Ansatz nicht verzichtet werden, da \texttt{Inherited\-Widgets} in die Baumstruktur eingebaut werden, wie in \autoref{lst:nestingInheritedWidget} zu sehen ist, und somit immer ein Child-Widget übergeben bekommen.
\begin{lstlisting}[caption={Nesting bei InheritedWidgets in app.dart \cite{repo}}, label={lst:nestingInheritedWidget}]
\begin{lstlisting}[caption={Nesting bei \texttt{Inherited\-Widgets} in app.dart \cite{repo}}, label={lst:nestingInheritedWidget}]
Zusammengefasst wird die Verständlichkeit/Lesbarkeit mit \textquote{nicht erfüllt} bewertet.
\paragraph{\nameref{sec:documentation}} Die Dokumentation gibt ein zwiegespaltenes Bild ab, da zum einen die einzelnen Widget-Typen an sich ausführlich beschrieben werden, allerdings das Zusammenspiel als Zustandsverwaltungssystem als Ganzes in der offiziellen Entwicklerdokumentation gar nicht beleuchtet wird. Dieses Zusammenspiel wird lediglich in Resourcen, außerhalb der Dokumentation beschrieben. Somit fehlen auch umfangreiche Beispiele, wie das Zustandsverwaltungssystem implementiert oder genutzt werden kann.
\paragraph{\nameref{sec:documentation}} Die Dokumentation gibt ein zwiegespaltenes Bild ab, da zum einen die einzelnen Widget-Typen an sich ausführlich beschrieben werden, allerdings das Zusammenspiel als Zustandsverwaltungssystem als Ganzes in der offiziellen Entwicklerdokumentation gar nicht beleuchtet wird. Dieses Zusammenspiel wird lediglich in Ressourcen, außerhalb der Dokumentation beschrieben. Somit fehlen auch umfangreiche Beispiele, wie das Zustandsverwaltungssystem implementiert oder genutzt werden kann.
Aufgrund der großen Bandbreite an Dritt-Resourcen zu InheritedWidgets erfolgt hier noch eine Bewertung mit \textquote{teilweise erfüllt}.
Aufgrund der großen Bandbreite an Dritt-Ressourcen zu \texttt{Inherited\-Widgets} erfolgt hier noch eine Bewertung mit \textquote{teilweise erfüllt}.
\paragraph{\nameref{sec:structure}}
Das Konzept mit InheritedWidgets stellt keine starren Strukturvorgaben an die Anwendung. Lediglich das Zusammenspiel zwischen InheritedWidget und StatefulWidget wird dadurch forciert, dass InheritedWidgets von sich aus ihren Zustand nicht ändern können. Dies hat allerdings keinen Einfluss auf die Gesamtarchitektur der Anwendung, das InheritedWidget und StatefulWidget als gemeinsame Komponente betrachtet werden können.
Das Konzept mit \texttt{Inherited\-Widgets} stellt keine starren Strukturvorgaben an die Anwendung. Lediglich das Zusammenspiel zwischen \texttt{Inherited\-Widget} und \texttt{Stateful\-Widget} wird dadurch forciert, dass \texttt{Inherited\-Widgets} von sich aus ihren Zustand nicht ändern können. Dies hat allerdings keinen Einfluss auf die Gesamtarchitektur der Anwendung, das \texttt{Inherited\-Widget} und \texttt{Stateful\-Widget} als gemeinsame Komponente betrachtet werden können.
Somit wird hier eine Bewertung mit \textquote{nicht erfüllt} vorgenommen.
@ -9,11 +9,11 @@ Für die Implementierung mit MobX werden die Bibliotheken mobx in der Version 2.
Für die Zustandsverwaltung wurden vier sogenannte Stores implementiert. Diese beinhalteten einen Teilzustand, sowie Computed-Values und Actions zum Ändern des Zustands. Dabei wurde jeweils ein Store für den Anmeldezustand, die Produktliste, den Warenkorb und die Anzahl eines Produktes im Warenkorb implementiert. Die Stores werden ergänzt durch automatisch generierten Quelltext von der MobX Bibliothek.
Die Stores werden über ein InheritedWidget in die Widget-Tree übergeben. Hierfür können auch beliebige andere Dependency-Injection-Lösungen genutzt werden. Auf Zustände wird innerhalb eines Observer-Widgets zugegriffen.
Die Stores werden über ein \texttt{Inherited\-Widget} in die Widget-Tree übergeben. Hierfür können auch beliebige andere Dependency-Injection-Lösungen genutzt werden. Auf Zustände wird innerhalb eines Observer-Widgets zugegriffen.
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit MobX \autocite[branch=mobx]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit MobX \autocite[branch=\\mobx]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}} Zur Skalierbarkeit lässt sich sagen, dass hier keine Hindernisse bei der Skalierbarkeit festgestellt werden konnten. So lassen sich beliebig viele Zustände miteinander koppeln, da hier ein einfacher Zugriff auf Observable-Variablen genügt, um Zustandsaktualisierungen zu erhalten.
@ -23,18 +23,18 @@ Aufgrund der einfachen Kopplung von Zuständen und der Änderbarkeit wird MobX h
\paragraph{\nameref{sec:testability}} Bei der Testbarkeit wird zum einen geprüft, inwiefern sich die Geschäftslogik testen lässt, und wie sich die Zustände bei Widget-Tests durch Platzhalter ersetzen lassen.
Die Tests der Geschäftslogik konnten ohne zusätzliche Maßnahmen implementiert werden, da nach außen hin hier eine Klasse mit Variablen und Funktionen existiert, die sich mit den mitteln eines Unit-Test testen lässt.
Die Tests der Geschäftslogik konnten ohne zusätzliche Maßnahmen implementiert werden, da nach außen hin hier eine Klasse mit Variablen und Funktionen existiert, die sich mit den Mitteln eines Unit-Test testen lässt.
Bei den Widget-Tests ist es nötig, die Store-Klasse mit neuen Platzhalter-Klassen, die von den Store-Klassen erben, zu ersetzen. Damit lassen sich erfolgreich Stores durch triviale Platzhalter ersetzen.
Die Testbarkeit wird mit \textquote{vollständig erfüllt} bewertet.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei MobX}]{results/mobx/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei MobX}]{results/mobx/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:mobx}) ergab eine \ac{mi} von 83 für das gesamte Projekt.
\paragraph{\nameref{sec:readability}} Zur Verständlichkeit und Lesbarkeit lässt sich sagen, dass mit MobX hier ein Ansatz verfolgt wird, der zur Lesbarkeit beiträgt, da hier mit Hilfe der Annotationen wie \texttt{computed} der Sprachfluss beim Lesen unterstützt wird. Zusätzlich beinhalten die Zustandsklassen nur die tatsächlich notwendige Geschäftslogik und Prozeduren für die Zustandsverwaltung werden mittels Quelltext-Generierung ausgelagert. Somit ist die Struktur nachvollziehbar und klar.
\paragraph{\nameref{sec:readability}} Zur Verständlichkeit und Lesbarkeit lässt sich sagen, dass mit MobX hier ein Ansatz verfolgt wird, der zur Lesbarkeit beiträgt, da hier mithilfe der Annotationen wie \texttt{computed} der Sprachfluss beim Lesen unterstützt wird. Zusätzlich beinhalten die Zustandsklassen nur die tatsächlich notwendige Geschäftslogik und Prozeduren für die Zustandsverwaltung werden mittels Quelltext-Generierung ausgelagert. Somit ist die Struktur nachvollziehbar und klar.
Die Quelltext-Generierung kann allerdings auch ein Risiko darstellen, da Fehler oder Probleme im generierten Quelltext schwer nachzuvollziehen sind und der generierte Quelltext schwer lesbar ist.
@ -50,6 +50,6 @@ Die Dokumentierung wird daher mit \textquote{vollständig erfüllt} bewertet.
\paragraph{\nameref{sec:structure}} Zur Strukturbestimmung lässt sich sagen, dass MobX über die Annotationen und Quelltext-Generierung eine gewisse Struktur der Zustände vorgibt, allerdings leitet sich daraus keine Struktur für die gesamte Anwendung ab.
Technisch forciert, wird die Struktur der Zustände in Teilen durch die Quelltext-Generierung, wenn von Vorgaben abgewichen wird. So schlägt beispielsweise die Quelltext-Generierung fehl, wenn man versucht Funktionen mit Parametern als \texttt{computed} zu markieren.
Technisch forciert, wird die Struktur der Zustände in Teilen durch die Quell\-text-Generierung, wenn von Vorgaben abgewichen wird. So schlägt beispielsweise die Quell\-text-Generierung fehl, wenn man versucht, Funktionen mit Parametern als \texttt{computed} zu markieren.
Die Strukturbestimmung wird daher mit \textquote{teilweise erfüllt} bewertet.
@ -7,27 +7,27 @@ Provider stellt die erste externe Bibliothek zur Zustandsverwaltung dar, die in
Für die Implementierung der Zustandsverwaltung mit der Provider Bibliothek wurde die Version 6.0.2 eingesetzt. Die Zustände wurden dabei in 4 Stores beziehungsweise Providern umgesetzt. Für die Verwaltung des Anmeldezustands wurde ein ChangeNotifier mit gleichnamigen Provider verwendet (siehe dazu \autoref{lst:providerStore}). Zum Laden der Produkte wurde eine FutureProvider eingesetzt. Dieser stellt das Ergebnis eines Futures, also einer asynchronen Operation, zur Verfügung und kann auch auf Fehler beim Warten auf das Future reagieren. Für die Produktliste wurde eine ProxyProvider2 eingesetzt. Dieser ermöglicht es, die Zustände zweier verschiedener Provider zu kombinieren. In diesem Fall wurde der Zustand der beiden bereits beschriebenen Provider kombiniert, um den Rabatt anzuwenden. Für die Abbildung des Warenkorbs wurde ein ChangeNotifierProxyProvider verwendet. Dies stellt eine Mischung aus einem ProxyProvider und einem ChangeNotifierProvider dar, und ermöglicht es so ChangeNotifier-Klassen, über Änderungen von anderen Providern zu informieren.
In die Benutzeroberfläche eingebunden, werden die Provider über diverse Extension-Funktionen für den BuildContext. Dabei lässt sich der Zustand abonnieren oder exakt einmal abrufen, falls keine Aktualisierungen erwünscht sind. Zudem lässt sich auch ein Teil des Zustands selektieren, was dazu führt, dass das Widget nur aktualisiert wird, wenn sich dieser Teil des Zustands tatsächlich ändert.
In die Benutzeroberfläche eingebunden, werden die Provider über diverse Exten\-sion-Funktionen für den BuildContext. \\Dabei lässt sich der Zustand abonnieren oder exakt einmal abrufen, falls keine Aktualisierungen er\-wünscht sind. Zudem lässt sich auch ein Teil des Zustands selektieren, was dazu führt, dass das Widget nur aktualisiert wird, wenn sich dieser Teil des Zustands tatsächlich ändert.
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit Provider \autocite[branch=provider]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit Provider \autocite[branch=\\provider]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}}
Das Thema Skalierbarkeit und Änderbarkeit zeigt sich als ambivalent bei Providern. Bei der Änderbarkeit muss man sagen, dass hier Provider einen Vorteil bietet, da durch das Konzept der Provider eine einheitliche Abstraktion für Zustandsverwaltungen geschaffen werden. Somit ist es nicht von Belang, ob ein Zustand über einen Stream, Future oder einen ChangeNotifier abgebildet werden. Dies bietet damit einen Vorteil für Änderbarkeit, da sich Zustände zu einem späteren Zeitpunkt einfach auf ein anderes Zustandsverwaltungsmodell abändern lassen, ohne das Anpassungen außerhalb dieses Zustands von Nöten wären.
Das Thema Skalierbarkeit und Änderbarkeit zeigt sich als ambivalent bei Providern. Bei der Änderbarkeit muss man sagen, dass hier Provider einen Vorteil bietet, da durch das Konzept der Provider eine einheitliche Abstraktion für Zustandsverwaltungen geschaffen werden. Somit ist es nicht von Belang, ob ein Zustand über einen Stream, Future oder einen ChangeNotifier abgebildet werden. Dies bietet damit einen Vorteil für Änderbarkeit, da sich Zustände zu einem späteren Zeitpunkt einfach auf ein anderes Zustandsverwaltungsmodell abändern lassen, ohne das Anpassungen außerhalb dieses Zustands vonnöten wären.
Eine Designentscheidung von Provider zum Nachteil der Änderbarkeit ist es, dass Provider alleine über Ihren Klassentyp per Generic in Widgets injiziert werden. Dabei kann zur Kompilierungszeit nicht geprüft werden, ob der referenzierte Zustand sich auch tatsächlich in der Widget-Hierarchie befindet. Somit können bei Änderungen in der Hierarchie von Widgets zu einem späteren Zeitpunkt Laufzeitfehler auftreten, die nicht durch den Compiler gefunden werden können.
Zur Skalierbarkeit lässt sich sagen, dass in der Dokumentation bereits Limitationen für den Einsatz von Providern gezeigt werden. So träten ab der Verwendung von mehr als 150 Providern StackOverflowErrors auf. \autocite{providerReadme} Zudem ist die Koppelung von Zuständen stark begränzt. Dies liegt daran, dass es für jede Anzahl der zu koppelnden Zustände eine entsprechende Klasse in der Provider-Bibliothek geben muss. Aktuell ist dies mit der Klasse ProxyProvider6 auf sechs Klassen begrenzt. Ob diese Limitation in der Realität jedoch tatsächlich relevant ist, ist fraglich, da es wohl architekturell besser wäre in diesem Fall, die Zustände in mehrere Zustände zusammenzufassen.
Zur Skalierbarkeit lässt sich sagen, dass in der Dokumentation bereits Limitationen für den Einsatz von Providern gezeigt werden. So träten ab der Verwendung von mehr als 150 Providern StackOverflowErrors auf. \autocite{providerReadme} Zudem ist die Koppelung von Zuständen stark begrenzt. Dies liegt daran, dass es für jede Anzahl der zu koppelnden Zustände eine entsprechende Klasse in der Provider-Bibliothek geben muss. Aktuell ist dies mit der Klasse ProxyProvider6 auf sechs Klassen begrenzt. Ob diese Limitation in der Realität jedoch tatsächlich relevant ist, ist fraglich, da es wohl architekturell besser wäre in diesem Fall, die Zustände in mehrere Zustände zusammenzufassen.
Aufgrund der eingeschränkten Skalierbarkeit, wird die Änderbarkeit/Skalierbarkeit mit \textquote{teilweise erfüllt} bewertet.
\paragraph{\nameref{sec:testability}} Zur Testbarkeit lässt sich sagen, dass sowohl die Geschäftslogik als auch verwendende Widgets sich ohne Änderungen am Quelltext der Anwendung testen lassen.
Bei der Geschäftslogik lassen sich die Zustandsklassen komplett ohne die Verwendung von Flutter oder von Provider an sich testen. Somit ist es auch nicht nötig, etwaige Abhängigkeiten zu Mocken oder Widget-Tests zu benutzen.
Bei der Geschäftslogik lassen sich die Zustandsklassen komplett ohne die Verwendung von Flutter oder von Provider an sich testen. Somit ist es auch nicht nötig, etwaige Abhängigkeiten zu mocken oder Widget-Tests zu benutzen.
Bei den Tests der Widgets, die auf Zustände zugreifen, lässt sich sagen, dass das Mocking hier unaufwendig ist, da Provider die jeweiligen Zustände abstrahiert und so es möglich ist, einfache \textquote{Value}-Provider zu injizieren, wie in \autoref{lst:providertest} zu sehen ist. Diese können dann wie im Beispiel zu sehen ist, einfache Klassen injizieren, die die Eigenschaften der ursprünglichen Zustandsklasse überschreiben.
Bei den Tests der Widgets, die auf Zustände zugreifen, lässt sich sagen, dass das Mocking hier unaufwändig ist, da Provider die jeweiligen Zustände abstrahiert und so es möglich ist, einfache \textquote{Value}-Provider zu injizieren, wie in \autoref{lst:providertest} zu sehen ist. Diese können dann wie im Beispiel zu sehen ist, einfache Klassen injizieren, die die Eigenschaften der ursprünglichen Zustandsklasse überschreiben.
\begin{lstlisting}[caption={Widget-Test bei Provider in total\_price\_test.dart \cite{repo}}, label={lst:providertest}]
class CartStoreMock extends CartStore {
@ -50,16 +50,16 @@ void main() {
}
\end{lstlisting}
Da sowohl Widget-Tests als Geschäftslogik-Tests implementierbar waren, wird die Testbarkeit mit \textquote{vollständig erfüllt} bewertet.
Da sowohl Widget-Tests als Geschäftslogik-Tests implementierbar waren, \\wird die Testbarkeit mit \textquote{vollständig erfüllt} bewertet.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei Provider}]{results/provider/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei Provider}]{results/provider/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:provider}) ergab eine \ac{mi} von 83 für das gesamte Projekt.
\paragraph{\nameref{sec:readability}} Die Verständlichkeit und Lesbarkeit lässt sich hier anhand mehrerer Aspekte bewerten.
Bei Provider werden hauptsächlich bereits bestehende Konzepte von Flutter erweitert. Dies kann beispielsweise an der Injizierung der Zustandsklassen in Widgets beobachtet werden. Hier wird ein ähnliches Syntax wie bei anderen Flutter-Komponenten genutzt wie auch im Beispiel in \autoref{lst:providerof} zu sehen ist. Jedoch muss hier auch angemerkt werden, dass es empfehlenswert ist, immer eine Zustandsklasse zu erstellen, auch wenn die an die Widgets zu übergebenden Zustände einem trivialen Datentyp wie einem \texttt{String} entsprechen, da für das Injizieren immer das Klassen-Name der zu injizierenden Klasse verwendet wird. So ist es also nicht möglich, mehrere Zustände vom Type \texttt{String} gleichzeitig zu verwenden. Diese zusätzlichen Klassen können somit die Lesbarkeit erschweren.
Bei Provider werden hauptsächlich bereits bestehende Konzepte von Flutter erweitert. Dies kann beispielsweise an der Injizierung der Zustandsklassen in Widgets beobachtet werden. Hier wird eine ähnliche Syntax wie bei anderen Flutter-Komponenten genutzt wie auch im Beispiel in \autoref{lst:providerof} zu sehen ist. Jedoch muss hier auch angemerkt werden, dass es empfehlenswert ist, immer eine Zustandsklasse zu erstellen, auch wenn die an die Widgets zu übergebenden Zustände einem trivialen Datentyp wie einem \texttt{String} entsprechen, da für das Injizieren immer der Klassen-Name der zu injizierenden Klasse verwendet wird. So ist es also nicht möglich, mehrere Zustände vom Type \texttt{String} gleichzeitig zu verwenden. Diese zusätzlichen Klassen können somit die Lesbarkeit erschweren.
\begin{lstlisting}[caption={Vergleich des Abrufs von Zuständen zwischen Flutter und Provider}, label={lst:providerof}]
Die Zustandsklassen an sich führen auch keine neuen Konzepte ein und Verwenden bereits bekannte Konzepte wie die ChangeNotifier. Diese Zustandsklassen bieten auch eine gute Lesbarkeit, da hier direkt über Variabeln auf den Zustand zugegriffen wird und über Funktionen dieser direkt geändert wird. Hier muss also keine Spezialbehandlung aufgrund der Zustandsverwaltung genutzt werden.
Auf der anderen Seite werden Konstrukte wie der ProxyProvider, welcher die Verknüpfung von mehreren Zuständen ermöglicht, schnell auf Grund der vielen Parameter unübersichtlich.
Auf der anderen Seite werden Konstrukte wie der ProxyProvider, welcher die Verknüpfung von mehreren Zuständen ermöglicht, schnell aufgrund der vielen Parameter unübersichtlich.
Der letzte untersuchte Aspekt, ist ob das Zustandsverwaltungssystem, der tiefen Verschachtlung wirksam entgegentritt. Dazu lässt sich konstatieren, dass hier durch die Einführung eines sogenannten \texttt{MultiProvider} dieses Problem umgangen wird. Zwar muss bei Provider, alle Provider in den Widget-Baum eingesetz werden, allerdings kann ein \texttt{MultiProvider} eine Liste von Providern entgegennehmen und in den Widget-Baum einsetzen. Somit kommt es nicht zu einer tiefen Verschachtlung.
Der letzte untersuchte Aspekt, ist, ob das Zustandsverwaltungssystem, der tiefen Verschachtlung wirksam entgegentritt. Dazu lässt sich konstatieren, dass hier durch die Einführung eines sogenannten \texttt{MultiProvider} dieses Problem umgangen wird. Zwar muss bei Provider, alle Provider in den Widget-Baum eingesetzt werden, allerdings kann ein \texttt{MultiProvider} eine Liste von Providern entgegennehmen und in den Widget-Baum einsetzen. Somit kommt es nicht zu einer tiefen Verschachtlung.
Zusammenfassend wird dieser Bewertungsaspekt mit \textquote{teilweise erfüllt} bewertet.
@ -15,7 +15,7 @@ Für die Einbindung in die Benutzeroberfläche kommen zwei verschiedene Möglich
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit Redux \autocite[branch=redux]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit Redux \autocite[branch=\\redux]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}} Die Änderbarkeit und Skalierbarkeit bei Redux lässt sich anhand verschiedener Aspekte beschreiben.
@ -37,8 +37,8 @@ Der zentrale Zustand sollte selbst keine Geschäftslogik beinhalten und lässt s
Aufgrund der Probleme beim Testen der Middlewares, wird die Testbarkeit mit \textquote{teilweise erfüllt} bewertet.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei Redux}]{results/redux/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei Redux}]{results/redux/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:redux}) ergab eine \ac{mi} von 82 für das gesamte Projekt.
@ -52,7 +52,7 @@ Das Problem der tiefen Verschachtlung findet bei Redux keine Anwendung, da hier
Die Verständlich und Lesbarkeit wird abschließend mit \textquote{teilweise erfüllt} bewertet.
\paragraph{\nameref{sec:documentation}} Die Dokumentation \autocite{reduxDocs} von Redux beschreibt die grundlegenden Konzepte sowie verweist für detaillierte Erklärungen auf die Dokumentation von Redux für React. Zudem existieren in der Dokumentation detaillierte Anwendungsbeispiele. Zusätzlich zur offiziellen Dokumentation, gibt es eine große Zahl an Publikationen für Redux für React, die in großen Teilen anwendbar auf die Flutter-Umsetzung von Redux sind.
\paragraph{\nameref{sec:documentation}} Die Dokumentation \autocite{reduxDocs} von Redux beschreibt die grundlegenden Konzepte, sowie verweist für detaillierte Erklärungen auf die Dokumentation von Redux für React. Zudem existieren in der Dokumentation detaillierte Anwendungsbeispiele. Zusätzlich zur offiziellen Dokumentation, gibt es eine große Zahl an Publikationen für Redux für React, die in großen Teilen anwendbar auf die Flutter-Umsetzung von Redux sind.
Die Dokumentierung wird daher mit \textquote{vollständig erfüllt} bewertet.
@ -5,15 +5,15 @@ Riverpod stellt eine externe Bibliothek zur Zustandsverwaltung dar, welche das K
\subsection{Implementierung}
Für die Implementierung wurde die Bibliotheken \texttt{riverpod} und \texttt{flutter\_riverpod} in der Version 1.0.3 verwendet.
Für die Implementierung wurde die Bibliotheken \texttt{riverpod} und \texttt{flutter\_ri\-verpod} in der Version 1.0.3 verwendet.
Die Zustände wurden dabei über diverse Provider. Der Anmeldezustand beispielsweise wurde mittels eines StateNotifierProvider umgesetzt, welcher eine StateNotifier-Klasse, wie im Grundlagenkapitel erläutert, injiziert. Zudem wurde ein FutureProvider genutzt, um die Produkte zu laden und ein einfacher Provider, um den Anmeldezustand mit den geladenen Produkten zu verknüpfen und somit den Rabatt anzuwenden. Der Zustand des Warenkorbs wird über mehrere Provider abgebildet. So stellt ein StateNotifierProvider die Grundlage für den Warenkorb. Diese wird dann anschließend durch weitere Provider um zusätzliche Informationen ergänzt wie beispielsweise den Produktdaten. Alle Provider werden als globale finale Variablen der Benutzeroberfläche zur Verfügung gestellt.
Die Einbindung in die Benutzeroberfläche erfolgte mittels ConsumerWidget. Dafür werden die Basisklassen der Widgets, die auf Zustände zugreifen müssen auf ConsumerWidget geändert. Dadurch erhält man in der build-Methode einen zusätzlichen Parameter, der es erlaubt die Provider anhand ihrer globale Variablen auszulesen.
Die Einbindung in die Benutzeroberfläche erfolgte mittels ConsumerWidget. Dafür werden die Basisklassen der Widgets, die auf Zustände zugreifen müssen auf ConsumerWidget geändert. Dadurch erhält man in der build-Methode einen zusätzlichen Parameter, der es erlaubt die Provider anhand ihrer globalen Variablen auszulesen.
\subsection{Bewertung}
Im folgenden Abschnitt wird die Implementierung mit Riverpod \autocite[branch=riverpod]{repo} anhand der definierten Bewertungskriterien bewertet.
Im folgenden Abschnitt wird die Implementierung mit Riverpod \autocite[branch=\\riverpod]{repo} anhand der definierten Bewertungskriterien bewertet.
\paragraph{\nameref{sec:changeablility}} Zur Änderbarkeit und Skalierbarkeit bei Riverpod lassen sich Parallelen zu Provider ziehen, da hier ähnliche Konzepte zum Einsatz kommen. So bietet Riverpod auch eine einheitliche Abstraktionsschicht für Zustände und kann somit ChangeNotifier, StateNotifier, Futures, Streams und einfache Werte als Zustand verwalten. Somit werden Änderungen in der Zukunft vereinfacht, da nur die jeweiligen Zustände und Provider angepasst werden müssen und nicht die davon abhängigen Zustände und andere Komponenten.
@ -49,8 +49,8 @@ Bei den Widget-Tests bietet Riverpod die Möglichkeit an, beliebig viele Provide
Die Testbarkeit wird daher mit \textquote{vollständig erfüllt} bewertet.
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke, ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Render-Vorgänge bei Riverpod}]{results/riverpod/benchmarks.txt}
\paragraph{\nameref{sec:efficiency}} Nach der Ausführung der Teststrecke ergaben die Zähler folgendes Ergebnis:
\lstinputlisting[caption={Anzahl der Rendervorgänge bei Riverpod}]{results/riverpod/benchmarks.txt}
\paragraph{\nameref{sec:complexity}} Die Auswertung der Metriken (vgl. \autoref{metrics:riverpod}) ergab eine \ac{mi} von 80 für das gesamte Projekt.
@ -60,7 +60,7 @@ Im Gegensatz zu Provider wird bei Riverpod eine neue Syntax zur Injizierung von
Die Verwendung von Variablen anstatt Generics zum Abruf der Provider, erhöht die Verständlichkeit, da somit direkt aus dem Variabl-Namen klar wird, um was es sich handelt. Zudem besteht somit die Möglichkeit, mehere Provider mit dem gleichen Datentyp zu haben. Damit kann dann auf Wrapper-Klassen, verzichtet werden, die die Komplexität und damit die Lesbarkeit behindern.
Das neu eingeführte Konzept des StateNotifier bietet aus der Perspektive der Lesbarkeit und Verständlichkeit keine Vorteile, da damit die Zustandsänderungs-Funktionen und der Inhalt des Zustands in getrennten Klassen behandelt werden, zudem muss zur Änderung des Zustands immer eine neue Instanz des Zustandsobjekts \texttt{state} gesetzt werden, was auf den ersten Blick verwirrend wirken kann. Die Vorteile des StateNotifier liegen eher in der Verbesserung der Performance, da hier bei jeder Zustandsänderung tatsächlich geprüft wird, ob die Zustandsvariable selbst sich tatsächlich geändert hat.
Das neu eingeführte Konzept des StateNotifier bietet aus der Perspektive der Lesbarkeit und Verständlichkeit keine Vorteile, da damit die Zustands\-änderungs-Funktionen und der Inhalt des Zustands in getrennten Klassen behandelt werden, zudem muss zur Änderung des Zustands immer eine neue Instanz des Zustandsobjekts \texttt{state} gesetzt werden, was auf den ersten Blick verwirrend wirken kann. Die Vorteile des StateNotifier liegen eher in der Verbesserung der Performance, da hier bei jeder Zustandsänderung tatsächlich geprüft wird, ob die Zustandsvariable selbst sich tatsächlich geändert hat.
Die Möglichkeit andere Zustände über die Referenzvariable zu injizieren hingegen, verbessert die Lesbarkeit, da hier im Vergleich zu ProxyProvider sofort klar wird, warum dieser Provider gelesen oder abonniert werden muss.
In der mobilen Software-Entwicklung dominieren seit Jahren bereits die beiden mobilen Betriebssysteme iOS und Android. |\autocite{osDist} Dies macht es für die Entwicklung 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 Entwicklung 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 der mobilen Software-Entwicklung dominieren seit Jahren die beiden mobilen Betriebssysteme iOS und Android. |\autocite{osDist} Dies macht es für die Entwicklung 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 Entwicklung 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 \autocite{flutterFirstRelease} veröffentlicht und unterstützt mit der Version 1.20 die Möglichkeit eine Anwendung sowohl als App auf Android und iOS, als Desktop-Anwendung für macOS, Linux und Windows sowie als Website auszuspielen. \autocite{flutterSupportedPlatforms} Weitere Details zur Flutter-Technologie werden im entsprechenden Grundlagen-Kapitel behandelt.
@ -8,8 +8,8 @@ Im Anschluss wird im Grundlagenkapitel anhand von Literaturrecherchen und Quellt
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 Realisierungskapitel 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 protokolliert.
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 protokolliert.
Im Evaluationskapitel werden die Ergebnisse aus dem Entwurfs- und Realisierungskapitel anhand der im Analysekapitel gewonnen Kritieren bewertet und die Ergebnisse analysiert. Zum Schluss findet eine Gesamtbewertung der Lösungsansätze statt.
Im Evaluationskapitel werden die Ergebnisse aus dem Entwurfs- und Realisierung-Kapitel anhand der im Analyse-Kapitel gewonnen Kriterien bewertet und die Ergebnisse analysiert. Zum Schluss findet eine Gesamtbewertung der Lösungsansätze statt.
Am Ende der Ausarbeitung wird die Bewertung in einem Gesamtzusammenhang gesetzt und eine Empfehlung abgegeben, sowie mögliche weitere Forschungsfragen diskutiert.
Abschließend wird die Bewertung in einem Gesamtzusammenhang gesetzt und eine Empfehlung abgegeben, sowie mögliche weitere Forschungsfragen diskutiert.
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 Zwischenziele zu definieren. Das Erste Ziele ist dabei, zu analysieren, welche Probleme aktuell bei der Zustandsverwaltung in Flutter bestehen, um im nächsten Schritt geeignete Kriterien für die Bewertung von Lösungsansätzen zu finden. Dabei ist es auch ein Ziel, diese Bewertungsansätze möglichst objektiv definieren zu können, da die Auswahl von geeigneten Kriterien entscheidend ist für die Aussagekraft der Evaluation.
Zur Erreichung dieses übergeordneten Zieles ist es erforderlich, einige Zwischenziele zu definieren. Das Erste Ziele ist dabei, zu analysieren, welche Anforderungen aktuell an die Zustandsverwaltung in Flutter gestellt werden, um im nächsten Schritt geeignete Kriterien für die Bewertung von Lösungsansätzen zu finden. Dabei ist es auch ein Ziel, diese Bewertungsansätze möglichst objektiv definieren zu können, da die Auswahl von geeigneten Kriterien entscheidend ist für die Aussagekraft der Evaluation.
Nachdem nun die Anforderungen an die Zustandsverwaltungssysteme definiert worden sind, bedarf es eines Versuchsaufbaus, um die Zustandsverwaltungssysteme einer Evaluation unterziehen zu können.
Um die Evaluation möglichst praxisnah zu gestalten, macht es Sinn diese anhand einer Beispielanwendung durchzuführen. Dies bietet die Vorteile, dass die Zustandsverwaltungssystemen im Test realistischen Bedingungen unterzogen werden und somit die Aussagekraft der Evaluation höher ist. Dieses Vorgehen birgt allerdings das Risiko, dass andere Variablen, die nicht Teil der Evaluation sind, die Ergebnisse beeinflussen. Um dieses Risiko zu minimieren werden Maßnahmen getroffen, die sicherstellen sollen, dass ausschließlich die Auswirkungen der Implementierung mit einem Zustandsverwaltungssystem überprüft werden.
Um die Evaluation möglichst praxisnah zu gestalten, ist es sinnvoll, diese anhand einer Beispielanwendung durchzuführen. Dies bietet die Vorteile, dass die Zustandsverwaltungssysteme im Test realistischen Bedingungen unterzogen werden und somit die Aussagekraft der Evaluation höher ist. Dieses Vorgehen birgt allerdings das Risiko, dass andere Variablen, die nicht Teil der Evaluation sind, die Ergebnisse beeinflussen. Um dieses Risiko zu minimieren werden Maßnahmen getroffen, die sicherstellen sollen, dass ausschließlich die Auswirkungen der Implementierung mit einem Zustandsverwaltungssystem überprüft werden.
\section{Anforderungen an die Beispielanwendung}
@ -27,7 +27,7 @@ Nachdem nun die impliziten Anforderungen definiert worden sind, sollten nun weit
Die Beispielanwendung sollte so mehrere Seiten haben, um das Verhalten der Zustandsverwaltung auch nach einer Navigation testen zu können. Hier ist es wichtig, dass auf allen Seiten in der App die Daten persistent sind, und sich nicht unterscheiden.
In einer mobilen Anwendung werden oft Eingabemöglichkeiten für Nutzer*innen bereitgestellt, die den Zustand der Anwendung verändern. Daher sollte auch diese Beispielanwendung eine Form von zustandsverändernden Interaktion besitzen.
In einer mobilen Anwendung werden oft Eingabemöglichkeiten für Nutzer*innen bereitgestellt, die den Zustand der Anwendung verändern. Daher sollte auch diese Beispielanwendung eine Form von Zustands-verändernden Interaktion besitzen.
Auch das Laden von Daten für den Anwendungszustand aus anderen Quellen wie beispielsweise von einem Server sollte implementiert werden, damit im gleichen Zug auch getestet werden kann, was im Falle einer Zeitüberschreitung oder eines Verbindungsfehlers passiert und wie die Fehlerbehandlung der Zustandsverwaltung funktioniert.
@ -35,7 +35,7 @@ Auch das Laden von Daten für den Anwendungszustand aus anderen Quellen wie beis
Anhand der aufgestellten Kriterien wird im folgenden Abschnitt die Beispielanwendung für die Evaluation konzipiert.
Als Anwendungsfall für die Beispielanwendung wurde hier die Produktauswahl- und Warenkorbansicht eines Internetshops gewählt. Dieser Anwendungsfall bietet den Vorteil, das mit ihm alle gestellten Anforderungen umgesetzt werden können und gleichzeitig ein realistisches Szenario zum Einsatz gelangt.
Als Anwendungsfall für die Beispielanwendung wurde hier die Produkt\-auswahl- und Warenkorb\-ansicht eines Internetshops gewählt. Dieser Anwendungsfall bietet den Vorteil, das mit ihm alle gestellten Anforderungen umgesetzt werden können und gleichzeitig ein realistisches Szenario zum Einsatz gelangt.
Grundlage bildet eine Liste mit verschiedenen Produkten, die aus dem Internet geladen wird, aus der die Benutzer*innen eine gewisse Anzahl eines oder mehrerer Produkte in einen Warenkorb legen können. Zudem existiert eine Warenkorbansicht, in der alle Produkte und ein Gesamtpreis zu sehen sind, die sich aktuell im Warenkorb befinden. Hiermit werden bereits alle expliziten Anforderungen erfüllt.
@ -43,7 +43,7 @@ Grundlage bildet eine Liste mit verschiedenen Produkten, die aus dem Internet ge
Aufbauend auf diesen Anwendungsfall werden nun einzelne Funktionen anhand der gestellten Anforderungen beschrieben.
Um die Koppelung verschiedener Zustände testen zu können, wird in der Beispielanwendung die Möglichkeit angeboten, sich als registrierte*r Benutzer*in an- und abzumelden. Dies geschieht durch einen Schalter, welcher den aktuellen Anmeldezustand darstellt. Die Kopplung besteht nun darin, dass registrierten Benutzer*innen ein Rabatt von 20\% gewährt wird, und somit eine Kopplung zwischen der Berechnung des Warenkorbgesamtpreises und des Anmeldezustands notwendig wird.
Um die Koppelung verschiedener Zustände testen zu können, wird in der Beispielanwendung die Möglichkeit angeboten, sich als registrierte*r Benutzer*in an- und abzumelden. Dies geschieht durch einen Schalter, welcher den aktuellen Anmeldezustand darstellt. Die Kopplung besteht nun darin, dass registrierten Benutzer*innen ein Rabatt von 20\% gewährt wird, und somit eine Kopplung zwischen der Berechnung des Warenkorbgesamtpreises und des Anmeldezustands notwendig wird.
Gleichzeitig wird hier auch die Anforderung zum Testen der tiefen Verschachtlung (engl. nesting) erfüllt, da hier mehrere unabhängige Zustände auf einer Seite benötigt werden.
@ -64,7 +64,7 @@ Nachdem nun der Funktionsumfang der Anwendung beschrieben wurde, wird daraus ein
\section{Vorgehen bei der Implementierung}
Um diese Beispielanwendung für die Evaluation passend umsetzen zu können, ist eine nähere Beschreibung des Vorgehens bei der Implementierung von Nöten, um den späteren Versuchsaufbau nachvollziehen zu können.
Um diese Beispielanwendung für die Evaluation passend umsetzen zu können, ist eine nähere Beschreibung des Vorgehens bei der Implementierung notwendig, um den späteren Versuchsaufbau nachvollziehen zu können.
Für jedes zu evaluierende Zustandsverwaltungssystem muss eine eigene Anwendung entwickelt werden. Um sowohl den Aufwand der Implementierung zu verringern und die Vergleichbarkeit zu verbessern, bietet es sich an, eine gemeinsame Grundlage für alle Anwendungen zu schaffen. Diese sollte nur die Benutzeroberfläche mit Platzhaltern, sowie die Geschäftslogik zum Abrufen von Daten für die Produktliste beinhalteten.
@ -95,15 +95,15 @@ Neben den sichtbaren Elementen der Anwendung wurde auch eine Infrastruktur zur D
Die Messung der \nameref{sec:complexity} erfolgt über die Metrik des \acl{mi}. Dieser Wert wird mit dem Tool \texttt{dart\_code\_metrics} ermittelt. Um die Berechnung unabhängig von der Laufzeitumgebung zu machen, wird die Berechnung in einem Docker-Container mit einem \ac{ci} durchgeführt. Die einzelnen Durchführungsschritte sind dabei in der \ac{ci}-Konfigurationsdatei \autocite[.drone.yml]{repo} im Unterschritt \texttt{generate\_metrics} dokumentiert. Die vollständigen Testergebnisse sind dieser Ausarbeitung im Anhang beigefügt.
Für die Messung der \nameref{sec:efficiency} wird an zwei Messpunkten gemessen, wie oft ein Widget neu gebaut werden muss. Dazu wird ein Zähler erhöht, wenn die Build-Methode des entsprechenden Widgets aufgerufen worden ist. Zur Messung wird ein automatisierter Test \autocite[test/efficiency\_bechmark\_test.dart]{repo} durchgeführt, welcher mehrere Produkte zum Warenkorb hinzufügt und wieder entfernt, und die An- und Abmeldung des Benutzenden auslöst. Im letzten Schritt wird zur Warenkorbansicht navigiert.
Für die Messung der \nameref{sec:efficiency} wird an zwei Messpunkten gemessen, wie oft ein Widget neu gebaut werden muss. Dazu wird ein Zähler erhöht, wenn die Build-Methode des entsprechenden Widgets aufgerufen worden ist. Zur Messung wird ein automatisierter Test \autocite[test/\-efficiency\_\-bechmark\_\-test\-.dart]{repo} durchgeführt, welcher mehrere Produkte zum Warenkorb hinzufügt und wieder entfernt, und die An- und Abmeldung des Benutzenden auslöst. Im letzten Schritt wird zur Warenkorbansicht navigiert.
Die beiden Messpunkte wurden dabei an folgenden Stellen angebracht. Der erste Messpunkt \texttt{cartButton} wird ausgelöst beim Rendern des Warenkorb-Buttons und der zweite Messpunkt \texttt{userSwitch} wird ausgelöst beim Rendern des Schalters zum An- und Abmelden. Diese beiden Widgets wurden gewählt, da sie von Zustandsänderungen betroffen sind, allerdings nicht von allen Zustandsänderungen der Anwendung und außerhalb einer Listenansicht sind.
Beim Ausführen des Tests für die beschriebene Basisanwendung ohne Zustandsverwaltung wurde folgendes Ergebnis ermittelt:
Anzahl der Render-Vorgänge:
Anzahl der Rendervorgänge:
\lstinputlisting[caption={Anzahl der Render-Vorgänge}]{results/main/benchmarks.txt}
\lstinputlisting[caption={Anzahl der Rendervorgänge}]{results/main/benchmarks.txt}
Diese Ergebnisse stellen die Minimalwerte für den Test dar.
Flutter has multiple approaches in managing the state of an application. Flutter is a popular cross-platform framework for the development of applications 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 application 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 management in Flutter. The objective is to determine, which approach is the most suitable approach for managing state in Flutter. In order to meet this objective, 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.
This thesis describes multiple already existing approaches for state management in Flutter. The objective is to determine which approach is the most suitable approach for managing state in Flutter. In order to meet this objective, 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 recommendation which approach to use for a specific use case.