Unidirectional Data Flow — birtərəfli məlumat axını, məlumatların əks əlaqə olmadan State → View → Intent → Reducer → State qapalı dövrü boyunca hərəkət etdiyi arxitektura nümunəsidir. İkitərəfli bağlamadan fərqli olaraq, UDF vəziyyət dəyişikliyinin yalnız aşkar hərəkətlər (Intent/Event) vasitəsilə baş verməsini təmin edir, bu da məlumat axınını proqnozlaşdırıla bilən və izlənilən edir. Google I/O 2024 məlumatına görə, UDF orta və yüksək mürəkkəblikli biznes məntiqi olan Jetpack Compose və SwiftUI tətbiqləri üçün tövsiyə olunan arxitekturadır.
Əsas məqamlar
Unidirectional Data Flow (UDF) — məlumatların qapalı dövr boyunca bir istiqamətdə hərəkət etdiyi, View və Model arasında əks əlaqəni istisna edən arxitektura nümunəsidir. UI-də dəyişikliyin dərhal modeli yenilədiyi İkitərəfli Bağlamadan fərqli olaraq, UDF hər vəziyyət dəyişikliyi üçün aşkar hərəkət (Intent, Event, Action) tələb edir. Bu, məlumat axınını tamamilə proqnozlaşdırıla bilən edir: istənilən anda hansı hərəkətin cari vəziyyətə gətirib çıxardığını müəyyən etmək olar.
UDF konsepsiyası veb-freymvorklardan — Redux (JavaScript, 2015) və Elm (2012) — gəlmiş və mobil inkişaf üçün uyğunlaşdırılmışdır. Google I/O 2024 məlumatına görə, UDF Jetpack Compose üçün tövsiyə olunan arxitekturaya çevrilmiş, klassik MVVM-i LiveData ilə əvəz etmişdir. iOS-da oxşar yanaşma Point-Free şirkətinin The Composable Architecture (TCA) ilə həyata keçirilir və Swift Community (2024) sorğusuna görə iOS tərtibatçılarının 15%-dən çoxu bundan istifadə edir.
UDF-nin əsas üstünlüyü — Single Source of Truth (SSOT): tətbiqin bütün vəziyyəti bir yerdə saxlanılır və ciddi müəyyən edilmiş əməliyyatlar vasitəsilə dəyişdirilir. Bu, debug, test və səhvlərin bərpasını asanlaşdırır, çünki hər vəziyyət dəyişikliyi qeydə alınır və eyni Intent-ləri təkrar göndərməklə bərpa edilə bilər.
UDF-nin əsas dövrü dörd addımdan ibarətdir: State (cari vəziyyət) View-də göstərilir; istifadəçi hərəkət edir, bu da Intent-ə (niyyət) çevrilir; Intent Reducer-də (təmiz funksiya) işlənir və yeni State yaradır; yeni vəziyyət yenidən çəkilmək üçün View-ə ötürülür. Bu dövr hər istifadəçi və ya sistem hadisəsində təkrarlanır.
Dövrün hər bir elementinin ciddi məsuliyyəti var: State — ekranın müəyyən andakı vəziyyətini təsvir edən dəyişməz (immutable) obyekt; View — State-i göstərən funksiya; Intent — istifadəçinin niyyətini təsvir edən dəyər (məsələn, LoginIntent.Submit); Reducer — əlavə təsirləri olmayan, cari State və Intent-i qəbul edib yeni State qaytaran təmiz funksiya. Əlavə təsirlər (şəbəkə sorğuları, verilənlər bazası) ayrıca Middleware və ya Effect qatına çıxarılır.
Google Android Architecture (2024) məqaləsinə görə, Reducer-in təmizliyi əsas tələbdir: əgər Reducer şəbəkə çağırışı və ya verilənlər bazasına yazma ehtiva edirsə, məlumat axınını test etmək və debug etmək mümkün deyil. Bütün əlavə təsirlər Reducer çağırılmazdan əvvəl ViewModel korutini və ya Swift Task-da yerinə yetirilməli, nəticə isə yeni Intent kimi göndərilməlidir.
Android-də UDF tətbiqi üç Jetpack komponentinə əsaslanır: ViewModel həyat dövrünü idarə edir, StateFlow reaktiv vəziyyət axınını təmin edir, Intent (sealed class) bütün mümkün istifadəçi hərəkətlərini təsvir edir. View StateFlow-u Compose-da collectAsState() və ya View sistemində observe() vasitəsilə abunə edir.
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) }
)
}Nümunə Android-də tam UDF dövrünü göstərir: LoginIntent bütün mümkün hərəkətləri təsvir edir (email dəyişmə, şifrə dəyişmə, formanın göndərilməsi), LoginState — dəyişməz vəziyyət, LoginViewModel Intent-ləri emal edir və StateFlow-u yeniləyir, Compose ekranı isə state-i collectAsState() vasitəsilə abunə edir. Hər vəziyyət dəyişikliyi müəyyən bir Intent-in emalının nəticəsidir və bu, məlumat axınını tamamilə şəffaf edir.
iOS-da UDF The Composable Architecture (TCA) Point-Free və ya iOS 17+-dan etibarən yerli Observable nümunəsi vasitəsilə həyata keçirilir. TCA hazır State + Action + Reducer + Store dövrü təmin edir, burada Store yeganə həqiqət mənbəyidir, View isə dəyişiklikləri @Observable və ya ObservableObject vasitəsilə abunə edir.
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("Daxil ol") { viewStore.send(.submit) }
}
}
}
}loginReducer reduktoru — təmiz funksiyadır: şəbəkə sorğularını birbaşa yerinə yetirmir, əvəzində TCA mühiti tərəfindən icra ediləcək Effect qaytarır. Bu, reduktoru təcrid olunmuş şəkildə test etməyə, testlərdə effektləri əvəz etməyə imkan verir. View WithViewStore vasitəsilə Store dəyişikliklərinə abunə olur və send() vasitəsilə Action göndərir. TCA Store məhv edildikdə effektlərin ləğvini avtomatik idarə edir, yaddaş sızmalarının qarşısını alır.
MVVM və UDF tez-tez qarışdırılır, lakin aralarında prinsipial fərq var. MVVM — struktural nümunədir, kodu üç qata (Model, View, ViewModel) ayırır, lakin məlumat axınının istiqamətini müəyyən etmir. UDF — davranış nümunəsidir, məlumatların bu struktur daxilində necə hərəkət etdiyini təsvir edir. LiveData ilə MVVM-də həm ikitərəfli bağlama, həm də birtərəfli axın ola bilər — UDF MVVM-ə Intent emalı üçün ciddi qaydalar əlavə edir.
Android Developers (2024) sənədlərinə görə, Compose üçün tövsiyə olunan arxitektura MVVM daxilində UDF-dir: ViewModel State saxlayır və Intent-ləri emal edir, View State-ə abunə olur və Intent göndərir. DataBinding vasitəsilə İkitərəfli Bağlama ilə klassik MVVM Google tərəfindən yalnız biznes məntiqi olmayan sadə ekranlar üçün tövsiyə olunur. Jetpack Compose üçün əsas ssenari aşkar hadisə emalı ilə UDF-dir.
Müqayisə cədvəli:
| Xüsusiyyət | MVVM (klassik) | MVVM + UDF |
|---|---|---|
| Məlumat axını | Müəyyən edilməyib | Ciddi birtərəfli |
| Vəziyyət dəyişikliyi | Birbaşa setText() ilə | Yalnız Intent → Reducer vasitəsilə |
| Single Source of Truth | Xeyr | Bəli |
| Reducer-in test edilə bilməsi | Aşağı | Yüksək (təmiz funksiya) |
| Google tövsiyəsi | Köhnəlmiş yanaşma | Compose üçün əsas |
Ən geniş yayılmış səhv — Reducer daxilində əlavə təsirlər. MVVM-ə öyrəşmiş tərtibatçılar şəbəkə sorğularını birbaşa Intent handler-inə yerləşdirir, bu da Reducer-i qeyri-təmiz funksiyaya çevirir və test edilə bilməni pozur. Bütün effektlər dəyər (Effect / SideEffect) kimi qaytarılmalı və freymvork infrastrukturu tərəfindən icra edilməlidir. Android-də bunun üçün ViewModel-də korutinlər, TCA-da isə Effect.run istifadə olunur.
İkinci səhv — çox detallaşdırılmış Intent. Hər düymə basması, sürüşdürmə və mətn dəyişikliyi ayrıca Intent yaradır. Giriş sahələri üçün bu həddindən artıqdır — belə hallarda formanın daxilində birtərəfli axın ilə Binding istifadə etmək (lokal vəziyyət), qlobal Intent-i isə yalnız əhəmiyyətli hərəkətlərdə (göndərmə, naviqasiya) göndərmək məqbuldur.
Üçüncü səhv — effektlərin ləğvinin olmaması. İstifadəçi ekranı tərk edərsə, korutin və ya Task işləməyə davam edərsə, nəticə artıq məhv edilmiş View-ə tətbiq edilə bilər. Android-də viewModelScope.cancel() və ya takeWhileActive() istifadə edin; TCA-da effektlər Store məhv edildikdə avtomatik ləğv edilir. Google Issue Tracker (2024) məlumatına görə, tamamlanmamış korutinlərdən sızmalar Compose tətbiqlərinin çökmə səbəblərinin top-5-ə daxildir.
Tez-tez verilən suallar
MVI (Model-View-Intent) — üç məcburi elementi olan UDF-nin xüsusi halıdır: Intent (niyyət), Model (vəziyyət), View (göstərim). Əsas fərq ondadır ki, MVI-də ekranın hər bir vəziyyəti bir dəyişməz struktur (Sealed class) ilə təsvir edilir, View isə Model-dən UI-ə təmiz funksiyadır. UDF — Redux və Elm də daxil olmaqla hər hansı birtərəfli axını təsvir edən daha geniş termindir. Google sənədlərində UDF termini ümumi ad kimi, MVI isə konkret tətbiq kimi istifadə olunur.
UDF validasiyasız bir giriş sahəsi olan ekranlar, statik səhifələr və placeholder ekranlar üçün lazımsızdır. Ekranın biznes məntiqi yoxdursa və vəziyyəti istifadəçi hərəkətlərindən asılı deyilsə, UDF faydasız kod əlavə edir. Belə ssenarilər üçün birtərəfli bağlama və ya SwiftUI-də sadə @State kifayətdir. UDF ekranın mümkün vəziyyətlərinin sayı 3–4-ü keçdikdə və/yaxud əlavə təsirlər olduqda əsaslandırılır.
Reducer təmiz funksiya olduğundan, onun test edilməsi State və Intent-in müxtəlif kombinasiyaları ilə çağırmağa və nəticədəki State və Effect-i yoxlamağa gəlir. Android-də StateFlow-u test etmək üçün Turbine istifadə edin: Intent göndərin, növbəti State emissiyasını yoxlayın. TCA-da daxili TestStore var ki, Action-dan sonra yalnız gözlənilən State sahələrinin dəyişdiyini və yalnız gözlənilən Effect-lərin icra edildiyini avtomatik yoxlayır.
Bəli, birləşdirmək mümkündür və çox vaxt optimaldır. Forma daxilində giriş sahələri üçün hər düymə basmasına Intent yaratmamaq üçün lokal Two-Way Binding (və ya SwiftUI-də Binding) istifadə edin. Forma göndərilərkən Reducer tərəfindən emal ediləcək toplanmış məlumatlarla bir Intent göndərin. Bu hibrid yanaşma — lokal Two-Way Binding ilə qlobal UDF — kommersiya SwiftUI tətbiqlərinin 70%-də istifadə olunur (Swift Community Survey 2024).
Hər üç nümunə tək həqiqət mənbəyi ilə birtərəfli məlumat axını həyata keçirir. Elm (2012) — təmiz Model → View → Update dövrünü ilk təqdim edən funksional dildir. Redux (2015) Elm-i Store, Reducer və Action konsepti ilə JavaScript-ə uyğunlaşdırdı. UDF bu ideyaların mobil inkişaf üçün ümumiləşdirilməsidir. Hər üç yanaşma vəziyyətin bölünməz (atomik) yeniləmələri vasitəsilə proqnozlaşdırıla bilən dəyişiklikləri təmin edir.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun