Vergleich React Native vs Native App-Entwicklung

  • 13 Nov 2023
  • Gunnar Matz
  • 25 min

Welcher Software-Entwickler kennt es nicht? Es steht ein neues App-Projekt an, und wieder einmal stellt sich die Frage des zu wählenden Umsetzungskonzepts. In diesem Artikel ignorieren wir vorsätzlich die üblichen Abhängigkeiten (Kundenwunsch, ggf. Strategie der Vorgesetzten, Personalkapazitäten etc.) und fokussieren uns ausschließlich auf die technischen Aspekte beim Vergleich der zwei wesentlichen Programmierkonzepte für die Erstellung von Mobile Apps: Native Programmierung einerseits und hybride Entwicklung am Beispiel von „React Native“ andererseits.

Wir wollen beide Konzepte hinsichtlich verschiedener Parameter wie Programmieraufwand, Performance, Wartung und Pflege, Dokumentation etc. miteinander vergleichen und versuchen, Handlungsanleitungen zu geben, in welchem Kontext welches Konzept am meisten Sinn hat.

Wir werden dies jeweils anhand einer simplen Beispiel-Applikation durchführen. Die App berechnet auf Basis von Nutzereingaben den Benzinverbrauch pro 100 km sowie die Kosten für 100 km. Das Ergebnis des Verbrauchs soll zusätzlich in Form eines Tachometer-Diagramms dargestellt werden, der Code für das Diagramm soll dabei idealerweise aus einer externen Library der jeweiligen Plattform kommen.

Los geht’s!

Native Programmierung

Bekanntermaßen bedeutet native Programmierung, dass die originären Programmiersprachen der jeweiligen Plattformen iOS bzw. Android verwendet werden - und zwar ausschließlich. Auf der iOS-Seite weicht dabei gute alte Objective-C seit einigen Jahren immer mehr Apples Sprache Swift.

Der folgende Screenshot zeigt eine Vorschau unserer App im iOS-Emulator:

Vorschau im iOS-Emulator

Im Folgenden ist der Quellcode zu sehen, welcher die Berechnung in Swift durchführt sowie die Benutzeroberfläche mittels SwiftUI realisiert.

import SwiftUI
import ABGaugeViewKit
struct ContentView: View {
@State private var distance = ""
@State private var fuelAmount = ""
@State private var price = ""
@State var fuelEfficiencyText = "-"
@State var costEfficiencyText = "-"
@State var gaugeNeedleValue = 0.0
var body: some View {
VStack(spacing: 20) {
Label("Benzinpreisrechner", systemImage: "book.fill")
.labelStyle(TitleOnlyLabelStyle())
.font(.system(size: 26.0))
TextField("Gefahrene Distanz [km]", text: $distance).keyboardType(.decimalPad).multilineTextAlignment(.center)
TextField("Getankte Menge [l]", text: $fuelAmount).keyboardType(.decimalPad).multilineTextAlignment(.center)
TextField("Preis/Liter [EUR]", text: $price).keyboardType(.decimalPad).multilineTextAlignment(.center)
Button("Berechnen") {
calculateFuelEfficiency()
}
Text(fuelEfficiencyText).labelStyle(TitleOnlyLabelStyle())
Text(costEfficiencyText).labelStyle(TitleOnlyLabelStyle())
TachometerView(gaugeNeedleValue: $gaugeNeedleValue)
.frame(minWidth: 150, maxWidth: 150, minHeight: 150, maxHeight: 150)
}
.padding()
}
private func calculateFuelEfficiency() {
let numberFormatter = NumberFormatter()
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximumFractionDigits = 2
numberFormatter.decimalSeparator = ","
if let distance = Double(distance),
let gasolineUsed = Double(fuelAmount),
let pricePerLiter = Double(price){
let fuelEfficiency = (gasolineUsed / distance) * 100
if let formattedFuelEfficiency = numberFormatter.string(from: NSNumber(value: fuelEfficiency)) {
fuelEfficiencyText = "Verbrauch: \(formattedFuelEfficiency) l/100km"
gaugeNeedleValue = fuelEfficiency * 6
}
let totalGasolinePrice = gasolineUsed * pricePerLiter
let costForDistance = (totalGasolinePrice / distance) * 100
if let formattedCost = numberFormatter.string(from: NSNumber(value: costForDistance)) {
costEfficiencyText = "Kosten: \(formattedCost) EUR/100km"
}
}
}
}


