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

248 lines
23 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 betrachtet werden, was überhaupt die Zustandsverwaltung umfasst.
\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}
TBD
\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.
\subsubsection{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.
\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, 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 \blockquote[{\cite[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.
\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 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}
\subsection{Business Logic Components (BLoC)}
\label{sec:bloc}
Das 2018 auf der Entwicklerkonferenz DartConf vorgestellte \ac{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 \ac{bloc} ist es, die komplette Logik von der Benutzeroberfläche zu trennen. \autocite[S.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:
\blockquote[{\cite[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 \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.
% TODO ggf noch beispiele einsetzen, wie die regeln anzuwenden sind
Die zweite Regel sagt aus, dass \ac{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 \ac{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 \ac{bloc}s keine Unterscheidungen zwischen Betriebssystemen oder Plattformen vorgenommen werden darf.
Der innere Aufbau der \ac{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 \ac{bloc} zugeordnet sein. Damit die Widgets auf diesen \ac{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}
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.
\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 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.
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.
\subsection{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[S.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 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.
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.
\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{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.
\subsection{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 basiert auf drei grundlegenden Prinzipien:
\blockquote[{\cite[Kap.1.3.2]{redux}}]{Single source of truth [...]
State is read-only [...]
Changes are made with pure functions [...]}
Das Prinzip der \blockquote[{\cite[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 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.
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.
\subsection{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:
\blockquote[{\cite[S.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 da, 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.
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 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.
\subsection{Binder}