Native statt Cross-Platform: zwei Apps, ein Vertrag
Wer eine App für iPhone und Android plant, bekommt früher oder später die Frage gestellt: Warum nicht einmal schreiben und auf beiden Plattformen ausliefern? Frameworks wie React Native oder Flutter versprechen genau das. Wir haben mit React Native und Expo selbst gearbeitet, unter anderem an einem frühen Prototyp unserer Reisekosten-App ContoTrip. Für unsere eigenen Produkte haben wir uns trotzdem für einen anderen Weg entschieden: Jede Plattform wird nativ gebaut, ohne gemeinsamen Code.
Was „nativ“ bei uns bedeutet
Auf iOS schreiben wir die App in Swift mit SwiftUI, Daten liegen in SwiftData, die Synchronisation läuft über CloudKit. Auf Android ist der Weg Kotlin mit Jetpack Compose. Beide Implementierungen stehen für sich. Es gibt keine gemeinsame Geschäftslogik in einer dritten Sprache, keine Brücke und keine Abstraktionsschicht, die beide Welten gleichmacht.
Das klingt zunächst nach doppelter Arbeit. In der Praxis gewinnen wir dafür drei Dinge:
- Plattformgerechte Bedienung. Navigation, Share-Sheet, Widgets, Kurzbefehle und Systemdialoge verhalten sich so, wie Nutzer es von ihrem Gerät erwarten.
- Direkter Zugang zu neuen Systemfunktionen. Wenn Apple oder Google eine Schnittstelle einführen, warten wir nicht auf ein Update des Frameworks.
- Weniger Abhängigkeiten. Ein natives Projekt hängt an der Toolchain des Plattformherstellers, nicht zusätzlich an einem Drittanbieter und dessen Plugin-Ökosystem.
Offen gesagt: Bei unseren Dual-native-Projekten ist die iOS-Seite in Arbeit, die Android-Seite ist geplant. Gerade deshalb legen wir die Grundlage für beide Plattformen jetzt schon fest.
Der einzige Berührungspunkt: das Austauschformat
Wenn zwei Apps keinen Code teilen, brauchen sie trotzdem eine gemeinsame Sprache. Nutzer schicken sich Einträge über WhatsApp, Mail oder AirDrop, und eine Android-App soll später dieselben Dateien lesen und schreiben können wie die iOS-App. Dieses Austauschformat behandeln wir wie einen Vertrag. Was einmal verschickt wurde, muss auch in einem Jahr noch importierbar sein.
Die Regeln des Vertrags
- Versionierte Hülle. Jede Datei trägt eine Struktur aus Version, Typ und Nutzdaten, etwa
{ "v": 1, "type": "…", "payload": { … } }. So kann ein Import ältere Versionen erkennen und gezielt behandeln. - Datumswerte in ISO 8601. Ohne ausdrückliche Einstellung schreibt Swift Datumswerte als Fließkommazahl seit dem Apple-Referenzdatum 2001. Außerhalb der Apple-Welt ist das unbrauchbar.
- Stabile englische Enum-Werte. Eine Priorität heißt in der Datei
"high", nie „Hoch“ oder „Yüksek“. Übersetzte Beschriftungen gehören in die Oberfläche, nicht in die Daten. - Beträge als ganze Cent. Fließkommazahlen und landesspezifische Formatierung führen zu Rundungsfehlern und Parser-Problemen.
- MIME-Typ neben der UTI. iOS kennt Dateitypen über Uniform Type Identifiers. Fehlt der MIME-Typ in der Deklaration, verschickt iOS die Datei als
application/octet-stream, und Android kann sie nicht zuordnen.
Import am Inhalt erkennen
Mail-Programme und Messenger benennen Anhänge um oder setzen falsche Typen. Deshalb entscheidet unser Import nicht an der Dateiendung, sondern am Inhalt: Er nimmt JSON an und prüft das Typ-Feld der Hülle. Zusätzlich erzeugt die App einen lesbaren Text als Rückfall für Empfänger, die die App nicht installiert haben.
Was bewusst nicht übertragen wird
Datenbank-IDs und gerätelokaler Zustand bleiben auf dem Gerät. Die ID des Absenders bedeutet beim Empfänger nichts. Verweise, etwa auf eine Kategorie, werden beim Import über den Namen zugeordnet. Und ein Import passiert nie still: Der Empfänger sieht eine bearbeitbare Vorschau und bestätigt, bevor etwas gespeichert wird.
Wann Cross-Platform trotzdem passt
Native Entwicklung ist keine Glaubensfrage. Es gibt Projekte, bei denen ein gemeinsamer Code sinnvoller ist:
- ein Prototyp, der schnell auf beiden Plattformen getestet werden soll,
- eine App, die vor allem Formulare und Listen aus einer bestehenden API darstellt,
- ein Team, das bereits tief in React und TypeScript steckt und diese Kenntnisse weiter nutzen will.
In solchen Fällen empfehlen wir React Native mit Expo und sagen auch, wo die Grenzen liegen: Native Module wie ein Dokumentenscanner laufen zum Beispiel nicht in Expo Go, sondern nur in einem eigenen Entwicklungs-Build.
Unser Fazit
Für Produkte, die lange leben, tief ins System greifen und sich auf jeder Plattform selbstverständlich anfühlen sollen, bauen wir nativ. Den Mehraufwand fangen wir mit einer klaren Architektur und einem sauber definierten Austauschformat ab. Dieses Format ist das eigentliche Bindeglied zwischen den Apps. Wer es früh festlegt, erspart sich später die schwierigste Migration von allen: die von Daten, die schon bei den Nutzern liegen.
Sie planen eine App und sind unsicher, welcher Weg zu Ihrem Vorhaben passt? Wir besprechen das gern mit Ihnen, bevor die erste Zeile Code entsteht.