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/flutter.tex

103 lines
9.7 KiB

\section{Flutter}
Um die konkrete Problemstellung im Detail verstehen zu können, ist es erforderlich, die grundlegende Plattform näher zu betrachten. Dabei wird zu Beginn erst ein Überblick über das Cross-Plattform-Werkzeug Flutter gegeben und im Anschluss detaillierter auf einzelne Aspekte, die für die Erfassung der Problemstellung von Relevanz sind, eingegangen.
\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 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 mit den bereits etablierten Programmiersprachen wie Java oder JavaScript vergleichen, wie in \autoref{lst:dartExample} zu sehen ist, und ist damit eine objektorientierte, optional statisch typisierte Programmiersprache\autocite{dartTypes}.
\begin{lstlisting}[caption={Aufbau eines einfachen Flutter-Widgets in Dart}, label={lst:dartExample}]
import 'package:flutter/material.dart';
class ExampleWidget extends StatelessWidget {
const ExampleWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Row(children: [Text('Hello World'), Text('123')];
}
}
\end{lstlisting}
Von der Architektur her ist Flutter in einem Schichtentwurf aufgebaut, wie in \autoref{fig:flutterArch} zu sehen ist.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\textwidth]{chapters/basics/archdiagram.png}
\caption{Architekturdiagramm von Flutter \autocite{flutterArch}}
\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 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 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.
Das Konzept des Widgets, welches bereits öfters bisher erwähnt worden ist, wird in dem folgenden Kapitel näher beleuchtet.
\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 zum Ausdruck, dass Flutter das Konzept des Widgets für viele Anwendungsfälle nutzt. So kann ein Widget diverse Aufgaben übernehmen wie beispielsweise das Rendern einer UI-Komponente, Animationen oder das Anordnen von anderen Widgets. Zur Darstellung der Benutzeroberfläche benutzen also Widgets Kompositionen von diversen Widgets. So lassen sich beispielsweise mit einer \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 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.
Ein \texttt{StatelessWidget} hat grundsätzlich keinen veränderbaren Zustand. Dies heißt, dass alle Klassenvariablen unveränderlich sein sollen. In Dart wird dies mit dem Modifikator \texttt{final} gekennzeichnet. Daraus wird impliziert, dass alle Informationen zum Zustand des Widgets aus den im Konstruktor übergebenen Werten stammen müssen. Somit wird das Widget nur neu gebaut, wenn sich Änderungen an den Werten des Konstruktors durch Änderungen in der Hierarchie darüber liegenden Widgets ergeben.
Ein \texttt{StatefulWidget} auf der anderen Hand besteht aus zwei Klassen. Zum einen dem \texttt{StatefulWidget} zum anderen dem \texttt{State} dieses Widgets. Diese Widgets vereinen die Möglichkeiten eines \texttt{StatelessWidget} mit der Möglichkeit, den Zustand des Widgets selbstständig zu verändern. Dies wäre beispielsweise bei einem Zähler relevant, wenn der Zähler erhöht werden soll. Zustandsänderungen werden dabei über die, wie in \autoref{lst:stateful} gezeigte, \texttt{setState} Methode vorgenommen, damit auch die Änderung an das Framework kommuniziert wird. Intern wird dabei das Widget als \texttt{dirty} markiert, welches dazu führt, dass die Build-Methode des States erneut aufgerufen wird durch das Framework. Entscheidend ist dabei, dass der State persistent bleibt, auch wenn sich beispielsweise Konstruktor-Parameter der \texttt{StatefulWidget}-Klasse ändern.
\begin{lstlisting}[caption={StatefulWidget}, label={lst:stateful}]
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Count: $_counter"),
IconButton(icon: Icon(Icons.add),
onPressed: _incrementCounter),
],
);
}
}
\end{lstlisting}
\begin{wrapfigure}{r}{0.33\textwidth}
\centering
\includegraphics[width=0.98\linewidth]{chapters/basics/flutter_tree.png}
\caption{Flutter Widget Tree}
\label{fig:flutterTree}
\end{wrapfigure}
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 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.