Zum Inhalt springen

Flutter vs. React Native für App-Entwicklung

  • von

Handy-Apps spielen eine immer wichtigere Rolle und können beispielsweise eine Möglichkeit darstellen, sich ein passives Einkommen aufzubauen. Die Entwicklung einer nativen App unter Verwendung der offiziellen Werkzeuge und APIs ist jedoch sowohl unter Android als auch unter iOS äußerst mühselig, da die APIs nicht sehr entwicklerfreundlich gestaltet sind. Hinzu kommt der zweifache Aufwand, wenn man die App unter beiden Plattformen anbieten möchte.

Glücklicherweise gibt es mittlerweile einige Frameworks, die diese Probleme angehen, z. B. Nativescript oder Xamarin. Die vielversprechendsten Tools sind jedoch Flutter und React Native, da sie die Konzepte des „Virtual DOM“ und „one-way data-flow“ umsetzen, welche vom React-Webframework populär gemacht wurden und welche das Programmieren komplexer Apps massiv vereinfachen.

Dank „Virtual DOM“ ist es nicht mehr notwendig, für jede Änderung des Applikations-Zustands zu entscheiden, welche grafischen Komponenten genau geupdated werden müssen. Stattdessen berechnet das Framework einen neuen virtuellen Komponenten-Baum, vergleicht ihn mit dem bestehenden und führt die notwendigen Änderungen aus.

Durch den „one-way data flow“ fließen Anwendungsdaten immer von Parent-Komponenten an Child-Komponenten, nicht umgekehrt. Children können Änderungen nur erwirken, indem sie ein Event in der Parent-Komponenten auslösen (z. B. per Callback). Eine direkte Kommunikation von 2 Geschwister-Komponenten ist also nicht vorgesehen.
Diese klare Struktur vereinfacht die Entwicklung komplexer Projekte, denn es ist immer genau eine Komponenten für einen jeweilige State verantwortlich und nur diese Komponente kann auch Änderungen am State durchführen.
Gleichzeitig führt diese Struktur dazu, dass die Child-Komponenten weitestgehend „state-less“ sind, was das Testen und auch Wiederverwenden vereinfacht.

Zusätzlich ist zu betonen, dass beide Frameworks mit Google (Flutter) bzw. Facebook (React Native) jeweils einen Großkonzern im Rücken haben, was auf eine gute Unterstützung für die Zukunft hoffen lässt.

Vergleich beider Frameworks

Der folgende Vergleich basiert primär auf praktischen Erfahrungen in der Nutzung beider Frameworks; er stellt daher keine umfassende Analyse dar, sondern soll die bedeutsamsten Vor- und Nachteile der jeweiligen Techniken erläutern, die im Alltag relevant sind.


Pro Flutter

mitgelieferte Widgets

Flutter liefert eine Menge Widgets von Haus aus mit. Snackbar, Navigator, Dialoge, AppBar, TabBar, etc. sind alle integriert. Die Qualität des Codes ist gleichbleibend hoch, da die Widgets vom Flutter-Team stammen.
Man kann also direkt loslegen und auch kompliziertere UIs schnell zusammenbauen.
Und sollte mal ein mitgeliefertes Widget nicht ganz den Vorstellungen entsprechen, kann man einfach den Code kopieren und es an die Bedürfnisse anpassen.

Bei React Native sind hingegen nur wenige Widgets inklusive. Selbst einfach Dinge, wie ein Checkbox-Widget, sind nicht enthalten. Man kann diese nun entweder selbst entwickeln, oder nach Community-entwickelten Widgets suchen. Hierbei ist dann allerdings mit schwankender Qualität zu rechnen.
Das Entwickeln eigener Widgets ist in React Native aufwändiger als in Flutter, da man in vielen Fällen Teile des Widgets in nativem Code schreiben müssen wird, was also doppelten Code für Android und iOS bedeutet.

kein Bundler

React Native nutzt den sogenannten Bundler, der dafür verantwortlich ist, die einzelnen Javascript-Dateien eines Projektes zusammenzufassen in eine einzige große Javascript-Datei, welche dann aufs Handy übertragen wird.

Dummerweise ist dieser Bundler alles andere als stabil. Immer wieder kommt es zu den seltsamsten Fehlern. Manchmal muss man dann den gesamten „node_modules“ Ordner löschen. Manchmal hilft nur eine langwierige Google-Suche in der Hoffnung, dass jemand schon mal dasselbe Problem hatte und eine Lösung fand.
Ebenso kann es passieren, dass der Bundler sich nach einiger Zeit mit einem „Out of memory“-Fehler verabschiedet. Hier hilft ein einfaches Neustarten des Bundlers, aber nervig ist es trotzdem.

Es sollte jedoch angemerkt werden, dass Bundler-Probleme mit den neueren React Native-Versionen seltener geworden sind. Trotzdem existieren all diese Probleme in Flutter schlichtweg nicht.

Global Keys

Wie auch in React Native gibt es in Flutter Keys, um beispielsweise Listen-Elemente eindeutig zu identifizieren. Eine besondere Variante sind die sogenannten „Global Keys„, welche es einem zusätzlich erlauben, auf den State des entsprechenden Widgets zugreifen (womit sie die Funktion von „refs“ in React einnehmen).

Global Keys bieten jedoch noch einen weiteren Vorteil: Wird ein Widget, welchem ein Global-Key zugewiesen ist, von einer Stelle im Widget-Baum an eine andere verschoben, so wird das Widget nicht un-gemounted und neu-gemounted, sondern es behält seinen gesamten internen State.

Als Beispiel: Zwei Widgets A und B stellen beide einen Navigations-Drawer dar, und weisen ihm den selben Global-Key zu. Wird nun Widget A durch Widget B ersetzt, so bleibt der Drawer exakt erhalten, inklusive seines internen States (z. B. ob er aufgeklappt ist).

Möchte man dies in React Native erreichen, ist man gezwungen, seinen Komponenten-Hierarchie so umzubauen, dass der Drawer in eine den Komponenten A und B übergeordnete Komponente ausgelagert wird, was zu einer weniger eleganten Komponenten-Struktur führen kann.

Debugging

Debugging in Flutter ist denkbar einfach. Man kann z. B. in Android Studio beliebig Breakpoints setzen und alles funktioniert wie erwartet.
Da nahezu das gesamte Flutter Framework, bis auf einige Low-Level Komponenten, in Dart programmiert ist, kannst du auch problemlos in den Framework-Code steppen, um zu sehen, was dort passiert.

Der Debugger in React Native hingegen läuft in einer Chrome-Instanz ab, die sich mit der laufenden App verbindet. Auch hier können Breakpoints gesetzt werden.
Da React Native selbst allerdings nur teilweise in Javascript, aber auch in Java bzw. Objective C geschrieben ist, ist ein Steppen nur in den Javascript-Teil des Framework-Codes möglich.

Hot Reload

Flutter und React Native unterstützen Hot-Reload, womit Änderungen am Code nahezu sofort (typisch < 1 Sekunde) in die laufende App übertragen werden. Hierbei bleibt der Zustand der App erhalten, beispielsweise auf welche Unterseite der App man navigiert hat. Dies kann enorme Zeitersparnis bieten, z. B. beim Entwerfen des Layouts der App, da man hierbei oftmals viele kleine Änderungen ausführt.

Die Implementation in React Native ist tendenziell etwas weniger robust als jene in Flutter. In React Native passiert es oft, dass nach einer Änderung die App in einem fehlerhaften Zustand ist, bei dem Teile des States zurückgesetzt wurden, und man sie daher neu laden muss.

In Flutter bekommt man meist nur dann Probleme, wenn man beispielsweise den Code zur Initialisierung eines Widgets ändert. Hier ist dann auch ein kompletter Reload notwendig, da beim Hot Reload das Widget nicht neu initialisiert wird, und der geänderte Initialisierungs-Code dementsprechend keine Auswirkung hat.

komplexe Animationen

Die gesamte Rendering-Architektur von Flutter ist auf maximale Performance ausgelegt. Da Flutter das gesamte User Interface selbst rendert, entfällt jeglicher Overhead für die Kommunikation mit dem Betriebssystem. Insbesondere bei komplexen Animationen erlaubt dies sehr hohe Frameraten.

In React Native hingegen ist es notwendig, die deklarative Animation API zu benutzen. Hierbei legt man in Javascript quasi die Start- und Endwerte der zu animierenden Variablen fest und die eigentlichen Werte werden dann in Java bzw. Objective-C berechnet. Dies bietet ebenfalls eine gute Performance, aber ist weniger flexibel als Flutter.

Startup Performance

Flutter-Anwendungen starten etwas schneller als React Native-Anwendungen. Der Grund liegt primär darin, dass React Native-Anwendungen beim Starten den gesamten Javascript-Code einlesen und parsen müssen, was einige Zeit kostet.

Flutter hingegen kompiliert den Code in Maschinencode, sodass er sofort ausgeführt werden kann.


Pro React Native

Community

Als React Native Entwickler stehen dir eine Menge Javascript-Bibliotheken zur Verfügung. Außerdem gibt es viele von der Community entwickelte grafische Komponenten.
Ein Anlaufpunkt kann die Liste Awesome React Native sein, die einen mit Community-Projekten geradezu erschlägt.

Flutter hat aktuell noch ein relativ kleines Ökosystem. Auch wenn, wie oben erwähnt, viele Widgets bereits mitgeliefert werden, kann es vorkommen, dass für etwas ausgefallenere Funktionen kein Community-Projekt existiert.
Auch hier gibt es eine Liste mit Community-Projekten: Awesome Flutter

Typescript

Die Programmierung bei React Native erfolgt in Javascript, bei Flutter in Dart. Zusätzlich lässt sich bei React Native die Sprache Typescript benutzen, die Javascript um ein mächtiges Typensystem erweitert.

Zwar ist Dart keine schlechte Sprache, aber Typescript ist in vielen Aspekten besser. So verfügt Typescript über fortschrittliche Features wie nullable types, algebraic data-types, destructuring und viele mehr.

Typescript kann seine Stärken insbesondere dann ausspielen, wenn der Fokus der App nicht auf dem User Interface, sondern auf der eigentlichen Business-Logik liegt, man also mit vielen komplexen Datentypen arbeitet.

Ebenso sind Typescript und Javascript auch für die Backend-Entwicklung geeignet, so dass man leicht Teile des Codes zwischen dem Client und dem Server teilen kann.

Code sharing mit React-Webapps

Da React Native auf dem React-Webframework aufbaut, ist es möglich, Teile des Codes wiederzuverwenden, falls man zusätzlich noch eine Web-Anwendung entwickeln möchte.

Hinzu kommt, dass React Native viele Web-APIs nachbildet, z. B. für Web-Requests. Das bedeutet, dass man den Großteil des Logik-Codes nahezu unverändert aus einer Web-App übernehmen kann.

Bei grafischen Komponenten liegt die Schwierigkeit darin, dass React und React Native plattformbedingt unterschiedliche Komponenten für die Darstellung der UI nutzen.

Um in React eine vertikale Liste darzustellen nutzt man typischerweise ein „<div>“, in React Native ein „<View>“, usw.
Ein direktes Code-Sharing ist hier also nicht immer möglich, aber durch cleveres Refactoring kann man versuchen, alle plattformbezogenen Komponenten zu isolieren, so dass alle darauf aufbauenden Komponenten dann plattform-neutral sind.

In der Flutter-Welt planen die Entwickler mit Hummingbird einen Ansatz, der es erlauben würde, Flutter-Applikationen direkt in Webanwendungen zu kompilieren. Es bleibt abzuwarten, ob dieser Ansatz problemlos funktioniert. Im Idealfall wären dann 100% Code-Sharing möglich.


Unentschieden

JSX vs Classes

React Native nutzt eine eigene Sprache namens JSX für die Deklaration der zu rendernden Komponenten. In Flutter hingegen spezifiziert man den Widget-Baum einfach durch das Verschachteln der entsprechenden Widget-Klassen.

// Flutter
Column(children: [
    Text("Hallo Welt"),
    RaisedButton(child: Text("Click"), onPressed: () => print("Clicked"))
]);
// React Native
<View>
    <Text>Hallo Welt</Text>
    <Button title="Click" onPress={() => console.log("Clicked")} />
</View>

Letzten Endes handelt es sich hier nur um kleine stylistische Unterschiede; in der Praxis sind beide Ansätze nach kurzer Eingewöhnung angenehm zu nutzen.

Styling

Sowohl React Native als auch Flutter verzichten auf einen globalen Styling-Ansatz, wie man ihn aus CSS kennt, und nutzen stattdessen individuelle Styles für jede Komponente.

// Flutter
Text("Hallo Welt", style: TextStyle(fontSize: 24.0))
// React Native
<Text style={{ fontSize: 24 }}>
    Hallo Welt
</Text>

Möchte man Styles mehrfach verwenden, so kann man sie in Flutter einfach in eine Variable auslagern oder in React Native ein StyleSheet Objekt benutzen.

Look & Feel

Flutter rendert das gesamte User Interface selbst und umgeht damit das Betriebssystem. Dadurch sehen Flutter-Apps nicht unbedingt genau so aus wie native Apps: Zwar versucht das Flutter-Team, möglichst detailgetreu die nativen Komponenten nachzubilden, aber garantiert ist das nicht.
Im Gegenzug bedeutet dies aber, dass du volle Freiheit hast und relativ einfach eigene Komponenten, wie z. B. Textfelder, selbst schreiben kannst.

React Native hingegen verwendet die System-Widgets, sodass die App sich perfekt in den Look des Betriebssystems einpasst. Auch das Verhalten ist dementsprechend identisch zu anderen Apps.

Hierbei sei allerdings angemerkt, dass viele Apps heutzutage bewusst auf native Widgets der jeweiligen Plattform verzichten und stattdessen einen einheitlichen Look auf beiden Plattformen (Android & iOS) anstreben.
Es muss also im Einzelfall entschieden werden, ob man wirklich ein „natives UI“ auf der jeweiligen Plattform möchte, oder lieber ein plattformübergreifendes einheitliches UI.

Plattformen

React Native unterstützt offiziell Android, iOS und Apple TV. Zusätzlich gibt es inoffizielle Portierungen für Windows, Qt und macOS.

Flutter unterstützt aktuell Android und iOS. Support für Desktop- und Web-Anwendungen ist in Arbeit.
Desweiteren enthält das neue Google-Betriebssystem namens Fuchsia offizielle Unterstützung für Flutter.

Stabilität

Beide Projekte sind noch recht jung. React Native erschien zum ersten Mal im März 2015, Flutter im Mai 2015 (damals noch unter dem Namen Sky). Dementsprechend sind beide Frameworks natürlich noch nicht 100% stabil.

Man wird also gelegentlich mit kleineren Bugs konfrontiert werden, die sich aber alle mit etwas Mühe lösen lassen. Noch dazu ist davon auszugehen, dass beide Frameworks in nächster Zeit dank steigender Nutzerzahlen immer stabiler werden.

Editor-Support

Prinzipiell lässt sich für beide Plattformen mit jedem beliebigen Editor entwickeln. Allerdings gibt es spezielle Plugins, die die Entwicklung vereinfachen können.

Für Flutter gibt es Plugins für Visual Studio Code und für IntelliJ IDEA bzw. Android Studio (basiert auf IDEA).

Für React Native bietet sich ebenfalls Visual Studio Code an. Dort gibt es auch eine spezielle React Native Extension, die das Debuggen vereinfachen soll (was bei mir unter Windows allerdings nicht funktionierte).

Run-time Performance