Sämtliche Code-Beispiele können selbstverständlich auch von unserer Webseite heruntergeladen werden, Download-Links werden am Ende dieses Artikels bereitgestellt.

Bei Android gab es eine vergleichbare Transition vom klassischen Java-Code hin zu Kotlin. Unser Beispiel würde in Kotlin mit Nutzung von Jetpack Compose, ungefähr so aussehen:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FuelEfficiencyCalculator() {
var screenStateCalcuationReady by remember { mutableStateOf(false) }
var distance by remember { mutableStateOf(TextFieldValue()) }
var gasoline by remember { mutableStateOf(TextFieldValue()) }
var price by remember { mutableStateOf(TextFieldValue()) }
var fuelEfficiency by remember { mutableStateOf(0.0) }
var costForDistance by remember { mutableStateOf(0.0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
Text("Benzinpreisrechner",
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Serif
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = distance,
onValueChange = { distance = it },
label = { Text("Gefahrene Distanz [km]:", fontFamily = FontFamily.Serif) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
OutlinedTextField(
value = gasoline,
onValueChange = { gasoline = it },
label = { Text("Getankte Menge [l]:", fontFamily = FontFamily.Serif) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
OutlinedTextField(
value = price,
onValueChange = { price = it },
label = { Text("Preis/Liter [EUR]:", fontFamily = FontFamily.Serif) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
// Calculate the fuel efficiency
val distanceValue = distance.text.toDoubleOrNull()
val gasolineValue = gasoline.text.toDoubleOrNull()
val priceValue = price.text.toDoubleOrNull()
if (distanceValue != null && gasolineValue != null && priceValue != null) {
screenStateCalcuationReady = true
fuelEfficiency = (gasolineValue / distanceValue) * 100
val totalGasolinePrice = gasolineValue * priceValue
costForDistance = (totalGasolinePrice / distanceValue) * 100
}
}
) {
Text("Berechnen")
}
Spacer(modifier = Modifier.height(16.dp))
if (screenStateCalcuationReady) {
Text(
text = "Verbrauch: %.2f l/100km".format(fuelEfficiency),
fontFamily = FontFamily.Serif
)
Text(
text = "Kosten: %.2f EUR/100km".format(costForDistance),
fontFamily = FontFamily.Serif
)
Spacer(modifier = Modifier.height(16.dp))
SpeedMeter(
modifier = Modifier.size(200.dp, 200.dp),
backgroundColor = Color.Black,
progressWidth = 16f,
progress = (fuelEfficiency.toFloat()),
needleColors = listOf(Color.Black,Color.White),
needleKnobColors = listOf(Color.Black,Color.Gray),
needleKnobSize = 10f,
progressColors = listOf(Color.Green, Color.Yellow, Color.Red),
unitText = "l"
)
}
}
}


Der folgende Screnshot zeigt die Anwendung im Android-Emulator:

Vorschau im Android-Emulator

React Native

React Native basiert auf React wurde ab der 2010er Jahre von Facebook entwickelt und auch für deren eigene Facebook-App verwendet. Es verfolgt die Idee des Code-Once-Run-Anywhere, also der Arbeit auf einer Quellcodebasis, aus welcher dann die Applikationen für die jeweiligen Plattformen generiert werden. Die React-UI-Komponenten werden dabei direkt auf die nativen UI-Elemente der jew. Plattform gemappt. React bzw. ReactNative verwendet seit jeher ebenfalls ein deklaratives Konzept für die Programmierung der Nutzeroberflächen. Das Weiteren haben wir das Erweiterungs-Framework Expo verwendet, welches insb. beim Thema Build und Deployment die Arbeit vereinfacht.

Der Quellcode unserer Demo-Applikation sieht folgendermaßen aus:

const App = () => {
const [distance, setDistance] = useState('');
const [fuelAmount, setFuelAmount] = useState('');
const [price, setPrice] = useState('');
const [fuelEfficiency, setFuelEfficiency] = useState(0);
const [costEfficiency, setCostEfficiency] = useState(0);
const calculateFuelEfficiency = () => {
const distanceValue = parseFloat(distance);
const fuelAmountValue = parseFloat(fuelAmount);
const priceValue = parseFloat(price);
if (!isNaN(distanceValue) && !isNaN(fuelAmountValue) && !isNaN(priceValue)) {
const efficiency = (fuelAmountValue / distanceValue) * 100;
setFuelEfficiency(efficiency);
let totalGasolinePrice = fuelAmountValue * priceValue
let costForDistance = (totalGasolinePrice / distanceValue) * 100
setCostEfficiency(costForDistance);
}
};
return (
<View style={styles.container}>
<Text style={styles.headline}>
Benzinpreisrechner
</Text>
<TextInput
style={styles.input}
placeholder="Gefahrene Distanz [km]"
keyboardType="numeric"
value={distance}
onChangeText={setDistance}
/>
<TextInput
style={styles.input}
placeholder="Getankte Menge [l]"
keyboardType="numeric"
value={fuelAmount}
onChangeText={setFuelAmount}
/>
<TextInput
style={styles.input}
placeholder="Preis/Liter [EUR]"
keyboardType="numeric"
value={price}
onChangeText={setPrice}
/>
<Button title="Berechnen" onPress={calculateFuelEfficiency}/>
<Text style={styles.resultText}>
Verbrauch: {Number(fuelEfficiency).toFixed(2)} l/100km
</Text>
<Text>
Kosten: {Number(costEfficiency).toFixed(2)} EUR/100km
</Text>
<RNSpeedometer value={fuelEfficiency} minValue={0} maxValue={15} labels={[
{
name: 'Gut',
labelColor: '#00ff00',
activeBarColor: '#00ff00',
},
{
name: 'Mittel',
labelColor: '#ffA500',
activeBarColor: '#ffA500',
},
{
name: 'Schlecht',
labelColor: '#ff0000',
activeBarColor: '#ff0000',
}
]} size={200}/>
</View>
);
};
view raw App.tsx hosted with ❤ by GitHub


Im folgenden ein Screenshot der Applikation, umgesetzt im React-Native-Ökosystem:

Die App umgesetzt mit React Native

Vergleich der Konzepte

Wir werden nun die beiden Konzepte hinsichtlich verschiedener Eigenschaften untersuchen und einander gegenüberstellen. Es werden die folgenden Parameter untersucht und bewertet:

  • die Lernkurve bzw. der notwendige Einarbeitungsaufwand
  • die vorhandene Dokumentation
  • der eigentliche Aufwand bei der Umsetzung des Projekts
  • die Performance der fertigen Applikation
  • die Komplexität des Deployment-Prozesses
  • Wartung und Pflege der App
  • der Themenkomplex Weiterentwicklung der Frameworks

1.) Einarbeitungsaufwand

Ein nicht von der Hand zu weisender Faktor ist, dass bei React Native lediglich eine Programmiersprache erlernt und verwendet werden muss, um gleichermaßen auf Android und iOS dabei sein zu können, wohingegen die native Entwicklung Kenntnisse in zwei komplett unterschiedlichen Ökosystemen voraussetzt. Im Hinblick auf den Einarbeitungsaufwand beziehungsweise den Bedarf an entsprechend ausgebildeten Programmierern stellt dies einen eindeutigen Vorteil für React Native dar. Zu berücksichtigen ist dabei allerdings, dass bei intensiver Nutzung von React Native früher oder später ggf. auch die Notwendigkeit bestehen wird, in den nativen Source-Code einzugreifen, um, z. B. eigene Komponenten hinzuzufügen oder bestehende anzupassen.

2.) Dokumentation

Hinsichtlich der Dokumentation der untersuchten Programmierkonzepte lassen sich kaum Unterschiede feststellen, sowohl die Dokumentation von Android/Kotlin bzw. iOS/Swift und auch die von React Native / Javascript lassen sich als gut gepflegt, vollständig und verständlich bezeichnen.

3.) Aufwand für die Projektumsetzung

Kommen wir nun zu einem sehr wichtigen Punkt und vergleichen den jeweiligen Umsetzungsaufwand anhand unseres Beispielprojekts. Ein halbwegs erfahrener Programmierer ist sicherlich in der Lage, unser simples iOS-Projekt mit Swift unter Verwendung von SwiftUI und inklusive der Einbindung einer externen Library innerhalb von 2-3 Stunden fertig zu stellen. Ganz ähnlich sieht es bei Android aus: Die Programmierung von Kotlin inklusive Programmierung der deklarativen UI mittels Compose und der Einbindung einer externen Bibliothek ist in ca. 3 Stunden zu schaffen. Positiv zu erwähnen bleibt hier, dass mit Android Studio eine etwas umfangreichere und an Features reichere IDE zur Verfügung steht als bei Apple mit Xcode. In beiden nativen Programmiersystemen stehen mit Swift UI auf der einen und Android Studio bzw. Compose auf der anderen Seite sehr mächtige und ausgereifte Tools zur Verfügung, um Oberflächen zu erstellen.

Bei React bzw. React Native kann prinzipiell jede IDE verwendet werden, mit der Javascript-Entwicklung möglich ist. Die Fertigstellung des Projekts inkl. Konfiguration des Erweiterungs-Framework Expo dauert ebenfalls ca. 3 Stunden.

Insofern lässt sich festhalten, dass der Programmieraufwand bei der nativen Programmierung nahezu doppelt so hoch ist wie bei React Native, um eine simple App zu erstellen, die auf beiden Plattformen lauffähig ist.

Bei React Native können wir des Weiteren als Vorteil den „Fast Refresh“ erwähnen: Änderungen am Quellcode sind in der Regel unmittelbar in der Web-Vorschau zu sehen und beschleunigen so den Implementierungs-prozess, wohingegen bei iOS und Android nur teilweise UI-Vorschauen möglich sind und üblicherweise jeweils ein entsprechender neuer Bild angestoßen werden muss, welcher bekanntermaßen je nach Projektgröße durchaus einige Zeit dauern kann.

Bei unserer Untersuchung zum Thema Programmieraufwand hat also ganz klar React Native die Nase vorn.

4.) Performance:

Bei unserer simplen Test-Applikation waren auf echten Geräten keinerlei Unterschiede in Hinblick auf die Geschwindigkeit beim Rendering der Seite oder bei Reaktionen auf Nutzerinteraktionen festzustellen. Lediglich beim initialen Starten der zuvor geschlossenen App schneiden die nativen Frameworks (etwas) besser ab ab.

Allgemein betrachtet besteht die Erwartung, dass React Native schlechter abschneidet beim Thema Performance, zumindest sofern komplexe UI-Animationen verwendet werden oder CPU-intensive Aufgaben durchgeführt werden sollen. Dies liegt daran, dass die nativen Apps direkteren Zugriff haben auf die plattform-spezifischen APIs.

Um dies ein wenig näher zu untersuchen, haben wir unsere App dahingehend erweitert, dass nun statt nur einem Chart ganze 100 Chart-UI-Komponenten in einem ScrollView untereinander gerendert werden. Folgender Screenshot verdeutlicht den Aufbau der App in diesem Szenario:

Die App erweitert auf 100 Chart-Komponenten

Interessanterweise lassen sich auch bei diesem Szenario sowohl auf dem iOS-Simulator/Android-Emulator als auch auf den echten Geräten nur sehr geringe Unterschiede zu den nativen Apps bezüglich der Performance ausmachen. Sowohl beim zügigen Scrollen durch die 100 Ui-Komponenten als auch beim Setzen eines Zeigerwertes reagieren die mit React Native erzeugten Apps nur unwesentlich langsamer als die nativ programmierten Apps. Selbstverständlich könnte man ein noch komplexeres Beispiel aufsetzen, um das Performance-Gefälle deutlicher darzustellen, aber dies würde den Rahmen dieses Artikels springen.

5.) Deployment:

Der Deploymentprozess bei Apple wurde in den vergangenen Jahre deutlich vereinfacht, so dass üblicherweise direkt aus Xcode heraus die fertige Application hochgeladen werden kann. Bei der Erstellung des Store-Eintrags fällt dann der übliche Aufwand an für das Hochladen der Screenshots, Einstellen der Texte usw. Bei Android sieht es ganz ähnlich aus. Üblicherweise erstellt man aus dem Android Studio heraus eine Datei, welche dann in der Play Store Developer Konsole hochgeladen werden kann. Dort können dann auch alle weiteren Einträge für den Store hinterlegt werden. Normalerweise sind beide Apps nach einer Prüfung dann im jeweiligen App Store verfügbar.

Das Deployment ist eine Schwäche von React Native, da letztlich die erzeugten Applikationen eweils über die individuellen Deployment-Wege der beiden nativen Plattformen hochgeladen werden müssen und somit doch wieder plattformspezifisches Knowhow notwendig wird. Dieses Problem wird teilweise durch die Verwendung von Expo abgemildert, da hier z. b. Builds auf einfachere Art und Weise durchgeführt werden können.

Letztlich gibt es hier keinen eindeutigen Sieger.

6.) Wartung und Pflege

Bei diesem Punkt geht es in der Regel um das Aktualisieren einer bereits im Store befindlichen App, also um das Deployen eines Updates zu einem bestehenden Projekt. Bei iOS und auch bei Android erfolgt dies nahezu über den identischen Weg wie der initiale Upload, d.h. es wird auch wieder eine Prüfung nötig, welche unter Umständen einige Tage dauern kann (und somit im Falle eines kritischen Bugfixes zu ernsthaften ungewollten Verzögerungen führen kann). Bei React Native hingegen ist es möglich, mittels Erweiterungsframeworks den „Over the Air“-Mechanismus zu verwenden und auf diese Weise den Nutzern ein Update unmittelbar zur Verfügung zu stellen und somit das zeitraubende Review zu umgehen. Dies ist ohne Zweifel ein großer Vorteil bei der Programmierung mit React Native ggü. der nativen Entwicklung.

7.) Aktualisierungen der Entwicklungsplattformen

Nicht verschwiegen werden soll der Nachteil, dass React Native (und analog dazu auch auch jedes andere System, das mehrere Plattformen auf einmal ansprecht) immer das Problem hat, bei Updates des iOS- oder Android-Betriebssystems „nachziehen“ zu müssen und das eigene Framework entsprechend anzupassen. So entsteht zumindest eine greringe Verzögerung, bis neueste Features aus den zugrunde liegenden nativen Frameworks verwendet werden können. Des Weiteren ist eine in Entwicklerkreisen bekannte Tatsache, dass jede zusätzlich verwendete Technologie weitere Komplexität und somit auch eine erhöhte Fehleranfälligkeit mit sich bringt. Der Punkt in dieser Kategorie geht somit an die native Programmierung.

Fazit

Folgende Übersicht fasst sämtliche Ergebnisse der Untersuchungen des letzten Abschnitts zusammen, dabei vergeben wir pro Plattform für jeden untersuchten Aspekt zwischen einem und drei Punkte:

 

iOS Swift     Android Kotlin    React Native   
Einarbeitungsaufwand + + ++
Dokumentation +++ +++ +++
Projektumsetzungsaufwand + + +++
Performance +++ +++ ++
Deployment ++ +++ ++
Wartung + + +++
Aktualisierung der Frameworks     +++ +++ +
Summe 14 15 16

  

Es lässt sich also (leider) kein eindeutiger Sieger feststellen. Stattdessen erhalten wir eindeutiges Sowohl-Als-Auch! Fazit: Je nach Anwendungsfall ist also manchmal die native Programmierung besser geeignet und manchmal React Native.

Selbstverständlich kann es den Sonderfall geben, dass gezielt ausschließlich nur für eine der beiden großen Plattformen entwickelt werden soll, dann würde man in der Regel zur nativen Programmiersprache der jeweiligen Plattform greifen. Dies ist aber erfahrungsgemäß nicht der Normalfall - üblicherweise soll natürlich für beide Plattformen entwickelt werden.

React Native hat ganz eindeutige Vorteile beim Aufwand der Projekt-umsetzung und bei der Aktualisierung durch das Over-the-Air-Prinzip. Die nativen Programmiersprachen haben hingegen bei der Aktualität der zur Verfügung stehenden Features sowie auch in einigen komplexen Szenarien beim Thema Performance Vorteile.
Als Faustregel lässt sich festhalten: Sofern Performance und die Verwendung von der allerneusten Plattform-Features keine kritische Rolle spielt, schlägt das Pendel tendenziell eher in Richtung React Native aus. Dennoch sollte man von Projekt zu Projekt neu entscheiden und die Vor- und Nachteile sehr genau abwägen.

Wir hoffen, wir konnten mit diesem Artikel ein wenig dabei helfen, die Technologien für Ihr nächstes Projekt einzuschätzen. Sollten Sie derzeit ein Projekt planen und sich gerade in der Entscheidungsfindung befinden, so zögern Sie bitte nicht, uns unter info@youniverse.com zu kontaktieren. Wir helfen Ihnen gern!

Materialien

Download sämtlicher Projektdateien dieses Artikels (iOS-App, Android-App und React-Native-App, jeweils als One-Chart- sowie also MultiChart-Version).