App‑Start und Initialisierung #
- In controllers/AppController.ts setzt prepareApp() früh globale Rahmenbedingungen:
- Plattform‑Setup (z. B. Android NavigationBar nur mit
Platform.OS === 'android'). - Audio‑Modus (iOS‑freundliche Defaults) via
expo-audio. - Schriftarten via
expo-font. - Am Ende setAppIsReady(true), damit der Rest der App rendern kann.
- Plattform‑Setup (z. B. Android NavigationBar nur mit
- Parallel sorgt der ContextControllerProvider in providers/ContextControllerProvider.tsx für genau eine Controller‑Instanz. Über
loadedwird das UI so lange „gegatet“, bis Module/Stores bereit sind. Erst dann werden Kinder gerendert.
Konsum der Steuerlogik in der UI #
- Screens/Komponenten beziehen den Controller über useContextControllerProvider(), z. B. in app/auth/index.tsx. Damit sind Modulzugriffe (z. B.
modules.core.controllers.*) und zentralisierte Navigation verfügbar. - Die UI bleibt präsentationsorientiert und delegiert Anwendungslogik an den Controller. So entstehen keine duplizierten Codepfade für Navigation oder Datenbeschaffung.
Ereignisse und Orchestrierung #
- Nutzerinteraktionen (Form‑Submit, Button‑Tap) werden als Aktionen an Controller‑Methoden weitergereicht, etwa authController.onPressLogin().
- Der Controller orchestriert die Domänenlogik:
- Validierung von Eingaben.
- Aufruf von Repositories (Directus‑API) zur Datenbeschaffung/Mutation.
- Schreiben der Ergebnisse in beobachtbare Stores (MobX).
- Optional: zentrale Navigation (z. B. Weiterleitung nach erfolgreichem Login).
Reaktive Aktualisierung mit observer #
- Stores sind beobachtbar. Sobald der Controller Daten schreibt (z. B. Token, Benutzerprofil, Listen), propagiert MobX die Änderungen automatisch an alle
observer‑Komponenten. observerbewirkt, dass die UI ausschließlich vom Zustand abhängt. Es sind keine manuellen Re‑Render‑Trigger notwendig. Dadurch bleibt die UI deklarativ, während Geschäftslogik in Controllern/Stores gebündelt ist.
Kurzer Beispiel‑Ausschnitt (konzeptuell, nicht vollständig):
// Store (vereinfacht)
class AuthStore {
@observable me: User | null = null;
@observable isLoading = false;
}
// Controller-Flow (vereinfacht)
async onPressLogin(credentials) {
this.authStore.isLoading = true;
const user = await this.authRepository.login(credentials);
this.authStore.me = user;
this.authStore.isLoading = false;
this.routeController.navigateToNext("(home)");
}
// UI-Komponente (vereinfacht)
export default observer(function AuthStatus() {
const { modules } = useContextControllerProvider();
const { me, isLoading } = modules.core.stores.authStore;
if (isLoading) return <Spinner />;
return <Text>{me ? `Hallo, ${me.name}` : "Nicht eingeloggt"}</Text>;
});
Wesentlich ist hier: Die Komponente fragt nur Zustand ab und markiert sich mit observer. Sobald der Controller den Store aktualisiert, rendert sie automatisch neu.
End‑to‑End‑Beispiel (Login‑Sequenz in der App) #
- Die Login‑Maske components/molecules/Login.tsx ruft beim Tap auf „Login“ eine Controller‑Methode auf (über Props/Controller‑Binding).
- Der Auth‑Controller validiert Eingaben, spricht das Auth‑Repository (Directus) an, verarbeitet die Antwort (Token/User) und schreibt Ergebnisse in den Auth‑Store.
- Der Store‑Wechsel (z. B.
megesetzt,isLoadingfalse) löst automatisch Re‑Render in allenobserver‑Komponenten aus, die diesen Zustand konsumieren (z. B. Anzeige des Benutzer‑Namens oder Freigabe weiterer Routen). - Der Controller triggert eine zentrale Navigation (z. B. zum Home‑Bereich), da Router‑Funktionen injiziert sind und so an einer Stelle konsistente Navigationsregeln gelten.
Ergebnis #
Der Datenfluss UI‑Ereignis → Controller‑Aktion → Store‑Update → Navigation/Render bleibt durchgängig unidirektional. Die Controller sind die zentrale Steuerinstanz, Stores sind die verlässliche Statusquelle, und observer hält die UI automatisch synchron mit dem Anwendungszustand. Das reduziert Doppelimplementierungen, schafft Vorhersehbarkeit und verbessert Wartbarkeit sowie Testbarkeit.