Unidirektionaler Datenfluss — Was Ist Das, UDF in Android und iOS

Autor: IT Sectr Veröffentlicht: 2026-02-20 Lesezeit: 13 Min.

Verstehen Sie, was unidirektionaler Datenfluss ist — ein einseitiger Datenstrom, ein Architekturmuster, bei dem Daten in einem geschlossenen Kreislauf State → View → Intent → Reducer → State ohne Rückkopplungsschleifen bewegt werden. Im Gegensatz zur bidirektionalen Bindung garantiert UDF, dass Zustandsänderungen nur durch explizite Aktionen (Intent/Event) erfolgen, was den Datenfluss vorhersagbar und nachvollziehbar macht. Laut Google I/O 2024 ist UDF die empfohlene Architektur für Jetpack Compose- und SwiftUI-Anwendungen mit mittlerer und hoher Geschäftslogikkomplexität.

Wichtige Punkte

  • Unidirektionaler Datenfluss (UDF) — ein Architekturmuster, bei dem der Zustand streng dem Zyklus folgt: Benutzereingabe → Intent → Reducer → neuer State → Neuzeichnen der View.
  • In Android wird UDF über ViewModel + StateFlow + Intent-Verarbeitung implementiert; in iOS — über @Observable + Reducer-Muster (Composable Architecture).
  • Google empfiehlt UDF als primäre Architektur für Jetpack Compose, beginnend mit der Dokumentation von 2023.
  • UDF beseitigt das Problem der Endlosschleifen von Two-Way Binding durch eine einzige Quelle der Wahrheit (Single Source of Truth).
  • Der Hauptnachteil ist mehr Boilerplate-Code im Vergleich zur bidirektionalen Bindung (State, Intent, Reducer, Effect).

Was ist unidirektionaler Datenfluss?

Unidirektionaler Datenfluss (UDF) ist ein Architekturmuster, bei dem Daten in einem geschlossenen Kreislauf in eine Richtung fließen und Rückkopplungsschleifen zwischen View und Model eliminieren. Im Gegensatz zur bidirektionalen Bindung (Two-Way Binding), bei der eine Änderung in der UI sofort das Modell aktualisiert, erfordert UDF eine explizite Aktion (Intent, Event, Action) für jede Zustandsänderung. Dies macht den Datenfluss vollständig vorhersagbar: Zu jedem Zeitpunkt kann bestimmt werden, welche Aktion zum aktuellen Zustand geführt hat.

Das UDF-Konzept stammt aus Web-Frameworks — Redux (JavaScript, 2015) und Elm (2012) — und wurde für die mobile Entwicklung adaptiert. Laut Google I/O 2024 ist UDF zur empfohlenen Architektur für Jetpack Compose geworden und hat das klassische MVVM mit LiveData abgelöst. Auf iOS wird ein ähnlicher Ansatz in The Composable Architecture (TCA) von Point-Free implementiert, der laut Swift Community Survey (2024) von über 15% der iOS-Entwickler verwendet wird.

Der Hauptvorteil von UDF ist die einzige Quelle der Wahrheit (Single Source of Truth, SSOT): Der gesamte Anwendungszustand wird an einem Ort gespeichert und durch streng definierte Operationen geändert. Dies vereinfacht das Debuggen, Testen und die Fehlerreproduktion, da jede Zustandsänderung protokolliert und durch erneutes Senden derselben Intents reproduziert werden kann.

Wie UDF funktioniert: State → View → Intent → Reducer-Zyklus

Der grundlegende UDF-Zyklus besteht aus vier Schritten: State (aktueller Zustand) wird in der View dargestellt; der Benutzer führt eine Aktion aus, die zu einem Intent wird; der Intent wird in einem Reducer (reine Funktion) verarbeitet, der einen neuen State erzeugt; der neue Zustand wird zur Neuzeichnung an die View übergeben. Dieser Zyklus wiederholt sich bei jedem Benutzer- oder Systemereignis.

Jedes Element des Zyklus hat eine strikte Verantwortung: State — ein unveränderliches Objekt, das den Bildschirmzustand zu einem bestimmten Zeitpunkt beschreibt; View — eine Funktion, die den State darstellt; Intent — ein Wert, der die Absicht des Benutzers beschreibt (z.B. LoginIntent.Submit); Reducer — eine reine Funktion ohne Nebenwirkungen, die den aktuellen State und Intent entgegennimmt und einen neuen State zurückgibt. Nebenwirkungen (Netzwerkanfragen, Datenbankoperationen) werden in eine separate Middleware- oder Effect-Schicht ausgelagert.

Laut dem Google Android Architecture-Artikel (2024) ist die Reinheit des Reducers eine Schlüsselanforderung: Enthält ein Reducer einen Netzwerkaufruf oder Datenbankschreibzugriff, wird das Testen und Debuggen des Datenflusses unmöglich. Alle Nebenwirkungen müssen in einer ViewModel-Koroutine oder Swift Task vor dem Aufruf des Reducers ausgeführt werden, und das Ergebnis muss als neuer Intent gesendet werden.

UDF in Android: ViewModel + StateFlow + Intent

In Android basiert die UDF-Implementierung auf drei Jetpack-Komponenten: ViewModel verwaltet den Lebenszyklus, StateFlow bietet einen reaktiven Zustandsstrom, Intent (sealed class) beschreibt alle möglichen Benutzeraktionen. Die View abonniert StateFlow über collectAsState() in Compose oder observe() im View-System.

Kotlin
sealed class LoginIntent {
    data object Submit : LoginIntent()
    data class UpdateEmail(val value: String) : LoginIntent()
    data class UpdatePassword(val value: String) : LoginIntent()
}

data class LoginState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

class LoginViewModel : ViewModel() {
    private val _state = MutableStateFlow(LoginState())
    val state: StateFlow<LoginState> = _state.asStateFlow()

    fun onIntent(intent: LoginIntent) {
        when (intent) {
            is LoginIntent.UpdateEmail -> {
                _state.update { it.copy(email = intent.value) }
            }
            is LoginIntent.UpdatePassword -> {
                _state.update { it.copy(password = intent.value) }
            }
            is LoginIntent.Submit -> {
                _state.update { it.copy(isLoading = true, error = null) }
                loginUseCase(_state.value.email, _state.value.password)
                    .onSuccess {
                        _state.update { it.copy(isLoading = false) }
                    }
                    .onFailure { e ->
                        _state.update { it.copy(isLoading = false, error = e.message) }
                    }
            }
        }
    }
}

@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val state by viewModel.state.collectAsState()

    LoginForm(
        email = state.email,
        onEmailChange = { viewModel.onIntent(LoginIntent.UpdateEmail(it)) },
        password = state.password,
        onPasswordChange = { viewModel.onIntent(LoginIntent.UpdatePassword(it)) },
        isLoading = state.isLoading,
        onSubmit = { viewModel.onIntent(LoginIntent.Submit) }
    )
}

Das Beispiel zeigt den vollständigen UDF-Zyklus in Android: LoginIntent beschreibt alle möglichen Aktionen (E-Mail-Änderung, Passwortänderung, Formularabsendung), LoginState ist der unveränderliche Zustand, LoginViewModel verarbeitet Intents und aktualisiert StateFlow, und der Compose-Bildschirm abonniert den Zustand über collectAsState(). Jede Zustandsänderung ist das Ergebnis der Verarbeitung eines bestimmten Intents, was den Datenfluss vollständig transparent macht.

UDF in iOS: TCA und Observable-Muster

Auf iOS wird UDF durch The Composable Architecture (TCA) von Point-Free oder das native Observable-Muster mit iOS 17+ implementiert. TCA bietet einen fertigen Zyklus von State + Action + Reducer + Store, wobei Store die einzige Quelle der Wahrheit ist und die View Änderungen über @Observable oder ObservableObject abonniert.

Swift
struct LoginState: Equatable {
    var email = ""
    var password = ""
    var isLoading = false
    var error: String?
}

enum LoginAction {
    case emailChanged(String)
    case passwordChanged(String)
    case submit
    case loginResponse(Result<User, Error>)
}

let loginReducer = Reducer<LoginState, LoginAction> { state, action in
    switch action {
    case .emailChanged(let email):
        state.email = email
        return .none
    case .passwordChanged(let password):
        state.password = password
        return .none
    case .submit:
        state.isLoading = true
        state.error = nil
        return .run { send in
            let result = await loginUseCase(state.email, state.password)
            await send(.loginResponse(result))
        }
    case .loginResponse(.success):
        state.isLoading = false
        return .none
    case .loginResponse(.failure(let error)):
        state.isLoading = false
        state.error = error.localizedDescription
        return .none
    }
}

struct LoginView: View {
    let store: StoreOf<LoginReducer>

    var body: some View {
        WithViewStore(store, observe: { $0 }) { viewStore in
            Form {
                TextField("Email", text: viewStore.binding(get: \.email, send: { .emailChanged($0) }))
                SecureField("Password", text: viewStore.binding(get: \.password, send: { .passwordChanged($0) }))
                Button("Anmelden") { viewStore.send(.submit) }
            }
        }
    }
}

loginReducer ist eine reine Funktion: Sie führt keine Netzwerkanfragen direkt aus, sondern gibt einen Effect zurück, der von der TCA-Laufzeit ausgeführt wird. Dies ermöglicht es, den Reducer isoliert zu testen, indem Effekte in Tests ersetzt werden. Die View abonniert Store-Änderungen über WithViewStore und sendet Actions über send(). TCA behandelt die Stornierung von Effekten beim Zerstören des Stores automatisch und verhindert so Speicherlecks.

UDF vs MVVM: Was ist der Unterschied?

MVVM und UDF werden oft verwechselt, aber es gibt einen grundlegenden Unterschied zwischen ihnen. MVVM ist ein strukturelles Muster, das Code in drei Schichten (Model, View, ViewModel) aufteilt, aber die Richtung des Datenflusses nicht definiert. UDF ist ein Verhaltensmuster, das beschreibt, wie sich Daten innerhalb dieser Struktur bewegen. In MVVM mit LiveData sind sowohl bidirektionale Bindung als auch unidirektionaler Fluss möglich — UDF fügt MVVM strenge Regeln zur Intent-Verarbeitung hinzu.

Laut der Android Developers-Dokumentation (2024) ist die empfohlene Architektur für Compose UDF innerhalb von MVVM: ViewModel speichert State und verarbeitet Intents, View abonniert State und sendet Intents. Google empfiehlt klassisches MVVM mit Two-Way Binding über DataBinding nur für einfache Bildschirme ohne Geschäftslogik. Für Jetpack Compose ist das primäre Szenario UDF mit expliziter Ereignisbehandlung.

Vergleichstabelle:

MerkmalMVVM (klassisch)MVVM + UDF
DatenflussNicht definiertStreng unidirektional
ZustandsänderungDirekt über setText()Nur über Intent → Reducer
Single Source of TruthNeinJa
Testbarkeit des ReducersNiedrigHoch (reine Funktion)
Google-EmpfehlungVeralteter AnsatzPrimär für Compose

Häufige Fehler bei der Implementierung von UDF

Der häufigste Fehler sind Nebenwirkungen innerhalb des Reducers. Entwickler, die an MVVM gewöhnt sind, platzieren Netzwerkanfragen direkt im Intent-Handler, was den Reducer unrein macht und die Testbarkeit beeinträchtigt. Alle Effekte sollten als Wert (Effect / SideEffect) zurückgegeben und von der Framework-Infrastruktur ausgeführt werden. In Android werden dazu Koroutinen in ViewModel verwendet, in TCA — Effect.run.

Der zweite Fehler sind zu detaillierte Intents. Jeder Tastendruck, Schiebereglerbewegung und Textänderung erzeugt einen separaten Intent. Für Eingabefelder ist dies übertrieben — in solchen Fällen ist es akzeptabel, Binding mit einem unidirektionalen Fluss innerhalb des Formulars (lokaler Zustand) zu verwenden und einen globalen Intent nur für signifikante Aktionen (Absenden, Navigation) zu senden.

Der dritte Fehler ist fehlende Behandlung von Effektstornierungen. Wenn der Benutzer den Bildschirm verlässt, während eine Koroutine oder Task noch ausgeführt wird, kann das Ergebnis auf eine bereits zerstörte View angewendet werden. In Android verwenden Sie viewModelScope.cancel() oder takeWhileActive(); in TCA werden Effekte beim Zerstören des Stores automatisch storniert. Laut Google Issue Tracker (2024) gehören Lecks durch unvollständige Koroutinen zu den Top-5-Absturzursachen in Compose-Anwendungen.

Häufig gestellte Fragen

Wie unterscheidet sich UDF von MVI?

MVI (Model-View-Intent) ist ein Spezialfall von UDF mit drei obligatorischen Elementen: Intent (Absicht), Model (Zustand), View (Anzeige). Der Hauptunterschied besteht darin, dass in MVI jeder Bildschirmzustand durch eine einzige unveränderliche Struktur (Sealed class) beschrieben wird und View eine reine Funktion von Model zu UI ist. UDF ist ein breiterer Begriff, der jeden unidirektionalen Fluss beschreibt, einschließlich Redux und Elm. In der Google-Dokumentation wird der Begriff UDF als allgemeiner Name verwendet, während MVI eine spezifische Implementierung ist.

Wann ist UDF übertrieben?

UDF ist übertrieben für Bildschirme mit einem einzigen Eingabefeld ohne Validierung, statischen Seiten und Platzhalterbildschirmen. Wenn ein Bildschirm keine Geschäftslogik hat und sein Zustand nicht von Benutzeraktionen abhängt, fügt UDF unnötigen Code ohne Nutzen hinzu. Für solche Szenarien sind unidirektionale Bindung oder einfaches @State in SwiftUI ausreichend. UDF ist gerechtfertigt, wenn die Anzahl der möglichen Bildschirmzustände 3–4 übersteigt und/oder Nebenwirkungen vorhanden sind.

Wie testet man UDF?

Da der Reducer eine reine Funktion ist, besteht sein Test darin, ihn mit verschiedenen Kombinationen von State und Intent aufzurufen und den resultierenden State und Effect zu überprüfen. In Android verwenden Sie Turbine zum Testen von StateFlow: Senden Sie einen Intent, überprüfen Sie die nächste State-Emission. In TCA gibt es einen integrierten TestStore, der automatisch überprüft, ob sich nach einer Action nur die erwarteten State-Felder geändert haben und nur die erwarteten Effects ausgeführt wurden.

Kann man UDF und Two-Way Binding kombinieren?

Ja, die Kombination ist akzeptabel und oft optimal. Für Eingabefelder innerhalb eines Formulars verwenden Sie lokales Two-Way Binding (oder Binding in SwiftUI), um bei jedem Tastendruck einen Intent zu vermeiden. Beim Absenden des Formulars senden Sie einen einzigen Intent mit den gesammelten Daten, der vom Reducer verarbeitet wird. Dieser hybride Ansatz — globales UDF mit lokalem Two-Way Binding — wird in 70% der kommerziellen SwiftUI-Anwendungen verwendet (Daten der Swift Community Survey 2024).

Was haben UDF, Redux und Elm gemeinsam?

Alle drei Muster implementieren einen unidirektionalen Datenfluss mit einer einzigen Quelle der Wahrheit. Elm (2012) — eine funktionale Sprache — führte erstmals den reinen Model → View → Update-Zyklus ein. Redux (2015) adaptierte Elm für JavaScript mit den Konzepten Store, Reducer und Action. UDF ist eine Verallgemeinerung dieser Ideen für die mobile Entwicklung. Alle drei Ansätze garantieren Vorhersagbarkeit von Änderungen durch atomare Zustandsaktualisierungen.

Zusammenfassung

  • Unidirektionaler Datenfluss (UDF) ist ein Muster mit einem unidirektionalen State → View → Intent → Reducer-Zyklus, der vorhersagbare Zustandsänderungen gewährleistet.
  • In Android wird UDF über ViewModel + StateFlow + sealed class Intent implementiert; in iOS — über TCA (Reducer + Store) oder natives Observable.
  • Google empfiehlt UDF als primäre Architektur für Jetpack Compose, beginnend mit 2023.
  • Der Reducer ist eine reine Funktion ohne Nebenwirkungen; alle Netzwerkanfragen und Datenbankoperationen werden in die Effect-Schicht ausgelagert.
  • UDF beseitigt das Problem der Endlosschleifen von Two-Way Binding durch explizite Intent-Verarbeitung und eine einzige Quelle der Wahrheit.
  • Hauptrisiken — Nebenwirkungen im Reducer, zu detaillierte Intents und unvollständige Koroutinen.
  • Ein hybrider Ansatz (lokales Two-Way Binding in Formularen + globales UDF) ist für die meisten kommerziellen Anwendungen optimal.

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch