Unidirectional Data Flow — ano ito, UDF sa Android at iOS

May-akda: IT Sectr Nai-publish: 2026-02-20 Oras ng pagbabasa: 13 min

Unawain kung ano ang Unidirectional Data Flow — one-way na daloy ng datos, isang pattern ng arkitektura kung saan ang datos ay gumagalaw sa isang saradong siklo State → View → Intent → Reducer → State nang walang feedback. Hindi tulad ng two-way binding, ginagarantiyahan ng UDF na ang pagbabago ng estado ay nangyayari lamang sa pamamagitan ng malinaw na mga aksyon (Intent/Event), na ginagawang predictable at traceable ang daloy ng datos. Ayon sa Google I/O 2024, ang UDF ay ang inirerekomendang arkitektura para sa Jetpack Compose at SwiftUI na mga application na may katamtaman hanggang mataas na kumplikadong lohika ng negosyo.

Mga Pangunahing Punto

  • Unidirectional Data Flow (UDF) — pattern ng arkitektura kung saan ang estado ay nagbabago nang mahigpit ayon sa siklo: input ng gumagamit → Intent → Reducer → bagong State → muling pagguhit ng View.
  • Sa Android ang UDF ay ipinapatupad sa pamamagitan ng ViewModel + StateFlow + pagproseso ng Intent; sa iOS — sa pamamagitan ng @Observable + pattern ng Reducer (Composable Architecture).
  • Inirerekomenda ng Google ang UDF bilang pangunahing arkitektura para sa Jetpack Compose, simula sa dokumentasyon ng 2023.
  • Inaalis ng UDF ang problema ng walang katapusang mga loop ng Two-Way Binding sa pamamagitan ng nag-iisang pinagmulan ng katotohanan (Single Source of Truth).
  • Pangunahing disbentaha — mas maraming boilerplate code kumpara sa two-way binding (State, Intent, Reducer, Effect).

Ano ang Unidirectional Data Flow?

Unidirectional Data Flow (UDF) — isang pattern ng arkitektura kung saan ang datos ay gumagalaw sa isang direksyon kasama ang isang saradong siklo, hindi kasama ang feedback sa pagitan ng View at Model. Hindi tulad ng Two-Way Binding, kung saan ang pagbabago sa UI ay agad na nag-a-update ng modelo, ang UDF ay nangangailangan ng isang malinaw na aksyon (Intent, Event, Action) para sa bawat pagbabago ng estado. Ginagawa nitong ganap na predictable ang daloy ng datos: sa anumang sandali ay matutukoy kung aling aksyon ang humantong sa kasalukuyang estado.

Ang konsepto ng UDF ay nagmula sa mga web framework — Redux (JavaScript, 2015) at Elm (2012) — at inangkop para sa mobile development. Ayon sa Google I/O 2024, ang UDF ay naging inirerekomendang arkitektura para sa Jetpack Compose, pinapalitan ang klasikong MVVM na may LiveData. Sa iOS, isang katulad na diskarte ay ipinatupad sa The Composable Architecture (TCA) mula sa Point-Free, na ginagamit ng higit sa 15% ng mga iOS developer ayon sa survey ng Swift Community (2024).

Ang pangunahing bentahe ng UDF — Single Source of Truth (SSOT): ang buong estado ng application ay nakaimbak sa isang lugar at binabago sa pamamagitan ng mahigpit na tinukoy na mga operasyon. Pinapasimple nito ang debugging, pagsubok, at pag-reproduce ng mga bug, dahil ang bawat pagbabago ng estado ay naitala at maaaring kopyahin sa pamamagitan ng muling pagpapadala ng parehong Intent.

Paano gumagana ang UDF: siklo State → View → Intent → Reducer

Ang pangunahing siklo ng UDF ay binubuo ng apat na hakbang: State (kasalukuyang estado) ay ipinapakita sa View; ang gumagamit ay nagsasagawa ng aksyon na nagiging Intent (intensyon); Intent ay pinoproseso sa Reducer (isang purong function), na lumilikha ng bagong State; ang bagong estado ay ipinapadala sa View para sa muling pagguhit. Ang siklong ito ay umuulit sa bawat kaganapan ng gumagamit o system.

Ang bawat elemento ng siklo ay may mahigpit na responsibilidad: State — isang hindi nababagong (immutable) na bagay na naglalarawan ng estado ng screen sa isang partikular na sandali; View — isang function na nagpapakita ng State; Intent — isang halaga na naglalarawan ng intensyon ng gumagamit (hal., LoginIntent.Submit); Reducer — isang purong function na walang side effect, na tumatanggap ng kasalukuyang State at Intent at nagbabalik ng bagong State. Ang mga side effect (mga kahilingan sa network, database) ay inililipat sa isang hiwalay na layer ng Middleware o Effect.

Ayon sa artikulo ng Google Android Architecture (2024), ang kadalisayan ng Reducer ay isang pangunahing kinakailangan: kung ang Reducer ay naglalaman ng tawag sa network o pagsulat sa database, ang pagsubok at debugging ng daloy ng datos ay nagiging imposible. Ang lahat ng side effect ay dapat isagawa sa coroutine ng ViewModel o Swift Task bago tawagan ang Reducer, at ang resulta ay ipadala bilang isang bagong Intent.

UDF sa Android: ViewModel + StateFlow + Intent

Sa Android ang pagpapatupad ng UDF ay batay sa tatlong bahagi ng Jetpack: ViewModel namamahala sa lifecycle, StateFlow ay nagbibigay ng reactive na daloy ng mga estado, Intent (sealed class) ay naglalarawan ng lahat ng posibleng aksyon ng gumagamit. Ang View ay nag-subscribe sa StateFlow sa pamamagitan ng collectAsState() sa Compose o observe() sa 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) }
    )
}

Ipinapakita ng halimbawa ang buong siklo ng UDF sa Android: Inilalarawan ng LoginIntent ang lahat ng posibleng aksyon (pagbabago ng email, password, pagpapadala ng form), LoginState — ang hindi nababagong estado, Pinoproseso ng LoginViewModel ang Intent at ina-update ang StateFlow, at ang Compose screen ay nag-subscribe sa state sa pamamagitan ng collectAsState(). Ang bawat pagbabago ng estado ay resulta ng pagproseso ng isang partikular na Intent, na ginagawang ganap na transparent ang daloy ng datos.

UDF sa iOS: TCA at pattern ng Observable

Sa iOS ang UDF ay ipinatutupad sa pamamagitan ng The Composable Architecture (TCA) mula sa Point-Free o ang native na pattern ng Observable sa iOS 17+. Ang TCA ay nagbibigay ng isang handa na siklo State + Action + Reducer + Store, kung saan ang Store ay ang nag-iisang pinagmulan ng katotohanan, at ang View ay nag-subscribe sa mga pagbabago sa pamamagitan ng @Observable o ObservableObject.

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("Mag-login") { viewStore.send(.submit) }
            }
        }
    }
}

Ang reducer na loginReducer — isang purong function: hindi direktang nagsasagawa ng mga kahilingan sa network, ngunit nagbabalik ng Effect na isasagawa ng kapaligiran ng TCA. Ito ay nagpapahintulot sa pagsubok ng reducer nang nakahiwalay, pinapalitan ang mga epekto sa mga pagsubok. Ang View sa pamamagitan ng WithViewStore ay nag-subscribe sa mga pagbabago ng Store at nagpapadala ng Action sa pamamagitan ng send(). Awtomatikong pinangangasiwaan ng TCA ang pagkansela ng mga epekto kapag ang Store ay nawasak, na pumipigil sa pagtagas ng memorya.

UDF laban sa MVVM: ano ang pagkakaiba?

MVVM at UDF ay madalas na napagkakamalan, ngunit may pangunahing pagkakaiba sa pagitan nila. Ang MVVM ay isang structural pattern na naghahati ng code sa tatlong layer (Model, View, ViewModel), ngunit hindi tinutukoy ang direksyon ng daloy ng datos. Ang UDF ay isang pattern ng pag-uugali na naglalarawan kung paano gumagalaw ang datos sa loob ng istrukturang ito. Sa MVVM na may LiveData ay maaaring magkaroon ng parehong two-way binding at one-way na daloy — nagdaragdag ang UDF ng mahigpit na mga patakaran sa pagproseso ng Intent sa MVVM.

Ayon sa dokumentasyon ng Android Developers (2024), ang inirerekomendang arkitektura para sa Compose ay UDF sa loob ng MVVM: Iniimbak ng ViewModel ang State at pinoproseso ang Intent, nag-subscribe ang View sa State at nagpapadala ng Intent. Ang klasikong MVVM na may Two-Way Binding sa pamamagitan ng DataBinding ay inirerekomenda ng Google para lamang sa mga simpleng screen na walang lohika ng negosyo. Para sa Jetpack Compose ang pangunahing senaryo ay UDF na may malinaw na pagproseso ng mga kaganapan.

Talahanayan ng paghahambing:

KatangianMVVM (klasiko)MVVM + UDF
Daloy ng datosHindi tinukoyMahigpit na one-way
Pagbabago ng estadoDirekta sa pamamagitan ng setText()Sa pamamagitan lamang ng Intent → Reducer
Single Source of TruthHindiOo
Kakayahang masuri ang reducerMababaMataas (purong function)
Rekomendasyon ng GoogleLumang diskartePangunahin para sa Compose

Mga karaniwang pagkakamali sa pagpapatupad ng UDF

Ang pinakakaraniwang pagkakamali — mga side effect sa loob ng Reducer. Ang mga developer na nakasanayan sa MVVM ay naglalagay ng mga kahilingan sa network nang direkta sa handler ng Intent, na ginagawang hindi purong function ang Reducer at sinisira ang kakayahang masuri. Ang lahat ng epekto ay dapat ibalik bilang halaga (Effect / SideEffect) at isagawa ng imprastraktura ng framework. Sa Android ginagamit ang mga coroutine sa ViewModel, sa TCA — Effect.run.

Pangalawang pagkakamali — masyadong detalyadong Intent. Ang bawat pagpindot ng key, paggalaw ng slider, at pagbabago ng text ay bumubuo ng hiwalay na Intent. Para sa mga field ng input ito ay labis — sa mga ganitong kaso, katanggap-tanggap na gumamit ng Binding na may one-way na daloy sa loob ng form (lokal na estado), at ang global na Intent ay ipadala lamang sa mga makabuluhang aksyon (submit, navigation).

Pangatlong pagkakamali — kawalan ng pangangasiwa sa pagkansela ng mga epekto. Kung ang gumagamit ay umalis sa screen at ang coroutine o Task ay patuloy na tumatakbo, ang resulta ay maaaring mailapat sa isang na-destroy na View. Sa Android gamitin ang viewModelScope.cancel() o takeWhileActive(); sa TCA ang mga epekto ay awtomatikong kinansela kapag ang Store ay nawasak. Ayon sa Google Issue Tracker (2024), ang mga pagtagas mula sa hindi natapos na mga coroutine ay nasa top-5 na dahilan ng pag-crash ng mga application ng Compose.

Mga Madalas Itanong

Paano naiiba ang UDF sa MVI?

Ang MVI (Model-View-Intent) — isang partikular na kaso ng UDF na may tatlong mandatoryong elemento: Intent (intensyon), Model (estado), View (display). Ang pangunahing pagkakaiba ay na sa MVI ang bawat estado ng screen ay inilalarawan ng isang hindi nababagong istruktura (Sealed class), at ang View ay isang purong function mula sa Model patungo sa UI. Ang UDF ay isang mas malawak na termino na naglalarawan ng anumang one-way na daloy, kabilang ang Redux at Elm. Sa dokumentasyon ng Google ang terminong UDF ay ginagamit bilang pangkalahatang pangalan, at ang MVI bilang tiyak na pagpapatupad.

Kailan labis ang UDF?

Ang UDF ay labis para sa mga screen na may isang field ng input na walang validation, mga static na pahina, at mga placeholder na screen. Kung ang screen ay walang lohika ng negosyo at ang estado nito ay hindi nakadepende sa mga aksyon ng gumagamit, ang UDF ay nagdaragdag ng hindi kinakailangang code nang walang benepisyo. Para sa mga ganitong senaryo, sapat na ang simpleng one-way binding o @State sa SwiftUI. Ang UDF ay makatwiran kapag ang bilang ng mga posibleng estado ng screen ay lumampas sa 3–4 at/o may mga side effect.

Paano subukan ang UDF?

Dahil ang Reducer ay isang purong function, ang pagsubok nito ay bumababa sa pagtawag na may iba't ibang kumbinasyon ng State at Intent at pagsusuri ng resultang State at Effect. Sa Android gamitin ang Turbine para sa pagsubok ng StateFlow: magpadala ng Intent, suriin ang susunod na emission ng State. Sa TCA mayroong built-in na TestStore na awtomatikong sumusuri kung pagkatapos ng Action ay nagbago lamang ang inaasahang mga field ng State at ang tanging inaasahang Effect ay naisagawa.

Maaari bang pagsamahin ang UDF at Two-Way Binding?

Oo, pinapayagan ang pagsasama at madalas na optimal. Para sa mga field ng input sa loob ng form, gumamit ng lokal na Two-Way Binding (o Binding sa SwiftUI) upang hindi lumikha ng Intent para sa bawat pagpindot ng key. Kapag nag-submit ng form, magpadala ng isang Intent na may nakolektang data, na pinoproseso ng Reducer. Ang hybrid na diskarte na ito — global na UDF na may lokal na Two-Way Binding — ay ginagamit sa 70% ng mga komersyal na SwiftUI application (data ng Swift Community Survey 2024).

Ano ang pagkakatulad ng UDF, Redux, at Elm?

Lahat ng tatlong pattern ay nagpapatupad ng one-way na daloy ng datos na may nag-iisang pinagmulan ng katotohanan. Elm (2012) — isang functional na wika na unang nagpakilala ng purong siklo na Model → View → Update. Inangkop ng Redux (2015) ang Elm para sa JavaScript na may konsepto ng Store, Reducer, at Action. Ang UDF ay isang generalisasyon ng mga ideyang ito para sa mobile development. Lahat ng tatlong diskarte ay ginagarantiyahan ang predictability ng mga pagbabago sa pamamagitan ng atomic (hindi mahahati) na mga update ng estado.

Buod

  • Unidirectional Data Flow (UDF) — pattern na may one-way na siklo State → View → Intent → Reducer, na ginagarantiyahan ang predictability ng mga pagbabago ng estado.
  • Sa Android ang UDF ay ipinatutupad sa pamamagitan ng ViewModel + StateFlow + sealed class Intent, sa iOS — sa pamamagitan ng TCA (Reducer + Store) o native na Observable.
  • Inirerekomenda ng Google ang UDF bilang pangunahing arkitektura para sa Jetpack Compose, simula 2023.
  • Reducer — purong function na walang side effect; lahat ng kahilingan sa network at operasyon ng database ay inililipat sa layer ng Effect.
  • Inaalis ng UDF ang problema ng walang katapusang mga loop ng Two-Way Binding sa pamamagitan ng malinaw na pagproseso ng Intent at Single Source of Truth.
  • Mga pangunahing panganib — mga side effect sa Reducer, masyadong detalyadong Intent, at hindi natapos na mga coroutine.
  • Ang hybrid na diskarte (lokal na Two-Way Binding sa form + global na UDF) ay optimal para sa karamihan ng mga komersyal na application.

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din