You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
thesis/chapters/basics/state-management.tex

255 lines
24 KiB

\pagebreak
\section{Zustandsverwaltung}
Nachdem nun die Grundlagen des Flutter-Frameworks und die Details der Zustandsverwaltung der Widgets im letzten Kapitel erläutert wurden, kann jetzt die Zustandsverwaltung im Detail betrachtet werden.
\citeauthor{flutterinaction} fasst den Komplex der Zustandsverwaltung in Flutter in seinem Standardwerk \textit{\citetitle{flutterinaction}} wie folgt zusammen:
\begin{displayquote}[{\cite[Kap.8.1.2]{flutterinaction}}]
State management is a combination of passing data around the app, but also re-rendering pieces of your app at the right time. All the re-rendering in Flutter is dependent on the State object and its lifecycle.
\end{displayquote}
Daraus ergibt sich, dass die State-Klasse in der Zustandsverwaltung von Flutter eine wichtige Rolle spielt, und alle Ansätze dieses Konzept benutzen müssen, um in das Flutter-Framework integrierbar zu sein. \citeauthor{flutterinaction} beschreibt dabei die Aufgabe der Zustandsverwaltung eher auf einer Ebene des Datenflusses und des Ablaufs der Neu-Erstellung der Benutzeroberfläche.
\citeauthor{managingstateinflutter} sieht die Zustandsverwaltung dabei eher auf einer eher ablaufszentrierten Sichtweise indem er die Aufgabe der Zustandsverwaltung wie folgt analysiert:
\begin{displayquote}[{\cite[Kap.1]{managingstateinflutter}}]
State management is simply a technique, or multiple techniques, used to take care of the changes that occur in your application.
\end{displayquote}
Als Beispiele nennt er dabei, das Reagieren auf Interaktionen mit der Anwendung oder die Beibehaltung des Datenflusses über mehrere Screens hinweg.
Beide Sichtweisen haben gemein, dass die Grundaufgabe der Zustandsverwaltung die Sicherstellung eines korrekten Zustands der Anwendung, einzelner Screens oder einzelner Widgets sein muss, sowie die mögliche Überführung dieses Zustands in einen neuen Zustand als Reaktion auf Veränderungen.
Nachdem nun eingeführt wurde, was unter einer Zustandsverwaltung in Flutter zu verstehen ist, werden nun mögliche bestehende Ansätze für eine Zustandsverwaltung skizziert, um im weiteren Verlauf der Ausarbeitung im Analyse- und Evaluationskapitel diese eingehender zu untersuchen.
\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, \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 \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 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. Zunächst werden einfacheren Konzepten und Werkzeugen vorgestellt, anschließend folgen die komplexeren Konzepte und Werkzeuge vorgestellt.
\subsubsection{setState}
\label{sec:setState}
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
\includegraphics[width=0.98\linewidth]{chapters/basics/setState_tree.png}
\caption{Flutter Widget Tree bei setState}
\label{fig:flutterTreeSetState}
\end{wrapfigure}
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, 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{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';
class UserStore extends InheritedWidget {
const UserStore({
Key? key,
required this.currentUser,
required Widget child,
}) : super(key: key, child: child);
final User currentUser;
static UserStore? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserStore>();
}
@override
bool updateShouldNotify(UserStore old) {
return currentUser != old.currentUser;
}
}
\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 neu gebaut 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 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 neu gebaut werden, wenn der betreffende \text{aspect} sich ändert. \autocite{inheritedModel}
\subsection{Business Logic Components (BLoC)}
\label{sec:bloc}
Das 2018 auf der Entwicklerkonferenz DartConf vorgestellte BLoC-Pattern ist im Vergleich zu den bisher vorgestellten Ansätzen ein Design-Pattern zur Verwaltung von Zuständen und nicht nur ein Werkzeug des Frameworks. Das Ziel von BLoC ist es, die komplette Logik von der Benutzeroberfläche zu trennen. \autocite[17]{Faust} Die Logik und der Zustand wird dabei in den namensgebenden \acf{bloc} verwaltet. Die Komponenten haben die Aufgabe, Zustands-Ereignisse von Widgets zu empfange und Widgets zu aktualisieren, wenn sich der Zustand ändert. Diese Komponenten unterliegen grundlegenden, nicht-verhandelbaren Regeln, welche im Vortrag von \citeauthor{blocTalk} definiert worden sind:
\blockcquote[24:04]{blocTalk}{\begin{enumerate}
\item Inputs and outputs are simple Streams/Sink only
\item Dependencies must be injectable and platform agnostic
\item No platform branching allowed [...]
\end{enumerate}}
Die erste Regel bedeutet, dass 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 BLoC übergeben.
% TODO ggf noch beispiele einsetzen, wie die regeln anzuwenden sind
Die zweite Regel sagt aus, dass BLoC keine Abhängigkeiten zur Benutzeroberfläche haben dürfen. Selbst das Importieren von Flutter-Bibliotheken in diese Dateien ist verboten. Damit wird erreicht, dass BLoC komplett plattformunabhängig sind, und somit die komplette Benutzeroberfläche theoretisch ersetzt werden könnte, ohne die Logik ändern zu müssen.
Die dritte Regel legt fest, dass innerhalb von BLoCs keine Unterscheidungen zwischen Betriebssystemen oder Plattformen vorgenommen werden darf.
Der innere Aufbau der BLoC ist explizit nicht vorgeschrieben, dient aber dazu die über die \texttt{Sink}s eingehenden Daten und Ereignisse zu verarbeiten und anschließend den neuen Zustand über die \texttt{Stream}s zurück an die Widgets zu propagieren. Technisch kommen hier oft Techniken und Werkzeuge aus der reaktiven Programmierung wie \texttt{RxDart} zum Einsatz.
Jede Seite (engl. Screen) sollte dabei exakt einem BLoC zugeordnet sein. Damit die Widgets auf diesen BLoC zugreifen können, müssen diese \texttt{injectable} sein - also zwischen mehreren Widgets geteilt. Dies kann unter anderem mit den bereits in \autoref{chap:included} vorgestellten Ansätzen umgesetzt werden.
\subsection{Provider}
\label{sec:provider}
Provider ist eine Bibliothek, die auf dem \texttt{InheritedWidget}-Ansatz (siehe \autoref{sec:inheritedWidget}) beruht. Dabei vereinfacht die Bibliothek die Benutzung und Erstellung von Klassen, die Zustände verwalten, und kombiniert das \texttt{InheritedWidget}-Konzept unter anderem mit der \texttt{ChangeNotifer}-Klasse. \autocite{stateManagementThesis}
Die abstrakte Klasse \texttt{ChangeNotifier} bietet die Möglichkeit Event-Listener für die erbende Klasse zu registrieren und bei Veränderung über den Aufruf einer entsprechenden Methode diese zu benachrichtigen, wie in \autoref{lst:providerStore} in der Methode \texttt{updateUser} gezeigt wird. \autocite{changeNotifier} Diese Kombinationsmöglichkeit wird auch für andere Konzepte und Klassen angeboten. So lassen sich Provider mit \texttt{Listnables}, also Objekte, die Events emittieren können, \texttt{ValueListanble}, also \texttt{Listnables}, die nur über Änderungen einer Variable benachrichtigen, \texttt{Streams}, welche bereits in \autoref{sec:bloc} vorgestellt worden sind oder \texttt{Futures} kombinieren.
\begin{lstlisting}[caption={Aufbau einer ChangeNotifier-Klasse für Provider}, label={lst:providerStore}]
class UserStore extends ChangeNotifier {
var user;
UserStore(this.user);
void updateUser(newUser) {
this.user = newUser;
notifyListeners();
}
}
\end{lstlisting}
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 {
final user;
const ProvidingWidget({Key? key, this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (context) => UserStore(user), child: ConsumingWidget());
}
}
class ConsumingWidget extends StatelessWidget {
const ConsumingWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final user = Provider.of<UserStore>(context).user;
return Text(user.firstName);
}
}
\end{lstlisting}
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 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 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.
\begin{lstlisting}[caption={Zustandsklasse in Riverpod}, label={lst:StateNotifier}]
class UserStore extends StateNotifier<User> {
UserStore(User initialState) : super(initialState);
changeFirstName(String newName) {
state = User(newName);
}
}
\end{lstlisting}
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 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.
\begin{lstlisting}[caption={ConsumerWidget}, label={lst:ConsumerWidget}]
final userStoreProvider = StateNotifierProvider((ref) => UserStore());
class ConsumingWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userStore = ref.watch(userStoreProvider);
return Text(userStore?.state.firstName);
}
}
\end{lstlisting}
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 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:
\blockcquote[Kap.1.3.2]{redux}{Single source of truth [...]
State is read-only [...]
Changes are made with pure functions [...]}
Das Prinzip der \blockcquote[Kap.1.3.2]{redux}{Single source of truth} wird dadurch umgesetzt, dass es für die komplette Anwendung nur einen zusammengefassten Zustand gibt. Es findet hier also keine Aufteilung in diverse Unterzustände beispielsweise für einzelne Seiten statt.
\begin{figure}[h]
\centering
\includegraphics[width=\textwidth]{chapters/basics/reduxflow.jpg}
\caption{Datenfluss in Redux \autocite[Kap.1.3.3]{redux}}
\label{fig:reduxflow}
\end{figure}
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 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-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}
MobX ist wie Redux ebenfalls ein Ansatz, der ursprünglich aus dem React-Ökosystem stammt. MobX stellt sich dabei die Aufgabe, es zu vereinfachen automatisch diverse Informationen aus dem Anwendungs-Zustand abzuleiten. Um dies zu erreichen, gibt es bei MobX fünf grundlegende Konzepte:
\blockcquote[130]{mobx}{\begin{itemize}
\item Observables [...]
\item Computed Values [...]
\item Reactions [...]
\item Actions [...]
\end{itemize}}
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 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.
Actions werden wie bei Redux hier ebenfalls verwendet, um Änderungen am Zustand - hier also Observables - durchzuführen. Allerdings wird hier als Action eine Funktion verstanden, welche Observables abändert und nicht ein Objekt, welche mithilfe eines Reducers den Zustand mutiert.
Observables, Computed Values und Actions werden dabei oft in Klassen zu einem Store zusammengefasst. Die Instanzen dieser Stores müssen dabei über einen Service Locator ähnlich wie bei Riverpod in die Widgets injiziert werden, damit diese den Store mithilfe von Observern nutzen können.
%\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 \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.
%\subsection{Binder}