Auf den ersten Blick würde man Flutter eine deutlich höhere Performance zutrauen als React Native. Immerhin umgeht Flutter das Betriebssystem und rendert das User Interface selbst. Noch dazu ist Dart als Programmiersprache leichter optimierbar als Javascript (es ist kein Zufall, dass ehemalige V8-Entwickler wie Lars Bak an der Entwicklung von Dart beteiligt waren).

React Native hingegen muss den Umweg gehen und alle Komponenten vom Betriebssystem rendern lassen. Aber da ein großer Teil von React Native in C++/Java geschrieben ist, ist auch das relativ performant.
Hinzu kommst, dass Javascript auch ziemlich schnell ist dank der immensen Ressourcen, die die Webbrowser-Entwickler in ihre Runtimes investiert haben.

Außerdem: Sollte ein bestimmter Codepfad zu langsam sein, kann man (auf beiden Plattformen) notfalls genau diesen Code nativ schreiben und einbinden. In React Native dürfte in Zukunft auch die Verwendung von WebAssembly möglich sein (ist aktuell noch nicht möglich!), was die Einbindung nativen Codes noch vereinfachen würde.

Embedding

Falls bereits eine native Mobile-App vorliegt, kann die Frage aufkommen, ob eine graduelle Migration auf Flutter bzw. React Native möglich ist. Hierzu wäre es erforderlich, Flutter bzw. React Native und bestehende native Views zu kombinieren.

Diese Einbettung ist in zwei Richtungen möglich: Entweder, man bindet existierende native Views in seine neue (React Native- oder Flutter-) App ein, oder aber man bindet in die bestehende native App eine Instanz von Flutter bzw. React Native ein, die dann bestimmte Widgets darstellt.

Sowohl React Native als auch Flutter unterstützen beide Formen der Einbettung. Hier eine Übersicht der einzelnen Ansätze:


Was bringt die Zukunft?

Für die Zukunft sind bei React Native einige Neuerungen zu erwarten:

  • Hooks erlauben einen eleganteren Programmierstil für Components und das bessere Abstrahieren von Funktionalität.
  • Fabric & JSI sind Teil einer Restrukturierung der Grundlagen von React Native. Hierdurch ist eine bessere Performance zu erwarten.
  • Prepack ist ein Tool, welches Javascript-Code partiell evaluieren kann. Im Idealfall ist der resultierende Code dann kleiner und kann schneller geladen werden. Für React Native könnte dies insbesondere schnellere Startzeiten ermöglichen.

Auch bei Flutter sind Verbesserungen in Arbeit: So soll Dart Unterstützung für Nullable-Types bekommen.
Ebenso ist davon auszugehen, dass die Popularität von Flutter in den kommenden Monaten und Jahren stark ansteigen wird und dementsprechend viel mehr Community-entwickelte Widgets verfügbar werden.

Fazit

Beide Frameworks sind äußerst leistungsfähig und bieten eine höhere Produktivität im Vergleich zu nativer Android- oder iOS-Entwicklung. Einen klaren Gewinner festzustellen ist nicht möglich, da unterschiedliche Stärken und Schwächen vorliegen.

Trotzdem hier nochmal zusammenfassend eine kleine Entscheidungshilfe:
Flutter bietet sich insbesondere an, wenn du sehr komplexe Animationen benötigst, oder von den vielen mitgelieferten Widgets Gebrauch machen willst.
React Native lohnt sich dann, wenn du eine native UI möchtest, du kompliziertere Logik hast, die von Typescript profitiert, oder wenn du schon eine React-Webanwendung hast und Code zwischen Web-App und Mobile-App teilen willst.

Ansonsten macht man mit keinem der Frameworks einen großen Fehler, da die Unterschiede in den meisten Fällen nicht überaus dramatisch sind.
Auch ist es kein Problem, beide Frameworks zu erlernen und dann je nach Bedarf das passendere zu nutzen. Da viele der Konzepte so ähnlich sind, ist der Lernaufwand insgesamt überschaubar.