StateFlow — Kotlin Coroutines لائبریری سے ری ایکٹو اسٹیٹ کنٹینر، جو StateFlow<T> — Flow کا ذیلی قسم ہے جو ہمیشہ موجودہ قدر رکھتا ہے اور نئے سبسکرائبرز کو بھیجتا ہے۔ StateFlow کے جوہر کی وضاحت: LiveData کے برعکس، StateFlow Android فریم ورک سے منسلک نہیں ہے اور کسی بھی Kotlin پلیٹ فارم پر کام کرتا ہے۔ Google (Android Developers, 2025) کے مطابق، StateFlow کو خاص طور پر Jetpack Compose کے ساتھ MVVM فن تعمیر میں، خالص Kotlin پر نئے پروجیکٹس کے لیے LiveData کے اہم متبادل کے طور پر تجویز کیا گیا ہے۔
اہم نکات
collectAsState() یا View میں repeatOnLifecycle() استعمال ہوتا ہے۔StateFlow — kotlinx.coroutines.flow لائبریری سے ایک انٹرفیس ہے جو مقررہ replay = 1 پیرامیٹر کے ساتھ MutableSharedFlow کو بڑھاتا ہے۔ اس کا مطلب ہے کہ StateFlow ہمیشہ بھیجی گئی آخری قدر کو یاد رکھتا ہے اور فوری طور پر ہر نئے سبسکرائبر کو دوبارہ پیش کرتا ہے۔ LiveData کے برعکس، StateFlow Kotlin Coroutines معیاری لائبریری کا حصہ ہے اور اس کا Android پر کوئی انحصار نہیں ہے۔
تصوراتی طور پر StateFlow ایک ری ایکٹو پراپرٹی ہے: آپ اس کی موجودہ قدر کو .value کے ذریعے پڑھتے ہیں اور تبدیلیوں کو .collect() کے ذریعے سبسکرائب کرتے ہیں۔ اس ماڈل کو "ہاٹ" فلو (hot flow) کہا جاتا ہے — ڈیٹا ماخذ سبسکرائبرز کی موجودگی سے آزادانہ طور پر فعال ہے، "کولڈ" (cold) فلو کے برعکس جو flow { } کے ذریعے بنائے جاتے ہیں اور سبسکرائبر کے ظاہر ہونے پر شروع ہوتے ہیں۔
StateFlow کو kotlinx.coroutines 1.3.7 (دسمبر 2020) میں مستحکم کیا گیا تھا اور Google I/O 2021 سے Google نے LiveData کے متبادل کے طور پر تجویز کیا ہے۔ جنوری 2025 تک، JetBrains سروے کے مطابق، نئے Kotlin Android پروجیکٹس کا 56% بنیادی ری ایکٹو کنٹینر کے طور پر StateFlow استعمال کرتا ہے۔
StateFlow اور LiveData کے درمیان انتخاب پروجیکٹ کے فن تعمیر، ٹیکنالوجی اسٹیک اور پلیٹ فارم کی آزادی کی ضروریات پر منحصر ہے۔ ذیل میں چھ اہم معیاروں کے مطابق موازنہ ہے۔
| معیار | StateFlow | LiveData |
|---|---|---|
| پلیٹ فارم | Kotlin Multiplatform (Android, iOS, سرور) | صرف Android |
| Lifecycle-aware | نہیں — repeatOnLifecycle() ضروری | ہاں — بلٹ ان کنکشن |
| کوروٹینز | مکمل سپورٹ (map, filter, combine) | liveData { } builder کے ذریعے |
| Null-سیفٹی | ہاں — kotlinx.serialization کے ذریعے سیریلائزڈ | ہاں — LiveData<String?> nullability کے ذریعے |
| Conflation | Conflated — درمیانی قدروں کو چھوڑتا ہے | صرف postValue() کے ذریعے |
| ٹیسٹنگ | runTest + Turbine یا بلٹ ان آپریٹرز | InstantTaskExecutorRule + observeForever |
StateFlow کو View پرت میں واضح سبسکرپشن مینجمنٹ کی ضرورت ہے: Fragment/Activity میں سبسکرپشن repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } } کے ذریعے کی جاتی ہے۔ یہ LiveData کی خودکار سبسکرپشن سے زیادہ کنٹرول دیتا ہے، لیکن بوائلر پلیٹ کوڈ شامل کرتا ہے۔ Jetpack Compose میں سبسکرپشن val state by viewModel.uiState.collectAsState() سے آسان ہو جاتی ہے۔
Google کی سفارش (Android Developers, 2025): Kotlin کے ساتھ نئے پروجیکٹس کے لیے، خاص طور پر Compose کے ساتھ کام کرتے ہوئے StateFlow استعمال کریں۔ LiveData کو ان کے لیے چھوڑیں: (1) Java کوڈ، (2) Java کے ساتھ مطابقت کی ضرورت والی لائبریریاں، (3) Room DAO (DAO واپسی کی قسم کے طور پر LiveData اب بھی مقبول ہے)۔
MutableStateFlow — تحریر کے لیے کھلی value property کے ساتھ StateFlow کا قابل تبدیلی ورژن ہے۔ MutableLiveData کی طرح، MutableStateFlow ViewModel کے اندر استعمال ہوتا ہے اور بیرونی سبسکرائبرز کے لیے StateFlow (صرف پڑھنے کے لیے) کے طور پر شائع کیا جاتا ہے۔
class TimerViewModel : ViewModel() {
private val _seconds = MutableStateFlow(0)
val seconds: StateFlow<Int> get() = _seconds
private val _isRunning = MutableStateFlow(false)
val isRunning: StateFlow<Boolean> get() = _isRunning
private var job: Job? = null
fun start() {
if (_isRunning.value) return
_isRunning.value = true
job = viewModelScope.launch {
while (_isRunning.value) {
delay(1000)
_seconds.value++
}
}
}
fun stop() {
_isRunning.value = false
job?.cancel()
}
}
MutableStateFlow کی خصوصیات: (1) قدر ہمیشہ null نہیں ہوتی — کنسٹرکٹر کے ذریعے ابتدا کی ضرورت ہے؛ (2) پرانی اور نئی قدروں کا equals() سے موازنہ — اگر نئی قدر پرانی کے برابر ہے تو سبسکرائبرز کو مطلع نہیں کیا جاتا؛ (3) value میں تحریر کسی بھی تھریڈ سے ممکن ہے، لیکن کال کرنے والے تھریڈ کو صرف CAS آپریشن کے مختصر وقت کے لیے بلاک کرتا ہے۔ Kotlin Coroutines دستاویزات کے مطابق، equals() کے ذریعے موازنہ LiveData کے مقابلے میں غیر ضروری اطلاعات کی تعداد کو 90% تک کم کرتا ہے — یہ زیادہ اپ ڈیٹ فریکوئنسی پر کارکردگی میں اضافہ فراہم کرتا ہے۔
ViewModel میں StateFlow استعمال کرتے ہوئے ان اصولوں پر عمل کریں: (1) ViewModel کے اندر MutableStateFlow کو private موڈیفائر کے ساتھ استعمال کریں؛ (2) صرف پڑھنے کے لیے StateFlow کو get() کے ذریعے شائع کریں؛ (3) پیچیدہ اسکرینوں کے لیے بطور اسٹیٹ sealed class استعمال کریں؛ (4) موجودہ قدر کے برابر قدر بھیجنے سے گریز کریں (StateFlow یہ خودکار طور پر کرتا ہے)۔
// اسکرین اسٹیٹ کی تجویز کردہ ساخت
sealed interface ProfileState {
data object Loading : ProfileState
data class Success(
val name: String,
val email: String,
val avatarUrl: String
) : ProfileState
data class Error(val message: String) : ProfileState
}
class ProfileViewModel : ViewModel() {
private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
val state: StateFlow<ProfileState> get() = _state
fun loadProfile(userId: String) {
viewModelScope.launch {
_state.value = ProfileState.Loading
try {
val profile = repository.getProfile(userId)
_state.value = ProfileState.Success(
name = profile.name,
email = profile.email,
avatarUrl = profile.avatarUrl
)
} catch (e: Exception) {
_state.value = ProfileState.Error(e.message ?: "Unknown error")
}
}
}
}
واحد اسٹیٹ قسم کے طور پر sealed class کا استعمال Google کی تجویز کردہ نقطہ نظر ہے (UDF — Unidirectional Data Flow)۔ یہ ضمانت دیتا ہے کہ UI ہمیشہ مستقل اسٹیٹ میں ہے: Loading, Success یا Error، لیکن ایک ساتھ نہیں۔ IT Sectr میں ہم 2022 میں تمام اسکرینوں کے لیے StateFlow + sealed class پر منتقل ہوئے — اس نے پیش قیاسی اسٹیٹس کی بدولت ViewModel کی جانچ کو 40% آسان بنا دیا۔
stateIn() — کولڈ Flow کو ہاٹ StateFlow میں تبدیل کرنے والا آپریٹر۔ اسے CoroutineScope (جہاں اندرونی کوروٹین شروع ہوتی ہے) اور SharingStarted حکمت عملی کی وضاحت کی ضرورت ہے۔ درست SharingStarted کا انتخاب StateFlow کی کارکردگی اور لائف سائیکل کو اہم طریقے سے متاثر کرتا ہے۔
// SharingStarted کی تین حکمت عملیاں:
// 1. SharingStarted.Eagerly — فوری طور پر شروع ہوتا ہے، رکے بغیر
val eagerFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = 0
)
// 2. SharingStarted.Lazily — پہلے سبسکرائبر پر شروع ہوتا ہے، رکے بغیر
val lazyFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = 0
)
// 3. SharingStarted.WhileSubscribed() — سبسکرائبرز کے دوران شروع ہوتا ہے،
// آخری سبسکرائبر کے جانے کے بعد stopTimeoutMillis (پہلے سے طے شدہ 0) کے ذریعے رک جاتا ہے
val whileSubscribedFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
initialValue = 0
)
WhileSubscribed(5000) — ViewModel کے لیے بہترین حکمت عملی: آخری سبسکرائبر کے جانے کے بعد اندرونی کوروٹین مزید 5 سیکنڈ تک کام جاری رکھتی ہے۔ اگر صارف اس دوران اسکرین پر واپس آتا ہے تو سبسکرپشن فلو کو دوبارہ شروع کیے بغیر بحال ہو جاتی ہے۔ ٹائم آؤٹ اسکرینوں کے درمیان تیزی سے سوئچ کرنے پر بار بار ری اسٹارٹ کو روکتا ہے۔ Google ٹیسٹ (Android Performance, 2024) کے مطابق، 5 سیکنڈ ٹائم آؤٹ والا WhileSubscribed CPU استعمال کو Eagerly کے مقابلے میں 25% کم کرتا ہے۔
سرچ کوئری، نتائج اور لوڈنگ اسٹیٹ کے ساتھ مکمل سرچ اسکرین۔ ViewModel Compose کے ساتھ ری ایکٹو مواصلات کے لیے sealed class UIState اور StateFlow استعمال کرتا ہے۔
sealed interface SearchUiState {
data object Empty : SearchUiState
data object Loading : SearchUiState
data class Results(val items: List<Product>) : SearchUiState
data class Error(val message: String) : SearchUiState
}
class SearchViewModel constructor(
private val repository: ProductRepository
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> get() = _searchQuery
private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
val uiState: StateFlow<SearchUiState> get() = _uiState
init {
viewModelScope.launch {
_searchQuery
.debounce(300)
.filter { it.length >= 3 }
.flatMapLatest { query ->
_uiState.value = SearchUiState.Loading
repository.searchProducts(query)
}
.collect { products ->
_uiState.value = SearchUiState.Results(products)
}
}
}
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
}
// Compose میں:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
// ... UI جو Loading, Results, Error اسٹیٹس پر رد عمل ظاہر کرتا ہے
}
Room (ورژن 2.4.0 سے) DAO سے Flow واپسی کو سپورٹ کرتا ہے۔ متعدد Flow کو combine کے ذریعے یکجا کرنا پیچیدہ اسکرینوں کے لیے ایک طاقتور پیٹرن ہے۔
@Dao
interface OrderDao {
@Query("SELECT * FROM orders WHERE status = :status")
fun getOrdersByStatus(status: String): Flow<List<Order>>
}
class OrderViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).orderDao()
val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val summary: StateFlow<OrderSummary> = combine(
dao.getOrdersByStatus("active"),
dao.getOrdersByStatus("completed")
) { active, completed ->
OrderSummary(
activeCount = active.size,
completedCount = completed.size,
totalAmount = (active + completed).sumOf { it.amount }
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}
Room خود بخود orders ٹیبل میں تبدیلیوں کو ٹریک کرتا ہے اور کسی بھی تبدیلی پر ڈیٹا کو دوبارہ حاصل کرتا ہے۔ StateFlow + Room — Room + LiveData جوڑی کا جدید متبادل ہے۔ Google (Android Architecture Guide, 2025) کے مطابق، ڈیٹابیس تبدیلیوں پر ری ایکٹو UI اپ ڈیٹ کی ضرورت والے تمام Kotlin پروجیکٹس کے لیے Flow + StateFlow + Room کی سفارش کی جاتی ہے۔
اکثر پوچھے گئے سوالات
کنفلیشن — ایک طریقہ کار ہے جس میں StateFlow صرف بھیجی گئی آخری قدر کو محفوظ کرتا ہے۔ اگر سبسکرائبر پچھلی قدر پر کارروائی کرنے سے پہلے نئی قدر بھیجی جاتی ہے تو درمیانی قدر ضائع ہو جاتی ہے۔ یہ UI کے لیے اہم ہے: اگر اسٹیٹ Loading → Success → Error میں بدلتا ہے اور UI Success کو پیش کرنے میں کامیاب نہیں ہوتا ہے تو یہ بغیر غیر ضروری رینڈر کے براہ راست Error پر چلا جاتا ہے۔ کنفلیشن Compose میں غیر ضروری دوبارہ ترکیب کو روکنے والی کلیدی Android اصلاح ہے۔
lifecycle-livedata-ktx لائبریری سے liveData.asFlow() ایکسٹینشن فنکشن استعمال کریں، پھر StateFlow میں تبدیل کرنے کے لیے .stateIn() استعمال کریں۔ الٹا کنورژن — stateFlow.asLiveData()۔ کنورژن LiveData سے StateFlow میں منتقلی کے وقت مفید ہے: آپ بتدریج ViewModel کو StateFlow میں منتقل کر سکتے ہیں، پرانے View کی LiveData کے ذریعے سبسکرپشن چھوڑ کر۔
StateFlow کے پاس ہمیشہ ایک قدر ہونی چاہیے — یہ انٹرفیس کا معاہدہ ہے: ہر نیا سبسکرائبر جو ابھی منسلک ہوا ہے بغیر انتظار کے فوری طور پر موجودہ اسٹیٹ حاصل کرتا ہے۔ ابتدائی قدر MutableStateFlow(initialValue) کنسٹرکٹر یا stateIn(initialValue) آپریٹر کو دی جاتی ہے۔ اگر اسٹیٹ موجود نہ ہو سکے تو nullable قسم کے ساتھ MutableStateFlow<T?>(null) استعمال کریں اور UI میں null کو ہینڈل کریں۔
ہاں، StateFlow تھریڈ-سیف ہے: value کی تحریر اور پڑھائی جوہری آپریشنز (CAS) استعمال کرتی ہے۔ تاہم collect() ایک suspend فنکشن ہے اور اسے کوروٹین میں شروع کیا جانا چاہیے۔ اگر emission اور collect مختلف تھریڈز پر عمل میں آتے ہیں تو StateFlow تمام value آپریشنز کے لیے happens-before کی ضمانت دیتا ہے۔ View میں StateFlow جمع کرنے کے لیے lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } } استعمال کریں۔
کوئی حد نہیں ہے، لیکن فی اسکرین 3-5 سے زیادہ علیحدہ StateFlow تجویز نہیں کیے جاتے۔ اگر مزید مختلف اسٹیٹس کی ضرورت ہو تو انہیں sealed class یا data class کے ذریعے یکجا کریں۔ ہر StateFlow کو جمع کرتے وقت Continuation آبجیکٹ کی مختص کی ضرورت ہوتی ہے — سو StateFlow GC پر نمایاں بوجھ ڈال سکتے ہیں۔ Google کی سفارش کے مطابق، فی اسکرین ایک sealed class UIState پڑھنے کی اہلیت اور کارکردگی کے درمیان بہترین توازن ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں