سمجھیں کہ یک طرفہ ڈیٹا فلو کیا ہے — ایک یک سمتی ڈیٹا اسٹریم، ایک آرکیٹیکچرل پیٹرن جہاں ڈیٹا فیڈ بیک لوپس کے بغیر ایک بند لوپ State → View → Intent → Reducer → State میں حرکت کرتا ہے۔ دو طرفہ بائنڈنگ کے برعکس، UDF ضمانت دیتا ہے کہ حالت میں تبدیلیاں صرف واضح اقدامات (Intent/Event) کے ذریعے ہوتی ہیں، جو ڈیٹا کے بہاؤ کو پیش قیاسی اور قابلِ تعاقب بناتا ہے۔ Google I/O 2024 کے مطابق، UDF درمیانی اور اعلی پیچیدگی کے کاروباری منطق والی Jetpack Compose اور SwiftUI ایپلی کیشنز کے لیے تجویز کردہ آرکیٹیکچر ہے۔
کلیدی نکات
یک طرفہ ڈیٹا فلو (UDF) ایک آرکیٹیکچرل پیٹرن ہے جہاں ڈیٹا ایک بند لوپ میں ایک سمت میں حرکت کرتا ہے، View اور Model کے درمیان فیڈ بیک لوپس کو ختم کرتا ہے۔ دو طرفہ بائنڈنگ کے برعکس، جہاں UI میں تبدیلی فوری طور پر ماڈل کو اپ ڈیٹ کرتی ہے، UDF کو ہر حالت کی تبدیلی کے لیے ایک واضح اقدام (Intent، Event، Action) کی ضرورت ہوتی ہے۔ یہ ڈیٹا کے بہاؤ کو مکمل طور پر پیش قیاسی بناتا ہے: کسی بھی وقت، یہ تعین کیا جا سکتا ہے کہ کس اقدام نے موجودہ حالت کی طرف لے جایا۔
UDF کا تصور ویب فریم ورکس — Redux (JavaScript، 2015) اور Elm (2012) — سے آیا ہے اور اسے موبائل ڈویلپمنٹ کے لیے ڈھال لیا گیا ہے۔ Google I/O 2024 کے مطابق، UDF Jetpack Compose کے لیے تجویز کردہ آرکیٹیکچر بن گیا ہے، جو LiveData کے ساتھ کلاسک MVVM کی جگہ لے رہا ہے۔ iOS پر، Point-Free کی The Composable Architecture (TCA) میں اسی طرح کا نقطہ نظر لاگو کیا گیا ہے، جو Swift Community Survey (2024) کے مطابق 15% سے زیادہ iOS ڈویلپرز استعمال کرتے ہیں۔
UDF کا بنیادی فائدہ ایک ہی سچائی کا ذریعہ (SSOT) ہے: تمام ایپلیکیشن حالت ایک جگہ محفوظ ہوتی ہے اور سختی سے متعین کارروائیوں کے ذریعے تبدیل ہوتی ہے۔ یہ ڈیبگنگ، ٹیسٹنگ اور بگ کی تولید کو آسان بناتا ہے، کیونکہ ہر حالت کی تبدیلی لاگ کی جاتی ہے اور اسی Intents کو دوبارہ بھیج کر دوبارہ تیار کی جا سکتی ہے۔
بنیادی UDF چکر چار مراحل پر مشتمل ہے: State (موجودہ حالت) View میں رینڈر ہوتی ہے؛ صارف ایک عمل انجام دیتا ہے جو Intent بن جاتا ہے؛ Intent کو Reducer (خالص فنکشن) میں پروسیس کیا جاتا ہے، جو ایک نیا State بناتا ہے؛ نئی حالت دوبارہ رینڈرنگ کے لیے View کو بھیجی جاتی ہے۔ یہ چکر ہر صارف یا سسٹم ایونٹ پر دہرایا جاتا ہے۔
چکر کے ہر عنصر کی سخت ذمہ داری ہے: State — ایک ناقابل تبدیل آبجیکٹ جو کسی مخصوص لمحے میں اسکرین کی حالت بیان کرتا ہے؛ View — ایک فنکشن جو State کو رینڈر کرتا ہے؛ Intent — ایک قدر جو صارف کے ارادے کو بیان کرتی ہے (مثلاً، LoginIntent.Submit)؛ Reducer — بغیر ضمنی اثرات کے ایک خالص فنکشن، جو موجودہ State اور Intent لیتا ہے اور ایک نیا State لوٹاتا ہے۔ ضمنی اثرات (نیٹ ورک کی درخواستیں، ڈیٹا بیس آپریشنز) کو ایک علیحدہ Middleware یا Effect پرت میں منتقل کیا جاتا ہے۔
Google Android Architecture مضمون (2024) کے مطابق، Reducer کی پاکیزگی ایک اہم ضرورت ہے: اگر Reducer میں نیٹ ورک کال یا ڈیٹا بیس رائٹنگ شامل ہے، تو ڈیٹا کے بہاؤ کی جانچ اور ڈیبگنگ ناممکن ہو جاتی ہے۔ تمام ضمنی اثرات Reducer کو کال کرنے سے پہلے ViewModel coroutine یا Swift Task میں انجام دینے چاہئیں، اور نتیجہ ایک نئے Intent کے طور پر بھیجنا چاہیے۔
Android میں، UDF کا نفاذ تین Jetpack اجزاء پر مبنی ہے: ViewModel لائف سائیکل کا انتظام کرتا ہے، StateFlow ایک رد عمل والی حالت کا اسٹریم فراہم کرتا ہے، Intent (sealed class) تمام ممکنہ صارف اقدامات کو بیان کرتا ہے۔ View Compose میں collectAsState() یا View سسٹم میں observe() کے ذریعے StateFlow کو سبسکرائب کرتی ہے۔
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) }
)
}مثال Android میں مکمل UDF چکر کو ظاہر کرتی ہے: LoginIntent تمام ممکنہ اقدامات (ای میل تبدیلی، پاس ورڈ تبدیلی، فارم جمع کرانا) کو بیان کرتا ہے، LoginState ناقابل تبدیل حالت ہے، LoginViewModel Intents کو پروسیس کرتا ہے اور StateFlow کو اپ ڈیٹ کرتا ہے، اور Compose اسکرین collectAsState() کے ذریعے حالت کو سبسکرائب کرتی ہے۔ ہر حالت کی تبدیلی ایک مخصوص Intent کی پروسیسنگ کا نتیجہ ہے، جو ڈیٹا کے بہاؤ کو مکمل طور پر شفاف بناتا ہے۔
iOS پر، UDF کو The Composable Architecture (TCA) (Point-Free) یا iOS 17+ کے ساتھ مقامی Observable پیٹرن کے ذریعے لاگو کیا جاتا ہے۔ TCA State + Action + Reducer + Store کا ایک تیار چکر فراہم کرتا ہے، جہاں Store سچائی کا واحد ذریعہ ہے، اور View @Observable یا ObservableObject کے ذریعے تبدیلیوں کو سبسکرائب کرتی ہے۔
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("لاگ ان کریں") { viewStore.send(.submit) }
}
}
}
}loginReducer ایک خالص فنکشن ہے: یہ براہ راست نیٹ ورک کی درخواستیں نہیں کرتا بلکہ ایک Effect لوٹاتا ہے جو TCA رن ٹائم کے ذریعے انجام دیا جائے گا۔ یہ ٹیسٹوں میں اثرات کو تبدیل کرکے ریڈیوسر کو الگ تھلگ جانچنے کی اجازت دیتا ہے۔ View WithViewStore کے ذریعے Store کی تبدیلیوں کو سبسکرائب کرتی ہے اور send() کے ذریعے Actions بھیجتی ہے۔ جب Store تباہ ہو جاتا ہے تو TCA خود بخود اثرات کو منسوخ کرتا ہے، میموری لیک کو روکتا ہے۔
MVVM اور UDF اکثر الجھ جاتے ہیں، لیکن ان کے درمیان ایک بنیادی فرق ہے۔ MVVM ایک ساختی پیٹرن ہے جو کوڈ کو تین تہوں (Model، View، ViewModel) میں الگ کرتا ہے لیکن ڈیٹا کے بہاؤ کی سمت کی وضاحت نہیں کرتا۔ UDF ایک رویوں کا پیٹرن ہے جو بیان کرتا ہے کہ ڈیٹا اس ڈھانچے کے اندر کیسے حرکت کرتا ہے۔ LiveData کے ساتھ MVVM میں، دو طرفہ بائنڈنگ اور یک طرفہ بہاؤ دونوں ممکن ہیں — UDF MVVM میں سخت Intent پروسیسنگ کے قواعد شامل کرتا ہے۔
Android Developers دستاویزات (2024) کے مطابق، Compose کے لیے تجویز کردہ آرکیٹیکچر MVVM کے اندر UDF ہے: ViewModel State کو ذخیرہ کرتا ہے اور Intents کو پروسیس کرتا ہے، View State کو سبسکرائب کرتی ہے اور Intents بھیجتی ہے۔ Google ڈیٹا بائنڈنگ کے ذریعے دو طرفہ بائنڈنگ کے ساتھ کلاسک MVVM کو صرف کاروباری منطق کے بغیر سادہ اسکرینوں کے لیے تجویز کرتا ہے۔ Jetpack Compose کے لیے، بنیادی منظر نامہ واضح ایونٹ ہینڈلنگ کے ساتھ UDF ہے۔
موازنہ جدول:
| خصوصیت | MVVM (کلاسک) | MVVM + UDF |
|---|---|---|
| ڈیٹا کا بہاؤ | متعین نہیں | سختی سے یک طرفہ |
| حالت میں تبدیلی | براہ راست setText() کے ذریعے | صرف Intent → Reducer کے ذریعے |
| سچائی کا واحد ذریعہ | نہیں | ہاں |
| Reducer کی جانچ پڑتال | کم | اعلی (خالص فنکشن) |
| Google کی سفارش | پرانا طریقہ | Compose کے لیے بنیادی |
سب سے عام غلطی Reducer کے اندر ضمنی اثرات ہیں۔ MVVM کے عادی ڈویلپرز نیٹ ورک کی درخواستوں کو براہ راست Intent ہینڈلر میں رکھتے ہیں، جو Reducer کو ناپاک بناتا ہے اور جانچ پڑتال کو توڑ دیتا ہے۔ تمام اثرات کو ایک قدر (Effect / SideEffect) کے طور پر واپس کیا جانا چاہیے اور فریم ورک کے بنیادی ڈھانچے کے ذریعے انجام دیا جانا چاہیے۔ Android میں، اس کے لیے ViewModel میں coroutines استعمال ہوتے ہیں؛ TCA میں — Effect.run۔
دوسری غلطی حد سے زیادہ تفصیلی Intents ہیں۔ ہر کی اسٹروک، سلائیڈر موومنٹ اور ٹیکسٹ تبدیلی ایک علیحدہ Intent پیدا کرتی ہے۔ ان پٹ فیلڈز کے لیے یہ ضرورت سے زیادہ ہے — ایسے معاملات میں، فارم کے اندر یک طرفہ بہاؤ کے ساتھ بائنڈنگ (مقامی حالت) استعمال کرنا قابل قبول ہے، اور صرف اہم اقدامات (جمع کرانا، نیویگیشن) کے لیے ایک عالمی Intent بھیجنا چاہیے۔
تیسری غلطی اثرات کی منسوخی کے انتظام کی کمی ہے۔ اگر صارف اسکرین چھوڑ دیتا ہے جبکہ coroutine یا Task ابھی بھی عمل میں ہے، نتیجہ پہلے سے تباہ شدہ View پر لاگو ہو سکتا ہے۔ Android میں، viewModelScope.cancel() یا takeWhileActive() استعمال کریں؛ TCA میں، Store تباہ ہونے پر اثرات خود بخود منسوخ ہو جاتے ہیں۔ Google Issue Tracker (2024) کے مطابق، نامکمل coroutines سے لیک Compose ایپلی کیشنز میں کریش کی سرفہرست 5 وجوہات میں شامل ہیں۔
اکثر پوچھے گئے سوالات
MVI (Model-View-Intent) تین لازمی عناصر کے ساتھ UDF کی ایک مخصوص صورت ہے: Intent (ارادہ)، Model (حالت)، View (نمائش)۔ بنیادی فرق یہ ہے کہ MVI میں، ہر اسکرین حالت کو ایک ہی ناقابل تبدیل ساخت (Sealed class) سے بیان کیا جاتا ہے، اور View Model سے UI تک ایک خالص فنکشن ہے۔ UDF ایک وسیع تر اصطلاح ہے جو Redux اور Elm سمیت کسی بھی یک طرفہ بہاؤ کو بیان کرتی ہے۔ Google کی دستاویزات میں، UDF کی اصطلاح عام نام کے طور پر استعمال ہوتی ہے، جبکہ MVI ایک مخصوص نفاذ ہے۔
UDF تصدیق کے بغیر ایک ہی ان پٹ فیلڈ والی اسکرینوں، جامد صفحات اور پلیس ہولڈر اسکرینوں کے لیے ضرورت سے زیادہ ہے۔ اگر کسی اسکرین میں کاروباری منطق نہیں ہے اور اس کی حالت صارف کے اقدامات پر منحصر نہیں ہے، تو UDF فائدے کے بغیر غیر ضروری کوڈ شامل کرتا ہے۔ ایسے منظرناموں کے لیے، یک طرفہ بائنڈنگ یا SwiftUI میں سادہ @State کافی ہے۔ UDF اس وقت جائز ہے جب ممکنہ اسکرین حالتوں کی تعداد 3-4 سے زیادہ ہو اور/یا ضمنی اثرات موجود ہوں۔
چونکہ Reducer ایک خالص فنکشن ہے، اس کی جانچ State اور Intent کے مختلف امتزاج کے ساتھ اسے کال کرنے اور نتیجے میں آنے والے State اور Effect کو جانچنے پر مشتمل ہے۔ Android میں، StateFlow کی جانچ کے لیے Turbine استعمال کریں: ایک Intent بھیجیں، اگلے State کے اخراج کی جانچ کریں۔ TCA میں، ایک بلٹ ان TestStore ہے جو خود بخود تصدیق کرتا ہے کہ Action کے بعد صرف متوقع State فیلڈز تبدیل ہوئیں اور صرف متوقع Effects انجام پائے۔
ہاں، ان کو ملانا قابل قبول ہے اور اکثر بہترین ہے۔ فارم کے اندر ان پٹ فیلڈز کے لیے، ہر کی اسٹروک پر Intent بنانے سے بچنے کے لیے مقامی دو طرفہ بائنڈنگ (یا SwiftUI میں Binding) استعمال کریں۔ فارم جمع کراتے وقت، جمع کردہ ڈیٹا کے ساتھ ایک ہی Intent بھیجیں، جسے Reducer پروسیس کرتا ہے۔ یہ ہائبرڈ طریقہ — مقامی دو طرفہ بائنڈنگ کے ساتھ عالمی UDF — 70% تجارتی SwiftUI ایپلی کیشنز میں استعمال ہوتا ہے (Swift Community Survey 2024 ڈیٹا)۔
تینوں پیٹرن ایک ہی سچائی کے ذریعہ کے ساتھ یک طرفہ ڈیٹا فلو کو لاگو کرتے ہیں۔ Elm (2012) — ایک فعال زبان — نے پہلی بار خالص Model → View → Update چکر متعارف کرایا۔ Redux (2015) نے Store، Reducer اور Action کے تصورات کے ساتھ Elm کو JavaScript کے لیے ڈھال لیا۔ UDF موبائل ڈویلپمنٹ کے لیے ان خیالات کا ایک عمومی اطلاق ہے۔ تینوں طریقے جوہری حالت کی تازہ کاریوں کے ذریعے تبدیلیوں کی پیش قیاسی کی ضمانت دیتے ہیں۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں