Mobile-Developer-Interviewvorbereitung

Mobile-Developer-Interviewfragen

20 häufig gestellte Interviewfragen für Mobile Developers. Mobile Entwicklung ist ein gefragtes Feld für zuverlässige iOS-, Android- und Cross-Platform-Apps und behandelt Produkt-, Performance-, Release-, Offline- und Geräteintegrationsaufgaben. Die Fragen decken verschiedene Niveaus ab, und du kannst mündliche Antworten in unserem Interview-Trainer üben.

Mobile-Developer-KI-Interview startenKeine Kreditkarte erforderlich. 1 kostenlose Sitzung verfügbar.
Technisches Interview-Training auf EnglischEin Modus, in dem Nicht-Muttersprachler technische Interviews üben können.

Fragen für Einsteiger

1Was ist der Unterschied zwischen dem Speichern eines Authentifizierungs-Tokens in einem standardmäßigen Key-Value-Speicher wie SharedPreferences oder UserDefaults und einem sicheren Plattform-Speicher wie dem Android Keystore oder der iOS Keychain?

Standard-Speichermechanismen wie SharedPreferences unter Android und UserDefaults unter iOS sind für einfache, unkritische Einstellungen konzipiert. Sie speichern Daten in unverschlüsselten Klartextdateien (XML oder plist) im Sandbox-Verzeichnis der Anwendung. Jeder mit physischem Zugriff auf ein gerootetes oder gejailbreaktes Gerät, einem unverschlüsselten Geräte-Backup oder Dateisystemzugriff kann diese Tokens direkt auslesen. Im Gegensatz dazu bietet sichere Plattform-Speicherung – speziell iOS Keychain und Android Keystore (häufig über EncryptedSharedPreferences genutzt) – eine Verschlüsselung ruhender Daten. Die iOS Keychain verschlüsselt gespeicherte Elemente mit Schlüsseln, die an die Geräte-Hardware gebunden sind (wie die Secure Enclave), und ermöglicht feingranulare Zugriffsrichtlinien. Der Android Keystore generiert und speichert kryptografische Schlüssel in hardwareisolierten Sicherheitsmodulen (Trusted Execution Environment oder StrongBox), wodurch sichergestellt wird, dass kryptografische Schlüssel niemals im Anwendungsspeicher offengelegt werden und nicht aus dem Dateisystem extrahiert werden können.

// --- ANDROID ---
// INSECURE (Plain SharedPreferences):
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", token).apply() // Plaintext XML on disk

// SECURE (EncryptedSharedPreferences backed by Android Keystore):
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()
val securePrefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
securePrefs.edit().putString("auth_token", token).apply()

// --- IOS (Swift) ---
// INSECURE:
UserDefaults.standard.set(token, forKey: "auth_token") // Plain plist on disk

// SECURE (Keychain Services API):
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "user_auth_token",
    kSecValueData as String: token.data(using: .utf8)!,
    kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
Diese Frage mit einem KI-Coach beantworten

2Was ist der Hauptzweck einer mobilen CI/CD-Pipeline (Continuous Integration / Continuous Deployment) und welche einzigartigen Phasen und Einschränkungen unterscheiden sie von Web- oder Backend-Bereitstellungspipelines?

Der Hauptzweck einer mobilen CI/CD-Pipeline besteht darin, das Erstellen, Testen, Signieren und Verteilen mobiler Client-Anwendungen zu automatisieren, um eine gleichbleibende Qualität und wiederholbare Releases zu gewährleisten. Continuous Integration (CI) validiert Codeänderungen durch automatisierte Builds, statische Codeanalyse und Unit-Tests. Continuous Delivery/Deployment (CD) paketiert, signiert und verteilt Binär-Artefakte an Testkanäle (wie TestFlight oder Firebase App Distribution) oder App-Stores. Mobile CI/CD unterscheidet sich in mehreren zentralen Punkten von Web- und Backend-Pipelines: 1. Artefakttyp: Builds erzeugen kompilierte Client-Binärdateien (.ipa, .apk, .aab), anstatt direkt auf Servern oder in Container-Images ausgeführt zu werden. 2. Runner-Hardware-Einschränkungen: Die iOS-Kompilierung erfordert macOS-Hardware und Xcode-Befehlszeilentools, während Backend-Pipelines typischerweise in schlanken Linux-Containern laufen. 3. Release-Latenz und Freigabeprozesse: Die Veröffentlichung für Endbenutzer erfordert Überprüfungszyklen durch Drittanbieter-App-Stores (Apple App Store / Google Play Store), was bedeutet, dass Bereitstellungen nicht sofort über ein Server-Redeploy zurückgerollt werden können. Fehlerbehebungen erfordern das Einreichen einer neuen signierten Binärdatei oder den Einsatz von Feature-Flags zur Laufzeit.

name: Mobile CI/CD Workflow

on:
  pull_request:
    branches: [main]
  push:
    tags:
      - 'v*.*.*'

jobs:
  ci_validation:
    name: Lint & Unit Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Linter
        run: ./gradlew lint
      - name: Run Unit Tests
        run: ./gradlew testDebugUnitTest

  cd_release_ios:
    name: Build & Distribute iOS
    needs: ci_validation
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Install Signing Certificates
        run: fastlane match appstore --readonly
      - name: Build & Upload to TestFlight
        run: fastlane ios beta
Diese Frage mit einem KI-Coach beantworten

3Was ist in einer plattformübergreifenden mobilen Anwendung der Zweck der Trennung von Präsentations-, Domain- und Datenschicht und wie erleichtert diese Struktur das Teilen von Code zwischen iOS und Android?

Die Trennung einer Anwendung in Präsentations-, Domain- und Datenschicht schafft klare Zuständigkeitsgrenzen: 1. **Präsentationsschicht:** Verarbeitet das UI-Rendering, Benutzereingaben und den Zustand auf Bildschirmebene (z. B. Views, Widgets, ViewModels oder Presenter). 2. **Domainschicht:** Kapselt reine Geschäftslogik, Domain-Modelle/-Entitäten und Anwendungsfälle (Use Cases). Sie bleibt unabhängig von Benutzeroberflächen und Plattform-Frameworks. 3. **Datenschicht:** Verwaltet den Datenabruf und die Persistenz von entfernten APIs, lokalen Datenbanken oder dem Geräte-Cache über Repositories und Datenquellen. In der plattformübergreifenden mobilen Entwicklung erleichtert diese Struktur das Teilen von Code, da Domain- und Datenschicht plattformunabhängig sind. Kern-Geschäftsregeln, Datentransformationen und Netzwerklogik können zu 100 % zwischen iOS und Android geteilt werden. Dies ermöglicht es Teams, entweder den gesamten Stack zu teilen (wie in Flutter oder React Native) oder die Geschäfts- und Datenlogik zu teilen, während plattformspezifische native Präsentationsschichten beibehalten werden (wie in Kotlin Multiplatform).

[Presentation Layer] (UI, Screens, ViewModels)
         │
         ▼ calls
[Domain Layer]       (Use Cases, Business Rules, Entities)
         ▲
         │ implements interfaces
[Data Layer]         (Repositories, API Clients, Local Storage)
Diese Frage mit einem KI-Coach beantworten

4Was sind die grundlegenden Unterschiede zwischen imperativen und deklarativen UI-Entwicklungsparadigmen (User Interface) in der mobilen Softwareentwicklung, und wie spiegeln Flutter-Widgets und React-Native-Komponenten das deklarative Modell wider?

Bei der imperativen UI-Entwicklung schreiben Entwickler explizite Schritt-für-Schritt-Anweisungen, um UI-Elemente zu erstellen, zu verändern und zu zerstören (etwa das Suchen eines Views über eine ID und das direkte Aufrufen von Methoden wie `setText` oder `setVisibility`). Im Gegensatz dazu beschreiben deklarative UI-Modelle, wie die Benutzeroberfläche für einen bestimmten Zustand aussehen soll (häufig ausgedrückt als UI = f(state)). Ändert sich der Zustand, ermittelt das Framework effizient, wie die visuelle Darstellung aktualisiert werden muss. Sowohl Flutter als auch React Native verkörpern dieses deklarative Paradigma. In Flutter sind Widgets unveränderliche Konfigurationsbeschreibungen; wenn sich dynamische Daten ändern, löst der Aufruf von `setState()` in einem `StatefulWidget` einen Neuaufbau aus, bei dem die `build()`-Methode eine neue Widget-Baumbeschreibung zurückgibt, die Flutter mit seinen Element- und RenderObject-Bäumen abgleicht. In React Native sind Komponenten Funktionen oder Klassen, die JSX zurückgeben; eine Aktualisierung von State oder Props löst ein Re-Rendering aus, bei dem React den virtuellen Elementbaum abgleicht und die minimal notwendigen nativen Aktualisierungen über die Bridge oder die native Laufzeitumgebung anwendet.

class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _increment,
      child: Text('Count: $_count'),
    );
  }
}
Diese Frage mit einem KI-Coach beantworten

5Was ist der betriebliche Unterschied zwischen einem Kaltstart (Cold Start), einem Warmstart (Warm Start) und einem Heißstart (Hot Start) bei mobilen Anwendungen, und warum ist der Kaltstart die kritischste Metrik für Performance-Budgets?

Im Lebenszyklus mobiler Apps unterscheiden sich die Startzustände danach, ob der Prozess und der Speicherzustand der App im Betriebssystem bereits existieren: - Kaltstart (Cold Start): Die App startet von Grund auf neu, da das Betriebssystem ihren Prozess noch nicht erstellt hat oder der Prozess zuvor beendet wurde. Das Betriebssystem muss einen neuen Prozess zuweisen, Binärdateien und Laufzeitumgebungen laden, den Application-/Laufzeitkontext initialisieren und die erste View instanziieren. Dies hat die höchste Latenz. - Warmstart (Warm Start): Der Prozess der App befindet sich meist noch im Arbeitsspeicher, aber die Activity-/View-Hierarchie wurde zerstört oder freigegeben (z. B. durch Neuerstellung im Hintergrund oder Zurück-Navigation). Das Betriebssystem erstellt die UI bzw. Activity neu, ohne einen Prozess von Grund auf neu erzeugen zu müssen. - Heißstart (Hot Start): Die App und ihr UI-Zustand befinden sich noch vollständig im Arbeitsspeicher (z. B. wenn der Nutzer den Home-Button gedrückt hat und unmittelbar zurückkehrt). Das Betriebssystem bringt lediglich die bestehende View-Hierarchie in den Vordergrund – mit nahezu null Initialisierungsaufwand. Der Kaltstart ist die kritischste Metrik für Performance-Budgets, da er den langsamsten ersten Eindruck für Nutzer darstellt. Lange Kaltstarts korrelieren direkt mit sofortigen Nutzerabbrüchen, geringerer Nutzerbindung und schlechteren Platzierungen im App Store (wie den Schwellenwerten von Android Vitals).

Cold Start: [OS Process Spawn] -> [App Runtime Init] -> [Activity/UI Creation] -> [First Frame Drawn]
Warm Start: [Existing Process]  -> [Activity/UI Recreation] -> [First Frame Drawn]
Hot Start:  [Existing Process]  -> [Bring Existing View to Foreground] (Instant)
Diese Frage mit einem KI-Coach beantworten

6Was ist eine native Bridge in einer plattformübergreifenden mobilen App, und wann sollte man eine solche verwenden, anstatt das Feature vollständig in Flutter oder React Native zu schreiben?

Eine native Bridge ist die Kommunikationsschicht, die es plattformübergreifendem Code ermöglicht, plattformspezifischen iOS- oder Android-Code aufzurufen, und nativem Code erlaubt, Ergebnisse zurückzugeben oder Events zu senden. In Flutter geschieht dies üblicherweise über Platform Channels oder Plugins. In React Native wird dies meist über Native Modules, TurboModules oder native UI-Komponenten umgesetzt. Eine Bridge kommt zum Einsatz, wenn ein Feature etwas benötigt, worauf reiner Flutter- oder React-Native-Code nicht ausreichend zugreifen kann, wie etwa eine Plattform-API, eine Gerätefunktion, ein natives SDK (Software Development Kit), eine leistungskritische native Implementierung oder eine native UI-Komponente. Typische Beispiele sind Kamerafunktionen, Bluetooth, Push-Benachrichtigungen, Zahlungsabwicklungen, sichere Speicher, Hintergrunddienste, Health-APIs oder SDKs, die ausschließlich Swift/Objective-C- oder Kotlin/Java-Integrationen bereitstellen. Eine gute Bridge stellt der plattformübergreifenden Schicht in der Regel eine schlanke, klare API bereit, wie getBatteryLevel, startBluetoothScan oder openNativePaymentSheet, während die plattformspezifische Implementierung die eigentlichen iOS- und Android-Details verarbeitet.

Cross-platform screen:
  calls NativePayments.openPaymentSheet(orderId)

iOS implementation:
  uses Apple Pay / native payment SDK

Android implementation:
  uses Google Pay / native payment SDK
Diese Frage mit einem KI-Coach beantworten

7Was bedeutet eine Offline-First-Architektur in der mobilen Entwicklung und wie unterscheidet sie sich grundlegend vom Standard-HTTP-Response-Caching (Hypertext Transfer Protocol)?

In der mobilen Entwicklung betrachtet eine Offline-First-Architektur den lokalen Speicher als primäre Source of Truth für Lese- und Schreiboperationen. Anstatt auf den Abschluss von Netzwerkanfragen zu warten, bevor gerendert wird oder Benutzerinteraktionen zugelassen werden, interagiert die App direkt mit der lokalen Datenbank oder Speicherschicht. Hintergrund-Synchronisationsprozesse gleichen lokale Änderungen mit dem Remote-Server ab, sobald eine Verbindung besteht. Dies unterscheidet sich grundlegend vom Caching von HTTP-Antworten in mehreren Punkten: 1. **Datenmodell**: HTTP-Caching speichert rohe Netzwerkantworten (z. B. JSON-Nutzdaten), indexiert nach Anfrage-URLs und Headern. Eine Offline-First-Architektur speichert strukturierte Domänenentitäten in einer lokalen Datenbank (wie Room, SQLite oder SwiftData/Core Data). 2. **Schreib- vs. Lesezugriffe**: HTTP-Caching ist primär ein Mechanismus zur Leseoptimierung und unterstützt von Haus aus keine Offline-Schreibtransaktionen oder Mutationen. Offline-First ermöglicht vollständige lokale Schreibvorgänge und stellt Mutationen für die spätere Server-Synchronisierung in eine Warteschlange. 3. **Abfragbarkeit und Lebenszyklus**: Gecachte HTTP-Antworten unterliegen automatischen Bereinigungsrichtlinien und können nicht beliebig abgefragt, gefiltert oder verknüpft werden. Eine Offline-First-Persistenz bietet vollständige Abfragemöglichkeiten, Indizierung und ein deterministisches Lebenszyklusmanagement unabhängig vom Netzwerkstatus.

/* Network-First with HTTP Caching */
UI -> Network Request -> [Cache Hit ? Return Cached JSON : Fetch from API -> Save to HTTP Cache] -> UI
// Limitation: Read-only optimization; offline writes fail immediately.

/* Offline-First with Local Source of Truth */
UI <-> Observes Local DB (e.g., Room / Core Data / SQLite)
User Write -> Write to Local DB -> Queue Sync Task
Sync Worker (Background) -> Send queued mutations to Server -> Update Local DB with Server Response
Diese Frage mit einem KI-Coach beantworten

8Was ist der Unterschied zwischen einer Remote-Push-Benachrichtigung und einer lokalen Benachrichtigung in einer mobilen App?

Der wesentliche Unterschied zwischen lokalen und Remote-Push-Benachrichtigungen liegt darin, wo sie entstehen und wie sie ausgelöst werden: 1. Lokale Benachrichtigungen (Local Notifications) werden vollständig auf dem Endgerät von der Anwendung über Betriebssystem-APIs (wie `UserNotifications` unter iOS oder `AlarmManager`/`WorkManager`/`NotificationManager` unter Android) erstellt, geplant und ausgelöst. Sie basieren auf Bedingungen auf dem Gerät wie einem bestimmten Datum, einer Uhrzeit, einem Countdown-Timer oder einer geografischen Begrenzung (Geofencing). Da sie lokal über den Betriebssystem-Daemon laufen, benötigen sie zum Zustellzeitpunkt weder einen Backend-Server noch eine aktive Internetverbindung. 2. Remote-Push-Benachrichtigungen (Remote Push Notifications) stammen von einem externen Anwendungsserver und werden über das Internet über plattformspezifische Push-Gateways (APNs für Apple-Geräte, FCM für Android) übertragen. Das Gateway liefert die Nutzdaten (Payload) an den systemweiten Daemon des Geräts, der die App weckt oder ein Banner anzeigt. Remote-Benachrichtigungen sind für externe Echtzeitereignisse wie eingehende Chat-Nachrichten, Freundschaftsanfragen oder Eilmeldungen erforderlich.

let content = UNMutableNotificationContent()
content.title = "Workout Reminder"
content.body = "Time for your daily exercise routine."
content.sound = .default

// Triggers locally after 1 hour (3600 seconds) without any server interaction
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false)
let request = UNNotificationRequest(identifier: "dailyWorkout", content: content, trigger: trigger)

UNUserNotificationCenter.current().add(request) { error in
    if let error = error {
        print("Failed to schedule notification: \(error)")
    }
}
Diese Frage mit einem KI-Coach beantworten

9Was ist der strukturelle und betriebliche Unterschied zwischen einem Android App Bundle (.aab), einer APK (Android Package) und einem iOS-IPA-Archiv (iOS App Store Package) bei der Verteilung von Apps über App-Stores?

Eine APK (Android Package) ist ein ausführbares Paketformat, das kompilierten DEX-Bytecode, Ressourcen, Assets und native Bibliotheken enthält und direkt auf einem Android-Gerät installiert und ausgeführt werden kann. Eine universelle APK bündelt Ressourcen für alle Bildschirmdichten, CPU-Architekturen und Sprachen, was zu größeren Downloadgrößen führt. Ein Android App Bundle (.aab) ist ein Upload- und Veröffentlichungsformat, das von Google Play für den Store-Vertrieb vorausgesetzt wird. Ein AAB kann nicht direkt auf einem Gerät installiert werden; stattdessen nutzt Google Play das Bundle und Play App Signing, um optimierte Split-APKs zu generieren, die dynamisch auf die Architektur, Bildschirmdichte und Sprache eines bestimmten Zielgeräts zugeschnitten sind (Dynamic Delivery). Ein iOS-IPA (.ipa) ist eine Anwendungsarchivdatei (ein ZIP-Container, der das Payload-Verzeichnis, das `.app`-Bundle, Signierungs-Assets und Metadaten umfasst). Nach dem Upload in App Store Connect führt Apple App Thinning (wie App Slicing) durch, um nur die für das jeweilige Download-Gerät erforderlichen Assets und Binärdateien auszuliefern. Betrieblich können APKs und IPAs (vorbehaltlich korrekter Signierung und Provisionierung) direkt auf physischen Testgeräten installiert werden, während ein AAB vor der lokalen Geräteinstallation zuerst in Split-APKs umgewandelt werden muss (z. B. mithilfe von `bundletool`).

# 1. Standalone APK installs directly via ADB
adb install myapp-universal.apk

# 2. AAB requires bundletool to generate device-tailored split APKs
bundletool build-apks --bundle=myapp.aab --output=myapp.apks --connected-device

# 3. Install the generated split APK set onto the connected device
bundletool install-apks --apks=myapp.apks
Diese Frage mit einem KI-Coach beantworten

10Welchen Schutz bietet TLS (Transport Layer Security) für die Netzwerkaufrufe einer mobilen App, und wie setzen moderne mobile Betriebssysteme sichere Standardeinstellungen für die Datenübertragung durch?

TLS (Transport Layer Security) schützt mobile Netzwerkaufrufe durch drei grundlegende Garantien: Vertraulichkeit (Verschlüsselung der Daten bei der Übertragung, sodass Dritte Payloads und Header nicht mitlesen können), Datenintegrität (Erkennung von Manipulationen oder Veränderungen an Anfragen und Antworten während der Übertragung) und Serverauthentifizierung (Validierung des digitalen Serverzertifikats anhand vertrauenswürdiger Zertifizierungsstellen, um Man-in-the-Middle-Angriffe zu verhindern). Moderne mobile Betriebssysteme erzwingen eine sichere Übertragung standardmäßig, indem sie unverschlüsselten Klartext-HTTP-Verkehr blockieren: 1. iOS erzwingt App Transport Security (ATS), wodurch Netzwerkverbindungen (etwa über URLSession) standardmäßig HTTPS mit mindestens TLS 1.2 nutzen müssen, sofern keine Domänenausnahmen explizit in der `Info.plist` hinterlegt sind. 2. Android (ab API 28) deaktiviert Klartext-HTTP-Verkehr standardmäßig (`cleartextTrafficPermitted=false`). Die Zulassung von unverschlüsseltem HTTP erfordert explizite Ausnahmen über die Netzwerksicherheitskonfiguration (`network_security_config.xml`) oder das Manifest-Attribut `android:usesCleartextTraffic`.

<!-- Android: res/xml/network_security_config.xml -->
<network-security-config>
    <!-- Cleartext HTTP blocked by default. Exceptions require explicit declaration -->
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.example.com</domain>
    </domain-config>
</network-security-config>

<!-- iOS: Info.plist ATS configuration -->
<!-- ATS blocks HTTP by default; exceptions must be declared inside NSAppTransportSecurity -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>
Diese Frage mit einem KI-Coach beantworten

Fragen für Fortgeschrittene

11Wie implementieren Sie eine biometrische Authentifizierung (Face ID / Fingerabdruck) mit sicherem Plattform-Speicher, um sicherzustellen, dass kryptografische Schlüssel erst nach erfolgreicher biometrischer Überprüfung freigeschaltet werden?

Um kryptografische Schlüssel sicher an biometrische Merkmale zu koppeln, darf sich eine App nicht bloß auf einen booleschen UI-Callback eines biometrischen Prompts verlassen. Stattdessen müssen kryptografische Schlüssel in hardwaregestütztem Speicher generiert und abgelegt werden (Secure Enclave unter iOS, Android Keystore / StrongBox unter Android), der mit Zugriffskontrollrichtlinien konfiguriert ist, die eine biometrische Authentifizierung vor der Schlüsselverwendung erzwingen. Unter iOS werden Schlüssel oder Keychain-Einträge mit `SecAccessControlCreateWithFlags` und Flags wie `.biometryCurrentSet` (oder `.userPresence`) konfiguriert. Wird der private Schlüssel zum Signieren oder Entschlüsseln angefordert, fordert das Betriebssystem den Benutzer automatisch zu Face ID / Touch ID auf. Unter Android werden Schlüssel mit `KeyGenParameterSpec.Builder` und `.setUserAuthenticationRequired(true)` erzeugt. Eine kryptografische Operation (wie eine `Cipher` oder `Signature`) wird initialisiert und als `BiometricPrompt.CryptoObject` an `BiometricPrompt.authenticate()` übergeben. Der Schlüssel wird erst innerhalb von `onAuthenticationSucceeded` über dieses authentifizierte `CryptoObject` freigeschaltet und nutzbar gemacht. Die Verwendung von `.biometryCurrentSet` (iOS) oder `setInvalidatedByBiometricEnrollment(true)` (Android) stellt sicher, dass vorhandene Schlüssel dauerhaft ungültig werden, wenn ein neuer Fingerabdruck oder ein neues biometrisches Merkmal auf dem Gerät registriert wird. Dies verhindert unbefugten Zugriff, falls der Geräte-Passcode kompromittiert wurde.

val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val spec = KeyGenParameterSpec.Builder(
    "biometric_auth_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)
    .setInvalidatedByBiometricEnrollment(true)
    .build()

keyGen.init(spec)
keyGen.generateKey()
Diese Frage mit einem KI-Coach beantworten

12Welche Caching-Strategien und Pipeline-Optimierungen können Sie implementieren, um die Build-Zeiten von Pull Requests auf plattformübergreifenden mobilen CI-Runnern (Continuous Integration) deutlich zu verkürzen?

Um die Build-Zeiten von Pull Requests auf mobilen CI-Runnern drastisch zu verkürzen, müssen Optimierungen bei der Abhängigkeitsauflösung, dem Caching der Kompilierung und der selektiven Ausführung ansetzen. Erstens sollte ein effektives Dependency-Caching implementiert werden, das auf Hashes von Lockfiles basiert (z. B. `package-lock.json`, `Podfile.lock`, `gradle/verification-metadata.xml` oder Resolved-Dateien von SPM-Paketen). Dadurch wird verhindert, dass externe Pakete auf bereinigten Runner-Instanzen erneut heruntergeladen oder aufgelöst werden müssen. Zweitens sollten native Build-Caches konfiguriert werden. Aktivieren Sie für Android den Gradle Build Cache (`--build-cache`) mit Remote-HTTP-Caching oder CI-nativem persistentem Caching für `~/.gradle/caches` und Build-Verzeichnisse. Cachen Sie für iOS Xcode `DerivedData` und Kompilierungsartefakte von Swift Packages / CocoaPods sorgfältig oder nutzen Sie moderne Remote-Caching-Tools wie Tuist oder Bazel. Cachen Sie für React Native/Flutter die Ausgabe des JS-Bundles, node_modules und den SDK-Cache der Flutter-Engine. Drittens sollten Sie eine selektive Job-Ausführung anwenden (Änderungserkennung und Test-Impact-Analyse). Verwenden Sie Git-Pfadfilter, um iOS-Builds zu überspringen, wenn sich nur Android- oder Backend-Dateien geändert haben, führen Sie schnelle Linting- und Unit-Test-Stufen vor aufwendigen UI-Tests oder Kompilierungsschritten aus und vermeiden Sie das Erstellen vollständiger Release-Binärdateien (wie AABs oder universeller IPAs) bei PR-Durchläufen, wenn nur ein Simulator-/Debug-Build oder ein Unit-Test-Lauf erforderlich ist.

name: PR Fast Check
on:
  pull_request:
    paths:
      - 'android/**'
      - 'shared/**'

jobs:
  android-unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: '17'

      # Gradle build caching for dependencies & task outputs
      - uses: gradle/actions/setup-gradle@v3
        with:
          cache-read-only: false

      - name: Run Unit Tests & Lint Only
        run: ./gradlew testDebugUnitTest lintDebug --build-cache --parallel
Diese Frage mit einem KI-Coach beantworten

13Wie würden Sie ein geteiltes Feature strukturieren, damit Präsentation, Domain-Use-Cases und Datenzugriff unabhängig testbar und plattformübergreifend für iOS und Android wiederverwendbar bleiben?

Um ein geteiltes Feature für eine unabhängige Testbarkeit und Wiederverwendbarkeit unter iOS und Android zu strukturieren, empfiehlt sich eine saubere Schichtenarchitektur (Clean Architecture), die in Presentation-, Domain- und Data-Layer unterteilt ist: 1. **Domain Layer (reine geteilte Logik):** Enthält reine Geschäfts-Entitäten, Repository-Schnittstellen (Contracts) und Use Cases/Interactors (z. B. `GetCartUseCase`, `ApplyPromoCodeUseCase`). Diese Schicht hat keinerlei Abhängigkeiten von UI-Frameworks (UIKit, SwiftUI, Android Views, Jetpack Compose, Flutter-Widgets) oder Plattform-APIs. Jeder Use Case kapselt eine einzelne Geschäftsoperation, wodurch er zu 100 % mit einfachen Mocks isoliert per Unit-Test überprüfbar ist. 2. **Data Layer (Datenzugriff und Kapselung):** Implementiert die Repository-Schnittstellen der Domain-Schicht. Er interagiert mit Remote-Datenquellen (REST/GraphQL) und lokaler Persistenz (SQLite/Key-Value). Netzwerk-DTOs (Data Transfer Objects) und Datenbank-Entitäten werden strikt auf reine Domain-Entitäten gemappt, bevor sie diese Schicht verlassen. Dadurch wird verhindert, dass externe Serialisierungsschemata oder Datenbankänderungen in die Domain- oder UI-Schicht durchsickern. 3. **Presentation Layer (UI und Zustandsverwaltung):** Besteht aus State Holdern (ViewModels, Blocs oder Presentern) und UI-Views/Widgets. Der State Holder ruft Domain-Use-Cases auf, verwaltet den UI-Zustand (Laden, Erfolg, Fehler) und stellt dem View einen beobachtbaren Zustand zur Verfügung. Der View beobachtet diesen Zustand lediglich und rendert ihn. Diese Struktur ermöglicht es, Use Cases isoliert ohne UI zu testen, Repositories zu mocken, um ViewModels zu testen, und UI-Implementierungen zwischen Plattformen auszutauschen, während die gesamte Domain- und Datenlogik wiederverwendet wird.

[ UI View / Compose / SwiftUI ]
         |
         v (Observes State / Dispatches Events)
[ ViewModel / Presenter / BLoC ] (Presentation Layer)
         |
         v (Executes pure business operations)
[ Use Case / Interactor ] (Domain Layer - Pure Shared Logic)
         |
         v (Calls interface contract)
[ Repository Interface ] (Domain Layer Contract)
         ^
         | (Implements)
[ Repository Implementation ] (Data Layer)
         |---- Maps NetworkDTO -> DomainEntity
         |---- Maps DatabaseEntity -> DomainEntity
    [ API Client / Local DB / Storage ]
Diese Frage mit einem KI-Coach beantworten

14Wie beurteilen Sie, wann lokaler Komponenten-State nicht mehr ausreicht, und wie wählen Sie einen geeigneten Architekturansatz für das State Management in einer plattformübergreifenden Codebasis mit mehreren Entwicklern?

In der plattformübergreifenden mobilen Entwicklung wird der Übergang von lokalem Komponenten-State (`useState`/`StatefulWidget`) zu einer architektonischen State-Management-Lösung durch State-Scope, Lifecycle-Anforderungen und Testbarkeit getrieben: 1. **Wann lokaler State nicht mehr ausreicht**: - **Komponentenübergreifende Nutzung & Prop Drilling**: Wenn State über separate Navigationsrouten oder weit entfernte Zweige des Widget-/Komponentenbaums hinweg aufgerufen oder verändert werden muss. - **Lifecycle-Persistenz**: Wenn Daten das Schließen von Bildschirmen, Routenwechsel oder Navigations-Resets überdauern müssen (z. B. Benutzersitzung, Warenkorbinhalt, zwischengespeicherte Feeds). - **Trennung von Zuständigkeiten (Separation of Concerns) & Testbarkeit**: Wenn Geschäftslogik, Validierung und Seiteneffekte mit UI-Komponenten verwoben werden, was automatisierte Headless-Unit-Tests erschwert. 2. **Auswahl einer Architektur für Multi-Entwickler-Codebasen**: - **Unidirektionaler Datenfluss / Vorhersehbare Container (z. B. BLoC, Redux, Riverpod)**: Erzwingt eine strikte Trennung, bei der die UI explizite Events/Actions sendet und unveränderlichen State rendert, der von dedizierten Geschäftslogikeinheiten ausgegeben wird. Das schafft klare Schnittstellenverträge, reduziert Seiteneffekte und ermöglicht isolierte Unit-Tests der Geschäftslogik. - **Atomare / Scoped Reactive Stores (z. B. Zustand, MobX, Provider)**: Bieten weniger Boilerplate-Code und flexible Selector-Subscriptions, ideal wenn Teams eine leichtgewichtige Entkopplung und gezielte Re-Renderings einzelner Komponenten bevorzugen. In einem Teamumfeld besteht das primäre Architekturziel darin, reine Geschäftslogik von UI-Rendering-Schichten zu isolieren und selektive Subscriptions zu nutzen, um unnötige Re-Renderings des gesamten Baums zu vermeiden.

// Zustand store: pure state container isolated from React UI hierarchy
import { create } from 'zustand';

interface CartState {
  items: string[];
  addItem: (item: string) => void;
  clearCart: () => void;
}

export const useCartStore = create<CartState>((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  clearCart: () => set({ items: [] }),
}));

// Usage in components uses selective subscriptions to avoid unnecessary re-renders:
// const itemCount = useCartStore((state) => state.items.length);
Diese Frage mit einem KI-Coach beantworten

15Wie würden Sie Ruckler (Jank) in einer plattformübergreifenden mobilen App analysieren und reduzieren, wenn das Scrollen durch eine lange Liste mit Bildern und dynamischen Inhalten stockt?

Zunächst würde ich das Ruckeln auf realen Testgeräten reproduzieren und mit Profiling-Tools analysieren, da Ruckler beim Scrollen unterschiedliche Ursachen haben können: überschrittene Frame-Budgets, JS- oder Dart-Berechnungen, Auslastung des Main-/UI-Threads, GPU-/Rasterisierungsaufwand, Layout-Berechnungen, Bilddecodierung, Speicherdruck oder Netzwerkoperationen. Bei 60 Hz stehen pro Frame rund 16,7 ms zur Verfügung, bei 120 Hz etwa 8,3 ms. Aufwendiges Rendering, Decodieren, Layouten oder synchrone Berechnungen während des Scrollens führen daher schnell zu verworfenen Frames (Dropped Frames). Zur Lokalisierung des Engpasses eignen sich Tools wie Flutter DevTools, Performance-Tools für React Native, Flipper, Android Studio Profiler, Xcode Instruments sowie Frame-Timeline-Ansichten. Für die lange Liste stelle ich sicher, dass Lazy Loading oder Virtualisierung eingesetzt wird – etwa über `FlatList`, `FlashList` oder `RecyclerListView` in React Native beziehungsweise `ListView.builder`, `SliverList` oder ähnliche Builder in Flutter. Wichtig sind stabile Keys, das Vermeiden unnötiger Re-Renders aller Zeilen bei Zustandsänderungen übergeordneter Komponenten, die gezielte Memoisierung von Zeilenkomponenten oder Selektoren sowie ein performantes `build` bzw. `renderItem`. Sortier-, Filter-, JSON-Parsing-, Formatierungs- oder Bildverarbeitungslogik sollte vollständig aus dem Scroll-Pfad ausgelagert werden. Bei vorhersehbaren Elementgrößen bieten Layout-Hinweise wie `getItemLayout` in React Native oder feste Elementausmaße in Flutter weitere Optimierungsmöglichkeiten. Bei Bildern sollten passend skalierte Thumbnails bereitgestellt, zwischengespeichert und kleine Zellen nicht mit hochauflösenden Originalen befüllt werden. Platzhalter, Lazy Loading und das Überwachen von Speicher-Churn durch zu viele große Bitmaps sind hierbei essenziell. Zudem sollten übermäßig verschachtelte Zeilen-Layouts vereinfacht, rechenintensive Schatten, Clipping, Overdraw und Transparenzen reduziert sowie Daten gebatcht oder paginiert geladen werden. Abschließendes Re-Profiling bestätigt, ob sich Frame-Zeiten und Dropped Frames messbar verbessert haben.

1. Record a trace while scrolling on a real device.
2. Check frame timeline: are frames exceeding 16.7 ms / 8.3 ms?
3. Identify bottleneck: JS/Dart, main/UI thread, raster/GPU, image decode, memory, or network.
4. Fix the largest measured bottleneck: virtualization, image resizing/caching, row memoization, layout simplification, moving work off scroll path.
5. Re-test on low-end and target-refresh-rate devices.
Diese Frage mit einem KI-Coach beantworten

16Wie verhalten sich Thread-Grenzen bei Aufrufen über native Bridges, und wie verhindern Sie Blockaden durch Thread-Wechsel oder UI-Ruckler (User Interface), wenn native Module aufwendige Hintergrundarbeit ausführen?

Plattformübergreifende Frameworks nutzen spezifische Threading-Konventionen für Bridge-Aufrufe: Standardmäßige `MethodChannel`-Aufrufe in Flutter treffen beispielsweise auf dem UI-Hauptthread der Plattform ein, während traditionelle Bridge-Aufrufe in React Native auf einem dedizierten JavaScript-Thread laufen und asynchron an native Threads übergeben werden. Wenn eine native Bridge-Methode auf dem nativen UI-/Hauptthread ausgeführt wird, blockieren CPU-intensive Operationen, lange synchrone Dateisystem-I/O oder blockierende Netzwerkaufgaben die Haupt-Event-Loop. Dies führt zu ausgelassenen Frames, sichtbarem UI-Ruckeln und unter Android zu ANR-Fehlern (Application Not Responding) oder zur Beendigung durch den iOS-Watchdog. Um dies zu verhindern, sollten native Bridge-Handler rechenintensive oder blockierende Aufgaben an Hintergrund-Threadpools auslagern (z. B. Kotlin Coroutines mit `Dispatchers.IO`/`Dispatchers.Default`, Android `ThreadPoolExecutor` oder Swift `Task.detached` / GCD `DispatchQueue.global()`). Sobald die Arbeit abgeschlossen ist, muss das Ergebnis an den vom Framework erwarteten Bridge-Thread zurückübertragen werden (z. B. Rückgabe der Ergebnisse auf dem UI-Thread bei Standard-MethodChannels oder über Hintergrund-Task-Queues), damit plattformübergreifende Zustandsaktualisierungen den Renderzyklus der Benutzeroberfläche nicht blockieren.

class ImageProcessorPlugin : MethodChannel.MethodCallHandler {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        if (call.method == "applyFilter") {
            val imageBytes = call.argument<ByteArray>("image") ?: return result.error("INVALID_ARG", "Image null", null)
            
            // Dispatch heavy computation off the main UI thread
            scope.launch {
                try {
                    val processed = processBitmapBytes(imageBytes) // CPU heavy work
                    withContext(Dispatchers.Main) {
                        result.success(processed)
                    }
                } catch (e: Exception) {
                    withContext(Dispatchers.Main) {
                        result.error("PROCESSING_FAILED", e.localizedMessage, null)
                    }
                }
            }
        } else {
            result.notImplemented()
        }
    }
}
Diese Frage mit einem KI-Coach beantworten

17Wie würden Sie eine Caching- und Invalidierungsstrategie für Listen- und Feed-Ansichten mithilfe von Stale-While-Revalidate und bedingten HTTP-Headern (Hypertext Transfer Protocol) entwerfen?

Eine robuste Caching- und Invalidierungsstrategie für Feed- und Listenansichten kombiniert das Stale-While-Revalidate-Muster (SWR) mit bedingten HTTP-Validierungsheadern (wie `ETag` / `If-None-Match` oder `Last-Modified` / `If-Modified-Since`). Beim Öffnen des Bildschirms werden gecachte Daten sofort aus einer lokalen Datenbank (der Single Source of Truth, z. B. Room oder Core Data/SQLite) gelesen und gerendert, was dem Nutzer eine sofortige Darstellung ermöglicht. Parallel wird eine Hintergrund-Netzwerkanfrage mit dem gecachten Header `If-None-Match: <etag>` gesendet. Antwortet der Server mit `304 Not Modified`, werden keine Nutzdaten übertragen, was den lokalen Cache validiert sowie Bandbreite und Akku schont. Antwortet der Server mit `200 OK`, werden die neuen Daten und der aktualisierte `ETag` in einer Transaktion in die lokale Datenbank geschrieben, und reaktive Datenbank-Observer geben die aktualisierte Liste automatisch an die UI weiter. Bei Paginierung werden Page-Tokens oder Offsets zusammen mit den gecachten Einträgen gespeichert. Die Invalidierung wird durch TTL-Ablauf, Benutzeraktionen (wie Pull-to-Refresh, das bedingte Header umgeht) oder lokale Mutations-Nebeneffekte ausgelöst (z. B. führt das Erstellen oder Löschen eines Eintrags zu einem optimistischen lokalen Update und markiert gecachte Cursor für eine erneute Validierung).

fun getFeed(): Flow<List<FeedItem>> = flow {
    val cached = feedDao.getFeedItems()
    if (cached.isNotEmpty()) emit(cached)
    
    val lastEtag = feedDao.getFeedEtag()
    try {
        val response = api.fetchFeed(ifNoneMatch = lastEtag)
        if (response.code() == 200 && response.body() != null) {
            feedDao.updateFeedTransaction(response.body()!!, response.headers()["ETag"])
            emit(feedDao.getFeedItems())
        }
    } catch (e: Exception) {
        if (cached.isEmpty()) throw e
    }
}
Diese Frage mit einem KI-Coach beantworten

Fragen für Erfahrene

18Eine Finanz-Mobile-App muss Refresh-Tokens sicher auf dem Gerät speichern, biometrische Entsperrung unterstützen, OS-Upgrades (Operating System) überstehen und während vorübergehender Offline-Sitzungen sicher funktionieren. Wie würden Sie diese Token-Speicher- und Zugriffsarchitektur entwerfen?

Eine robuste Token-Speicherarchitektur für Mobilgeräte nutzt hardwaregestützte Sicherheitsmodule: die iOS-Keychain mit Secure Enclave und den Android Keystore mit StrongBox oder einer TEE (Trusted Execution Environment). Refresh-Tokens müssen im Ruhezustand mit Schlüsseln verschlüsselt werden, die durch biometrisches kryptografisches Gating geschützt sind (wie `kSecAccessControlBiometryCurrentSet` oder `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` unter iOS und `setUserAuthenticationRequired(true)` mit `AUTH_BIOMETRIC_STRONG` unter Android). Kryptografisches Gating stellt sicher, dass der Schlüssel nur nach erfolgreicher biometrischer Authentifizierung von der Hardware freigegeben wird, anstatt sich auf eine umgehbare boolesche Prüfung im Anwendungscode zu verlassen. Um OS-Upgrades zu überstehen und gleichzeitig unbefugten Zugriff zu verhindern, binden sich Schlüssel an die Gerätehardware und erzwingen Richtlinien bei Registrierungsänderungen, die reauthentifizieren oder Schlüssel ungültig machen, wenn neue Biometriedaten registriert werden (z. B. `setInvalidatedByBiometricEnrollment(true)` unter Android). Für temporäre Offline-Sitzungen können kurzlebige, verschlüsselte Access-Tokens und eingeschränkte lokale Offline-Sitzungszustände innerhalb definierter lokaler TTLs und begrenzter Berechtigungen arbeiten, während privilegierte Refresh-Aktionen zurückgestellt werden, bis wieder Netzwerkkonnektivität besteht. Nach der Wiederverbindung validiert eine Refresh-Token-Rotation mit Erkennung von Replay-Angriffen auf dem Backend die Sitzung; wird eine Token-Wiederverwendung oder ein Kontowiderruf festgestellt, sendet das Backend ein Signal zur Ungültigkeitserklärung, das den Client anweist, die hardwaregestützten Schlüssel zu löschen und den Offline-Speicher zu bereinigen.

val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val spec = KeyGenParameterSpec.Builder(
    "refreshTokenKeyAlias",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)
    .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
    .setInvalidatedByBiometricEnrollment(true)
    .build()
keyGenerator.init(spec)
keyGenerator.generateKey()
Diese Frage mit einem KI-Coach beantworten

19Wie würden Sie eine skalierbare CI/CD-Infrastruktur (Continuous Integration/Continuous Deployment) für ein plattformübergreifendes Mobile-Monorepo entwerfen, das mehrere Teams unterstützt, während begrenzte und teure macOS-Runner-Kapazitäten verwaltet werden?

Um eine skalierbare CI/CD-Infrastruktur für ein plattformübergreifendes Mobile-Monorepo zu entwerfen und gleichzeitig knappe und teure macOS-Kapazitäten zu optimieren, muss die Architektur auf graphenbasierter Erkennung betroffener Builds, strikter Arbeitslastaufteilung über hybride Runner-Flotten und Virtualisierung/ephemerer Orchestrierung für macOS aufbauen. Zunächst wird ein Monorepo-Build-Graph-Tool (wie Bazel, Nx oder Turborepo) mit verteilter Remote-Zwischenspeicherung implementiert. Bei jedem Pull Request berechnet die CI-Pipeline die Differenz im gerichteten azyklischen Graphen (DAG) gegenüber dem Ziel-Branch, um nur betroffene Pakete und deren nachgelagerte Abhängigkeiten zu bauen und zu testen, wodurch unveränderte Module vollständig übersprungen werden. Zweitens wird eine hybride Runner-Zuweisungsstrategie umgesetzt: Alle Aufgaben, die nicht zwingend Xcode erfordern (wie TypeScript/Dart-Linting und -Kompilierung, Unit-Tests, statische Codeanalyse, Sicherheitsscans und Android-Gradle-Builds), werden auf kostengünstige, skalierbare Linux/Kubernetes-Runner ausgelagert. macOS-Runner bleiben ausschließlich für die finale iOS-Paketierung, Swift/Objective-C-Kompilierung, Code-Signierung und iOS-Simulator-Testausführung reserviert. Drittens werden macOS-Runner mittels Virtualisierungsinfrastruktur verwaltet (wie Tart, Anka oder AWS/MacStadium-Bare-Metal-Knoten, orchestriert über Nomad oder Kubernetes). Jeder iOS-Build läuft in einer sauberen, ephemeren virtuellen Maschine mit vorbereiteten Toolchains und Derived-Data-Caches, was Runner-Drift verhindert und schnelles automatisches Skalieren basierend auf der Pipeline-Warteschlangentiefe ermöglicht.

name: Cross-Platform Monorepo CI
on: [pull_request]
jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      ios_affected: ${{ steps.filter.outputs.ios }}
      android_affected: ${{ steps.filter.outputs.android }}
    steps:
      - uses: actions/checkout@v4
      - id: filter
        run: |
          # Determine affected targets via monorepo tool (e.g. nx / bazel)
          echo "ios=$(./tools/affected.sh ios)" >> $GITHUB_OUTPUT
          echo "android=$(./tools/affected.sh android)" >> $GITHUB_OUTPUT

  build-android:
    needs: detect-changes
    if: needs.detect-changes.outputs.android_affected == 'true'
    runs-on: ubuntu-latest-16core
    steps:
      - uses: actions/checkout@v4
      - run: ./gradlew assembleRelease

  build-ios:
    needs: detect-changes
    if: needs.detect-changes.outputs.ios_affected == 'true'
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - run: bundle exec fastlane ios build_and_test
Diese Frage mit einem KI-Coach beantworten

20Wie würden Sie eine plattformübergreifende Mobile-Architektur für ein Produkt entwerfen, bei dem der Großteil der Geschäftslogik zwischen iOS und Android geteilt werden soll, während sich plattformspezifische UI (User Interface), Navigation und native Integrationen unabhängig voneinander weiterentwickeln können?

Ich würde die App um einen gemeinsamen Kern herum aufbauen, der die stabile Geschäftslogik verwaltet: Domänenmodelle, Validierungen, Use Cases, Geschäftsregeln und Datenzugriffsverträge. Plattformspezifische UI, Navigation, native SDKs, Berechtigungen, Lebenszyklus-Handling und App-Shell-Belange sollten außerhalb dieses Kerns verbleiben. Der geteilte Code sollte weder UIKit, SwiftUI, Jetpack, Android-Framework-APIs, React Native Navigation, Flutter Navigation noch native SDK-Details importieren. Stattdessen sollte er von präzisen Schnittstellen wie `AuthRepository`, `SecureStorage`, `Analytics`, `CameraService` oder `PaymentProvider` abhängen, die von der jeweiligen Plattform implementiert werden. Ich würde den Code so weit wie möglich nach Features strukturieren, anstatt eine einzige große gemeinsame Schicht zu erstellen. Beispielsweise können Checkout, Suche, Account und Messaging jeweils über geteilten Domain-/Use-Case-Code, Zustandskontrakte und Repository-Interfaces verfügen. Die iOS- und Android-Shells steuern dann den Aufbau der Bildschirme, native UI-Muster, Navigationsstapel, Berechtigungen und das Adapter-Wiring. Präsentationslogik kann geteilt werden, wenn sie vollständig unabhängig vom UI-Framework ist – wie z. B. Reducer, Zustandsautomaten oder View-Model-Verträge, die einfache Zustände und Aktionen ausgeben. Die eigentlichen Views und die Navigation sollten jedoch plattformspezifisch bleiben, damit sich jede Plattform unabhängig weiterentwickeln kann. Der zentrale Kompromiss besteht darin, gemeinsame, stabile Geschäftslogik konsequent zu teilen, während Abstraktionen vermieden werden, die reale Plattformunterschiede verschleiern. Ich würde Modulgrenzen durch Abhängigkeitsregeln, Dependency Injection, öffentliche APIs, Contract-Tests, Architekturprüfungen und klare Ownership durchsetzen. Native Integrationen sollten Ports und Adapter nutzen, sodass der geteilte Feature-Code eine einheitliche Funktionalität sieht, während jede Plattform SDK-Verhalten, Lebenszyklus, Berechtigungen, Fehler und UX-Unterschiede in ihrer eigenen Schicht handhabt.

shared-core/
  checkout/
    domain/          # Money, Cart, CheckoutRules, PlaceOrderUseCase
    ports/           # PaymentGateway, TaxRepository, Analytics
    state/           # CheckoutStateMachine or ViewModel contract

ios-app/
  checkout-ui/       # SwiftUI/UIKit screens and iOS navigation
  adapters/          # ApplePayPaymentGateway, KeychainStorage

android-app/
  checkout-ui/       # Compose/XML screens and Android navigation
  adapters/          # GooglePayPaymentGateway, EncryptedSharedPrefsStorage
Diese Frage mit einem KI-Coach beantworten