03 — Mobile Entwicklung

iOS, Android, plattformübergreifend und die Backends dahinter

Mobile Apps, die an echte Nutzer ausgeliefert wurden — und ausgeliefert blieben — über mehr als elf Jahre.

Natives iOS und Android in ihren eigenen Sprachen, React Native und Flutter, wo sie passen, und die Backends, Caches und Clouds, die ein Telefon mehr als eine dünne Hülle machen. Die elfjährige Bahn ist es, die den Rest glaubwürdig macht.

Das Fundament

Ich habe mehr als elf Jahre damit verbracht, mobile Anwendungen für echte Nutzer zu bauen und auszuliefern — und diese eine Tatsache ist es, die jede andere Behauptung auf dieser Seite lesenswert macht.

Jeder kann Frameworks aufzählen. Der Nachweis, der zählt, ist Langlebigkeit: Apps, die echte Menschen über mehr als ein Jahrzehnt auf iOS, Android und Windows installiert, behalten und genutzt haben. Die elfjährige Bahn ist keine Zeile in einem Lebenslauf — sie ist der Beweis, dass die untenstehenden Entscheidungen unter dem Druck von Software getroffen wurden, die nach dem Start weiterfunktionieren musste.

Ich arbeite nativ zuerst. Auf iOS bedeutet das Swift und Objective-C mit SwiftUI, Core Data, Core Animation und SiriKit; auf Android bedeutet es Kotlin und Java gegen das Android SDK über viele OS-Versionen. Genau diese native Geläufigkeit macht die plattformübergreifende Arbeit — React Native und Flutter auf fortgeschrittenem Niveau, Xamarin auf Arbeitsniveau — zu einer bewussten Wahl statt zu einem Weg, eine Plattform zu vermeiden.

Und eine mobile App ist nie nur der Client. Hinter ihr sitzen Backends in Node, Spring Boot, Django, Flask, Rails und Go, die REST und GraphQL bereitstellen, gestützt von SQLite auf dem Gerät und Redis auf dem Server, ausgeliefert auf Firebase, AWS und Google Cloud. Die folgende Seite ist diese gesamte Oberfläche, erzählt so, wie ich sie tatsächlich baue.

0+

Jahre Auslieferung mobiler Apps an echte Nutzer, auf iOS, Android und Windows

0

native Plattformen, in ihren eigenen Sprachen gepflegt — Swift/Objective-C und Kotlin/Java

0

plattformübergreifende Stacks, die ich auf fortgeschrittenem Niveau betreibe — React Native und Flutter

0

Backend-Frameworks, die ich mit mobilen Clients paare, von Node bis Go

Plattformmatrix

Zwei native Plattformen, zwei plattformübergreifende Stacks, eine Windows-Oberfläche.

Die erste Entscheidung bei jedem mobilen Projekt ist, worauf man baut, und es ist selten zweimal dieselbe Antwort. Die Matrix unten ist, wie ich darüber nachdenke: natives iOS und Android jeweils in ihrer eigenen Sprache, zwei plattformübergreifende Stacks, die ich auf fortgeschrittenem Niveau betreibe, und eine Windows-Oberfläche, die die gemeinsame Logik ehrlich hält. Das Diagramm bildet die Sprachen und Frameworks auf die Plattformen ab, statt vorzugeben, dass ein Werkzeug alles abdeckt.

SPRACHE ZIEL Swift / ObjC Kotlin / Java React Native Flutter iOS Android Windows durchgezogen = native Geläufigkeit · dünn = plattformübergreifende Reichweite

Sprachen und Frameworks, auf Plattformen abgebildet

Eine Matrix, kein Monolith — jede Plattform in der Sprache, die sie belohnt.

iOS bekommt Swift und Objective-C; Android bekommt Kotlin und Java; Windows bekommt seinen eigenen Client. React Native und Flutter überspannen die Stores, wenn die UI nicht gegen die Plattform kämpft, und die Spalte rechts zeigt, wo jedes Werkzeug tatsächlich landet.

Die Matrix zu lesen ist die Arbeit: So entscheide ich, ob eine Funktion nativ, plattformübergreifend oder ein dünner Client über einem gemeinsamen Backend ist, bevor eine Zeile UI geschrieben ist.

  • iOS — Swift, Objective-C, SwiftUI, Core Data
  • Android — Kotlin, Java, Android SDK
  • Plattformübergreifend — React Native, Flutter (fortgeschritten)
  • Windows — nativer Desktop-Client
iOSAndroidReact NativeFlutterWindows
Pro Plattform

Was ich auf jeder Plattform tatsächlich schreibe.

Die Plattformen sind nicht austauschbar, und das Werkzeug spiegelt das wider. Jeder Tab ist eine Plattform, auf der ich ausgeliefert habe — die Sprachen, die Frameworks und das Urteil darüber, wann sich jede ihren Platz verdient.

Natives iOS in Swift, mit Objective-C dort, wo es noch lebt

Auf iOS schreibe ich in Swift und lese Objective-C, denn echte Codebasen, die seit Jahren ausgeliefert werden, sind selten reines Swift. Ich baue Oberflächen in SwiftUI für neue Bildschirme und behalte UIKit dort, wo ein bestehender Fluss davon abhängt, statt funktionierenden Code um eines Frameworks willen neu zu schreiben.

Persistenz ist Core Data, wenn die App einen echten Objektgraphen besitzt, und Bewegung ist Core Animation, wenn ein Übergang sich nativ anfühlen muss statt angenähert. SiriKit behandelt Sprach-Intents dort, wo sie sich ihren Platz verdienen — nicht auf jedem Bildschirm, nur dort, wo ein gesprochener Shortcut echt schneller ist als Tippen.

  • Swift zuerst, Objective-C gelesen und gepflegt
  • SwiftUI für neue Bildschirme, UIKit behalten, wo es funktioniert
  • Core Data für den Objektgraphen, Core Animation für die Bewegung
  • SiriKit-Intents dort, wo Sprache tatsächlich schneller ist
iOS, in der Tiefe

Swift, wie es sich auf einem echten Bildschirm liest.

Ein Code-Panel ist ehrlicher als eine Funktionsliste — das ist die Gestalt der iOS-Arbeit, keine Beschreibung davon.

Auf iOS stütze ich mich auf SwiftUI für neue Oberflächen und Core Data für den Objektgraphen dahinter. Der Ausschnitt unten ist von der Art, die ich täglich schreibe: ein kleines, typisiertes Modell, ein Fetch, den die View beobachten kann, und ein Layout, das deklarativ bleibt. Es ist bewusst gewöhnlich — der Sinn nativer Geläufigkeit ist, dass der Routine-Code sauber ist, nicht clever.

ContactList.swiftSwift · SwiftUI · Core Data
import SwiftUI
import CoreData

struct ContactList: View {
    @FetchRequest(
        sortDescriptors: [SortDescriptor(\.name)]
    ) private var contacts: FetchedResults<Contact>

    var body: some View {
        List(contacts) { contact in
            VStack(alignment: .leading) {
                Text(contact.name)
                    .font(.headline)
                Text(contact.phone)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }
        }
        .animation(.easeInOut, value: contacts.count)
    }
}
SwiftUI · UIKit Core Anim. SiriKit Core Data Swift · Objective-C

Das iOS-Framework-Set

SwiftUI obenauf, Core Data darunter, SiriKit dort, wo Sprache schneller ist.

Die Frameworks setzen sich zusammen: SwiftUI rendert, Core Data persistiert, Core Animation behandelt die Bewegung, die SwiftUI nicht behandelt, und SiriKit gibt die ein oder zwei Intents frei, bei denen ein gesprochener Shortcut das Tippen echt schlägt. Objective-C bleibt im Bild, weil langlebige Apps es noch enthalten, und es zu lesen ist Teil ihrer Pflege.

Nichts davon wird um seiner selbst willen hinzugefügt. Jedes Framework ist in der App, weil ein bestimmtes Problem — Persistenz, Bewegung, Sprache — danach verlangte.

  • SwiftUI für neue Bildschirme, UIKit behalten, wo es funktioniert
  • Core Data für den Objektgraphen
  • Core Animation für nativ wirkende Bewegung
  • SiriKit-Intents nur dort, wo Sprache schneller ist
SwiftSwiftUICore DataSiriKit
Android, in der Tiefe

Kotlin gegen das Android SDK, samt Fragmentierung.

Android belohnt Explizitheit. Der Ausschnitt unten ist ein Kotlin-ViewModel, das den UI-Zustand als Flow freigibt — lebenszyklusbewusst, übersteht Konfigurationsänderungen und erledigt sein Laden außerhalb des Hauptthreads. Da ich über viele OS-Versionen ausgeliefert habe, schreibe ich Android-Code, der Prozesstod und strenge Hintergrundgrenzen annimmt, statt zu hoffen, sie zu vermeiden.

ContactViewModel.ktKotlin · Android SDK
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

class ContactViewModel(
    private val repo: ContactRepository
) : ViewModel() {
    private val _state =
        MutableStateFlow<UiState>(UiState.Loading)
    val state: StateFlow<UiState> = _state.asStateFlow()

    init {
        viewModelScope.launch {
            runCatching { repo.loadContacts() }
                .onSuccess { _state.value = UiState.Ready(it) }
                .onFailure { _state.value = UiState.Error }
        }
    }
}
UNTERSTÜTZTE OS-VERSIONEN min Spitze neueste

Viele OS-Versionen, echte Geräte

Die Telefone unterstützen, die Nutzer tragen, nicht das von der Keynote.

Android-Fragmentierung ist kein Problem, über das man sich beschwert — sie ist die Umgebung. Die Arbeit ist, ein ehrliches Mindest-SDK zu wählen, die Spanne von Versionen und Bildschirmgrößen zu testen, die die tatsächliche Nutzerbasis verwendet, und die Hintergrund- und Berechtigungsregeln zu behandeln, die sich mit jeder Version verschärfen.

Java bleibt im Werkzeugkasten neben Kotlin aus demselben Grund wie Objective-C auf iOS: Ausgelieferter Code lebt lange, und ihn zu pflegen bedeutet, ihn fließend zu lesen.

  • Kotlin und Java gegen das Android SDK
  • Ehrliche Mindest-SDK-Entscheidungen
  • Über die Versionen getestet, die Nutzer verwenden
  • Explizite Behandlung von Hintergrund und Berechtigungen
KotlinJavaAndroid SDKLifecycle
App-Architektur

Die Gestalt einer App, von der UI bis zum Datenspeicher gezeichnet.

Eine mobile App ist ein Stapel klarer Schichten — und die Disziplin ist, sie klar zu halten, damit jede für sich getestet und ersetzt werden kann.

Welche Plattform auch immer, die Architektur, zu der ich greife, ist im Geiste dieselbe: eine UI-Schicht, die nur Zustand rendert, eine Domänenschicht, die die Logik hält, eine Datenschicht, die mit dem Netz und dem Gerätespeicher spricht, und eine Sync-Grenze dazwischen. Das Diagramm zeigt die Schichten und die einzige Richtung, in die die Abhängigkeiten zeigen.

Die Pfeile in eine Richtung zeigend zu halten ist es, was die App ein Jahr später wartbar macht — die UI greift nicht ins Netz, und das Netz weiß nichts von der UI. Dieselbe Gestalt hält, ob der Client Swift, Kotlin, React Native oder Flutter ist.

UI-SCHICHT SwiftUI · Compose · RN · Flutter DOMÄNENSCHICHT Logik · Regeln · Zustand DATENSCHICHT Repositories · ORMs SQLite auf dem Gerät NETZ REST · GraphQL

UI → Domäne → Daten → Speicher

Vier Schichten, Abhängigkeiten in eine Richtung zeigend.

Die UI beobachtet den Zustand und emittiert Intents; die Domänenschicht entscheidet; die Datenschicht holt und persistiert; SQLite hält die Wahrheit auf dem Gerät und das Netz trägt den Rest. Jede Grenze ist ein Ort, an dem ich einen Test setzen oder eine Implementierung austauschen kann, ohne dass die Schichten darüber es bemerken.

Es ist keine Architektur um ihrer selbst willen. Es ist die Struktur, die elf Jahren von Apps erlaubte, weiter Funktionen aufzunehmen, ohne unter ihnen zusammenzubrechen.

  • Die UI rendert Zustand, greift nie ins Netz
  • Die Domänenschicht hält die Logik und die Regeln
  • Die Datenschicht besitzt Netz und Gerätepersistenz
  • SQLite als Quelle der Wahrheit auf dem Gerät
ArchitekturSchichtungTestbar
Backends

Die Serverseite jeder mobilen App.

Ein mobiler Client ist nur so gut wie das Backend, mit dem er spricht — also baue ich dieses Backend auch, in welchem Framework auch immer zum Team passt.

Hinter den Apps habe ich Node/Express, Spring Boot, Django, Flask, Ruby on Rails und Go mit Gin und Gorm ausgeliefert. Das Framework wird für das Team und das Latenzbudget gewählt, nicht aus Gewohnheit: Node, wo das Team JavaScript-geläufig ist, Spring, wo es ein JVM-Haus ist, Django oder Flask für Python-lastige Backends, Rails für konventionsgetriebenes CRUD, und Go, wo das Budget pro Anfrage knapp ist.

Jedes gibt einen Vertrag frei — REST oder GraphQL —, gegen den der mobile Client gebaut wird, persistiert über ORMs wie Sequelize und Mongoose und cached den heißen Pfad in Redis. Der untenstehende Prozess ist derselbe, egal welches Framework in der Mitte sitzt.

Vom Vertrag zu einem synchronisierten Gerät

  1. 01 Den Vertrag definieren REST- oder GraphQL-Schema zuerst vereinbart — Client und Server werden gegen einen Vertrag gebaut, nicht gegeneinander.
  2. 02 Das Framework wählen Node/Express, Spring Boot, Django/Flask, Rails oder Go (Gin/Gorm) — gewählt, um zum Team und zum Latenzbudget zu passen.
  3. 03 Die Daten modellieren ORMs — Sequelize oder Mongoose — über dem relationalen oder dokumentenbasierten Speicher, den die Domäne tatsächlich braucht.
  4. 04 Den heißen Pfad cachen Redis vor den teuren Lesezugriffen, mit expliziter Invalidierung statt hoffnungsvoller TTLs.
  5. 05 Das Gerät synchronisieren SQLite auf dem Gerät, ein Sync-Protokoll über den Vertrag, Konfliktlösung definiert, bevor sie gebraucht wird.
  6. 06 Die Pipeline ausliefern Signierte Builds, automatisierte Tests, Store-Auslieferung — die CI, die einen Commit in einen Build verwandelt, den ein Nutzer installieren kann.
handler.goGo · Gin · Gorm
func GetContact(c *gin.Context) {
    id := c.Param("id")
    if cached, err := rdb.Get(ctx, id).Result();
        err == nil {
        c.Data(200, "application/json",
            []byte(cached))
        return
    }
    var contact Contact
    if err := db.First(&contact, id).Error;
        err != nil {
        c.JSON(404, gin.H{"error": "not found"})
        return
    }
    c.JSON(200, contact)
}

Ein Go-Handler, wie er ausgeliefert wird

REST und GraphQL, schnell und typisiert bedient.

Der Ausschnitt ist ein Gin-Handler, der über Gorm liest, mit einem Redis-Cache davor — die kleine, wiederholbare Gestalt eines Endpunkts, auf den sich ein mobiler Client verlassen kann: eine typisierte Antwort, ein vorhersehbarer Fehler und ein Cache, der die Datenbank auf dem heißen Pfad entlastet.

Ob das Framework Go, Node oder Spring ist, der Vertrag, den das Telefon sieht, ist derselbe. Das ist der Sinn, ihn zuerst zu vereinbaren.

  • Go mit Gin und Gorm auf den Pfaden mit knappem Budget
  • Redis cached die teuren Lesezugriffe
  • Typisierte Antworten, vorhersehbare Fehlerformen
  • Derselbe Vertrag, egal welches Framework
GoGinGormRedisREST

Der vollständige mobile Stack — vom Client zur Cloud

iOS-Sprachen
Swift · Objective-C
iOS-Frameworks
SwiftUI · UIKit · Core Data · Core Animation · SiriKit
Android-Sprachen
Kotlin · Java
Android-Ziel
Android SDK, viele OS-Versionen
Plattformübergreifend
React Native · Flutter (fortgeschritten) · Xamarin (Arbeitsniveau)
Desktop
Windows-Clients
Backends
Node/Express · Spring Boot · Django/Flask · Rails · Go (Gin/Gorm)
APIs
REST · GraphQL
Auf dem Gerät / Daten
SQLite · Redis · Sequelize · Mongoose
Mobile Cloud
Firebase · AWS · Google Cloud
Frontend-Handwerk
HTML5 · CSS3 · SASS/SCSS · JS ES6+ · TypeScript
Offline-first

Wenn das Netz versagt — und auf einem Telefon wird es das.

Ein Telefon bewegt sich durch Tunnel, Aufzüge und Funklöcher. Eine App, die Konnektivität annimmt, ist eine App, die vor einem echten Nutzer an einem Spinner hängt.

Ich baue mobile Apps offline-first: Das Gerät schreibt sofort in SQLite und reiht die Änderung für Sync ein, sodass der Nutzer nie auf das Netz wartet, um seine eigene Aktion Wirkung zeigen zu sehen. Wenn die Konnektivität zurückkehrt, leert sich die Warteschlange über den REST- oder GraphQL-Vertrag, und Konflikte werden durch eine pro Entität entschiedene Richtlinie gelöst, bevor sie je gebraucht wurde.

Das Diagramm zeichnet diese Schleife nach — lokales Schreiben, Warteschlange, Sync, Abgleich — und der Fehlerfall ist der Teil, der zählt: Die App degradiert auf Nur-Lesen, statt still einen Schreibvorgang zu verlieren. Das ist der Unterschied zwischen einer App, der Nutzer vertrauen, und einer, die sie aufgeben.

GERÄT SQLite-Schreiben WARTESCHLANGE ausstehend SERVER abgleichen SPEICHER Redis · ORM offline Sync (REST/GraphQL) aufgelöster Zustand zurück

Lokales Schreiben → Warteschlange → Sync → Abgleich

Das Gerät ist die Quelle der Wahrheit, bis der Server zustimmt.

Jeder Schreibvorgang landet zuerst in SQLite und wird in eine Sync-Warteschlange gespiegelt. Ein Hintergrund-Sync leert die Warteschlange gegen den Server, der Server gleicht mit Redis-gestützten Lesezugriffen und dem ORM-gemappten Speicher ab, und der aufgelöste Zustand fließt zum Gerät zurück. Der Nutzer sieht sofortiges lokales Feedback und schließliche Konsistenz, keinen Spinner.

Die Konfliktrichtlinie im Voraus zu entwerfen — Last-Write-Wins, Merge oder manuell — ist es, was die Schleife ehrlich hält, wenn zwei Geräte denselben Datensatz bearbeiten.

  • Lokales Schreiben in SQLite vor jedem Netzaufruf
  • Änderung eingereiht, geleert, wenn die Konnektivität zurückkehrt
  • Konfliktrichtlinie pro Entität im Voraus definiert
  • Auf Nur-Lesen degradieren, nie einen Schreibvorgang verlieren
Offline-firstSQLiteSyncKonfliktlösung

Offline-first — der Vertrag mit dem Netz

Speicher auf dem Gerät
SQLite — die Quelle der Wahrheit, solange offline
Schreibpfad
Lokales Schreiben zuerst, für Sync eingereiht
Sync-Transport
REST oder GraphQL über den vereinbarten Vertrag
Konfliktrichtlinie
Pro Entität definiert, bevor sie gebraucht wird
Server-Cache
Redis vor den teuren Lesezugriffen
Server-Speicher
Relational oder dokumentenbasiert, via Sequelize oder Mongoose
Fehlerfall
Auf Nur-Lesen degradieren, nie still einen Schreibvorgang verlieren

Der Nutzer sollte nie auf das Netz warten, um seine eigene Aktion zu sehen. Lokal schreiben, im Hintergrund synchronisieren und nie einen Schreibvorgang verlieren — das ist die ganze Offline-first-Disziplin.

Mobile Cloud

Firebase, AWS und Google Cloud — nach Eignung gewählt.

Die Cloud ist nicht eine einzige Entscheidung. Ein kleines Team braucht ein Backend für gestern; ein skalierendes Produkt braucht Kontrolle über seine Rechenleistung. Jeder Tab ist eine Plattform, auf der ich mobile Backends deploye, und das Urteil darüber, wann jede die richtige Antwort ist.

Firebase, wenn ein kleines Team ein Backend für gestern braucht

Firebase verdient seinen Platz, wenn ein mobiles Produkt Authentifizierung, einen synchronisierten Datenspeicher und Push braucht, ohne zuerst Infrastruktur aufzustellen. Ich nutze Auth für die Anmeldung, Firestore für die strukturierten, abfragbaren Daten und die Realtime Database dort, wo das Zugriffsmuster echt ein lebender Baum statt Dokumenten ist.

Die Disziplin ist zu wissen, wo Firebase aufhört, die richtige Antwort zu sein — Sicherheitsregeln, die echte Autorisierung kodieren müssen, und Lesekosten, die mit einem naiven Datenmodell wachsen. Ich entwerfe die Dokumente rund um die Abfragen, nicht umgekehrt.

  • Auth für die Anmeldung, Firestore für strukturierte Daten
  • Realtime Database, wo ein lebender Baum zum Zugriffsmuster passt
  • Sicherheitsregeln als echte Autorisierung behandelt
  • Dokumente rund um die Abfragen entworfen
MOBIL FIREBASE Auth · Firestore AWS Lambda · EC2 · S3 GCP App Eng · Compute ein Client, das Backend, das zum Workload passt

Wo das Backend tatsächlich läuft

Verwaltet, wo es Arbeit spart, kontrolliert, wo es sein muss.

Firebase Auth und Firestore stellen ein synchronisiertes Backend ohne Infrastruktur auf; Lambda und EC2 geben serverlose und langlebige Rechenleistung auf AWS; App Engine und Compute Engine tun das Äquivalent auf Google Cloud; S3 hält die Medien, die ein Telefon herunterlädt. Das Diagramm bildet den Client auf diese Optionen ab.

Die mobile Randbedingung zieht sich durch alles: Kaltstarts spürt man auf einem Telefon, und Uploads müssen eine Mobilfunkverbindung überstehen. Die Cloud wird so gewählt, dass die Latenz, die der Nutzer sieht, im Budget bleibt.

  • Firebase — Auth, Firestore, Realtime Database
  • AWS — Lambda, EC2, S3
  • Google Cloud — App Engine, Compute Engine
  • Nach operativer Eignung gewählt, Latenz im Budget
FirebaseAWSGoogle CloudServerless
Plattformübergreifend, in der Tiefe

React Native und Flutter — eine Wahl, kein Standard.

Plattformübergreifende Stacks lassen ein Team beide Stores aus einer Codebasis ausliefern, und ich betreibe React Native und Flutter auf fortgeschrittenem Niveau. Aber der Grund, warum ich ihnen vertraue, ist, dass ich nativ zuerst ausgeliefert habe: Ich weiß genau, was die Bridge mir erspart und wo sie im Weg ist. Wenn ein Bildschirm Core Animation braucht, einen Sensor, den die Bridge nicht sauber freigibt, oder ein Plattformverhalten, das die Abstraktion überpinselt, steige ich ohne Zögern auf ein natives Modul ab.

Xamarin sitzt auf Arbeitsniveau im Werkzeugkasten, was die richtige Antwort in .NET-lastigen Organisationen ist, wo der Rest des Stacks bereits C# ist. Die Entscheidung zwischen ihnen ist nie eine Frage der Loyalität — es geht darum, den Stack an das Team und das Produkt anzupassen, mit dem Nativen immer darunter verfügbar.

GETEILTE CODEBASIS React Native · Flutter iOS-RENDER ANDROID-RENDER Swift-Modul Kotlin-Modul

Eine Codebasis, zwei Stores, native Notluke

Geteilte Geschäftslogik obenauf, native Module dort, wo die Bridge aufhört.

In einer plattformübergreifenden App leben die Geschäftslogik und der Großteil der UI in einer einzigen geteilten Codebasis — React Native oder Flutter —, die sowohl auf iOS als auch auf Android rendert. Die Teile, die die Abstraktion nicht bedienen kann, fallen auf native Module in Swift und Kotlin durch, sodass die App nie Fähigkeit gegen Portabilität tauscht.

Diese Notluke ist der ganze Grund, warum native Geläufigkeit auch in einem plattformübergreifenden Projekt zählt. Die geteilte Schicht deckt das gemeinsame Terrain ab; die nativen Module decken die Ränder ab.

  • Geteilte Logik und UI in einer Codebasis
  • Rendert sowohl auf iOS als auch auf Android
  • Native Module in Swift / Kotlin an den Rändern
  • Fähigkeit nie gegen Portabilität getauscht
React NativeFlutterNative Module
CI für Mobil

Die Pipeline, die einen Commit in eine Installation verwandelt.

Mobile CI ist härter als Web-CI — es gibt zwei Toolchains, Code-Signierung und zwei Store-Review-Prozesse zwischen dem Commit und dem Nutzer.

Ein mobiles Release ist kein Deploy; es ist ein Build, eine Signatur und eine Einreichung. Die Pipeline, die ich betreibe, lintet und type-checkt zuerst, baut dann das iOS-Archive mit Xcode und signiert es, baut das signierte Android-APK oder -AAB mit Gradle und liefert versionierte Artefakte an TestFlight und den internen Track vor dem Store. Die Regel ist einfach: keine grüne Pipeline, kein Release.

Das Diagramm unten zeigt dieses Auffächern — ein Commit, zwei Toolchains, zwei Stores — denn die Kosten, sich bei Code-Signierung oder einem Versionssprung zu irren, sind eine abgelehnte Einreichung und ein verlorener Tag, kein schnelles Re-Deploy.

COMMIT Release PRÜFUNGEN lint · type · test XCODE signieren · archive GRADLE signiertes AAB App Store Play Store

Commit → Prüfungen → zwei Builds → zwei Stores

Ein Commit fächert sich in zwei signierte Builds auf.

Die Pipeline führt die statischen Prüfungen einmal aus, dann teilt sie sich: ein Xcode-Pfad, der für iOS baut, signiert und archiviert, und ein Gradle-Pfad, der für Android assembliert und signiert. Jeder produziert ein versioniertes Artefakt, das vor dem Release auf den Test-Track seines Stores geht.

Den signierten Build als das Einzige zu behandeln, das ausgeliefert wird — nie ein lokales Archive von jemandes Maschine —, ist es, was Releases über elf Jahre von Apps reproduzierbar hält.

  • Lint, Type-Check und Unit-Tests als Tor
  • Xcode signiert und archiviert für iOS
  • Gradle assembliert signiertes APK/AAB für Android
  • Versionierte Artefakte auf Test-Tracks, dann Store
CICode-SignierungTestFlightGradle

Mobile CI — Stufe für Stufe

Auslöser
Commit auf einen Release-Branch
Statische Prüfungen
Lint, Type-Check (TypeScript), Unit-Tests
iOS-Build
Xcode-Build, Code-Signierung, Archive
Android-Build
Gradle assemble, signiertes APK/AAB
Artefakt
Versioniertes Build pro Plattform
Auslieferung
TestFlight / interner Track, dann Store
Tor
Keine grüne Pipeline, kein Release
Frontend-Handwerk

Die Web-Oberflächen, die neben der App sitzen.

Ein mobiles Produkt ist selten nur die App. Es gibt Onboarding-Seiten, Marketing-Oberflächen, eingebettete Web-Views und gemeinsame Logik, die alle dieselbe Sorgfalt wie der native Code wollen. Ich baue diese in sauberem HTML5 und CSS3, organisiere die Styles mit SASS/SCSS, damit das Design-System ein System bleibt, und schreibe die Logik in modernem JavaScript — ES6+ und TypeScript überall dort, wo das Projekt ein Typsystem toleriert.

TypeScript zahlt sich auf Mobil besonders aus: Ein typisierter Vertrag fängt das Missverhältnis zwischen Client und Server, bevor ein Nutzer es je sieht, und dieselben Typen können vom GraphQL-Schema bis in den React-Native-Client fließen.

01

HTML5 und CSS3

Die Web-Oberflächen neben einer mobilen App — Onboarding-Seiten, Marketing, eingebettete Web-Views — in sauberem HTML5 und CSS3 gebaut statt reflexartig in ein Framework gekippt.

02

SASS/SCSS

Stylesheets mit SASS/SCSS organisiert, damit das Design-System ein System bleibt: Variablen, Partials und Mixins statt kopierter Deklarationen.

03

JavaScript ES6+

Modernes JavaScript für die gemeinsame Logik und die Web-Schicht — Module, async/await und die Sprachfunktionen, die eine Codebasis Jahre später lesbar machen.

04

TypeScript

TypeScript überall dort, wo das Projekt es toleriert, denn ein typisierter Vertrag fängt das Missverhältnis zwischen Client und Server, bevor ein Nutzer es je sieht.

05

REST-APIs

REST für die unkomplizierten, ressourcenförmigen Endpunkte, mit Versionierung und vorhersehbaren Fehlerformen, auf die der mobile Client sich verlassen kann.

06

GraphQL-APIs

GraphQL dort, wo ein mobiler Bildschirm genau seine Daten in einer Rundreise braucht — die Netz-Rundreise, die ein Telefon zahlt, ist die, die zu sparen sich lohnt.

Der Bogen

Elf Jahre, eine Plattformentscheidung nach der anderen.

Fundament, nativ, plattformübergreifend, Backend, Cloud — derselbe Ingenieur, der der Arbeit folgte, während Produkte über ihre ersten tausend Nutzer hinauswuchsen.

In Reihenfolge gelesen ist die Mobil-Arbeit eine einzige durchgehende Bahn statt einer Liste von Frameworks. Sie beginnt mit elf Jahren Auslieferung an echte Nutzer, vertieft sich in natives iOS und Android, erstreckt sich über React Native und Flutter, wo sie passen, greift zurück auf die Backends, die einen Client mehr als eine Hülle machen, und gelangt zu den Clouds, die ihn skalieren.

Was sich durch alles zieht, ist dieselbe Weigerung, die den Rest meiner Arbeit durchläuft: Die App und das System dahinter sind ein Problem, nicht zwei, zwischen zwei Personen aufgeteilt.

  1. Fundament Elf Jahre Auslieferung an echte Nutzer Die Bahn, die alles andere verankert: mehr als elf Jahre Bau und Auslieferung mobiler Apps, die echte Menschen installiert und genutzt haben, auf iOS, Android und Windows.
  2. Nativ iOS und Android in ihren eigenen Sprachen Swift und Objective-C auf iOS mit SwiftUI, Core Data, Core Animation und SiriKit; Kotlin und Java auf Android über viele OS-Versionen. Die native Geläufigkeit, die die plattformübergreifenden Entscheidungen fundiert macht.
  3. Plattformübergreifend React Native und Flutter auf fortgeschrittenem Niveau Ein Team, das zwei Stores ausliefert, wo es passt, auf native Module absteigend, wo es nicht passt — plus Xamarin auf Arbeitsniveau für .NET-lastige Teams.
  4. Backend Die Serverseite jeder App Node/Express, Spring Boot, Django/Flask, Rails und Go (Gin/Gorm), die REST und GraphQL bereitstellen, gestützt von SQLite, Redis und ORMs — die Backends, die einen mobilen Client mehr als eine dünne Hülle machen.
  5. Cloud Firebase, AWS und Google Cloud Verwaltete Datenspeicher, serverlose Rechenleistung und Speicher, gewählt nach operativer Eignung — die Infrastruktur, die eine App über ihre ersten tausend Nutzer hinaus skaliert.
Wie ich arbeite

Die Prinzipien unter den Plattformen.

Die Plattformen und Frameworks ändern sich mit dem Produkt; die Prinzipien nicht. Dies sind die Regeln, die ich anwende, ob die App natives Swift, natives Kotlin, React Native oder Flutter ist — der Teil, der elf Jahre Mobil-Arbeit in Apps verwandelt hat, die weiterfunktionierten, statt in ein Portfolio von Starts.

01

Native Geläufigkeit verdient die Abstraktion

Weil ich Swift und Kotlin direkt ausgeliefert habe, ist die Wahl von React Native oder Flutter ein abgewogener Kompromiss, kein Weg, das Lernen einer Plattform zu vermeiden. Ich weiß, was die Bridge mir erspart und was sie mir verbirgt.

02

Der Vertrag kommt zuerst

Client und Server werden gegen einen vereinbarten REST- oder GraphQL-Vertrag gebaut, idealerweise durchgängig typisiert. Die Integration wird entworfen, bevor eine der beiden Seiten geschrieben ist, nicht verhandelt, nachdem beide brechen.

03

Annehmen, dass das Netz versagt

Ein Telefon bewegt sich durch Funklöcher. Die App schreibt zuerst lokal in SQLite, reiht für Sync ein und degradiert auf Nur-Lesen, statt einen Schreibvorgang zu verlieren oder an einem Spinner zu hängen.

04

Die Regeln der Plattform respektieren

Hintergrundgrenzen, Prozesstod, Store-Review, Code-Signierung — das sind keine Hindernisse zum Umgehen. Mit ihnen zu arbeiten ist es, was eine App installiert statt deinstalliert hält.

05

Die Fragmentierung testen

Viele Android-OS-Versionen und eine Spanne von iOS-Geräten bedeuten, das zu testen, was Nutzer tatsächlich tragen, nicht zu behaupten, die neueste Version decke alle ab.

06

Elf Jahre sind der Nachweis

Jede Behauptung hier ist durch Apps gestützt, die ausgeliefert wurden und ausgeliefert blieben. Die Langlebigkeit ist der Beweis — kein Framework auf einer Folie, sondern Software, die den Kontakt mit echten Nutzern überlebt hat.

Elf Jahre Apps, die ausgeliefert wurden und ausgeliefert blieben, sind der Nachweis. Alles andere auf dieser Seite ist wahr, weil diese eine Sache es ist.

Open to the right work

Wenn Ihr Produkt eine mobile App ist und das Backend, die Cloud und das Offline-Verhalten, die sie vertrauenswürdig machen, ist das das ganze Problem, das ich will.

If you are holding a problem that doesn't fit inside one field, that is the conversation I want.

NextCloud & DevOps