Jetpack Compose State<T> और MutableState<T>, सामान्य कामों के लिए इस्तेमाल किए जाने वाले वैल्यू होल्डर हैं. ये Compose के स्नैपशॉट सिस्टम पर काम करते हैं. हालांकि, इनका इस्तेमाल आम तौर पर यूज़र इंटरफ़ेस (यूआई) को फिर से बनाने के लिए किया जाता है, लेकिन स्नैपशॉट सिस्टम, यूज़र इंटरफ़ेस (यूआई) रेंडरिंग से नहीं जुड़ा होता.
AppState लाइब्रेरी—Transform और StateStore—की मदद से, ViewModels, मैनेजर, रिपॉज़िटरी, और बैकग्राउंड में चलने वाली सेवाओं में Compose State को होल्ड, ड्राइव, ऑब्ज़र्व, और परसिस्ट किया जा सकता है.
यूज़र इंटरफ़ेस (यूआई) के अलावा, Compose स्टेट का इस्तेमाल क्यों करें?
जब Compose State को किसी स्टेट-ऑब्ज़र्विंग ब्लॉक में पढ़ा जाता है, जैसे कि यूज़र इंटरफ़ेस (यूआई) कंपोज़ेबल, transform या derivedStateOf, तो Compose रनटाइम आपके आर्किटेक्चर की किसी भी लेयर पर दो मुख्य फ़ायदे देता है:
- डिपेंडेंसी को अपने-आप और डाइनैमिक तरीके से ट्रैक करना: किसी स्टेट की
.value(याbyप्रॉपर्टी डेलिगेशन का इस्तेमाल करके) को पढ़ने से, वह स्टेट अपने-आप ब्लॉक की डिपेंडेंसी के तौर पर रजिस्टर हो जाती है. ट्रैकिंग डाइनैमिक होती है. इसलिए, डिपेंडेंसी रनटाइम के दौरान तय होती हैं. यह इस बात पर निर्भर करता है कि कौनसे कोड पाथ लागू होते हैं. अगर कोई कंडिशनल ब्रांच नहीं ली जाती है, तो उस ब्रांच के अंदर मौजूद स्टेट को नहीं पढ़ा जाता. साथ ही, उनमें किए गए बदलावों से, फिर से कैलकुलेशन करने की ज़रूरत नहीं पड़ती. उदाहरण के लिए,ifकी जांच के बाद याreturnके बाद. - एक जैसा होना और बारीकी से जानकारी देना: आपके पास अलग-अलग और बारीकी से जानकारी देने वाली
MutableStateप्रॉपर्टी का एलान करने का विकल्प होता है. साथ ही, एक से ज़्यादा प्रॉपर्टी कोSnapshot.withMutableSnapshotमें रैप किया जा सकता है. इंटरमीडिएट अपडेट, म्यूटेबल स्नैपशॉट में अलग से रखे जाते हैं और ब्लॉक पूरा होने पर अपने-आप लागू हो जाते हैं. इसलिए, स्नैपशॉट में मौजूद डेटा को पढ़ने वाले ऑब्ज़र्वर को कभी भी इंटरमीडिएट स्टेट में मौजूद डेटा नहीं दिखता. वे सिर्फ़ तब फिर से कंप्यूट करते हैं, जब उनके पढ़े गए डेटा की खास प्रॉपर्टी बदलती हैं.StateFlowके उलट, आपको एक ही मोनोलिथिकStateFlow<UiState>(जो किसी भी फ़ील्ड में बदलाव होने पर हर कलेक्टर को अमान्य कर देता है) और अलग-अलगStateFlowप्रॉपर्टी (जिन्हें एक ही ऐटॉमिक ट्रांज़ैक्शन में एक साथ अपडेट नहीं किया जा सकता) में से किसी एक को चुनने की ज़रूरत नहीं है.
अपने आर्किटेक्चर के लिए सही स्टेट एपीआई चुनना
Compose State को होल्ड और सेव करने का तरीका चुनें. यह इस बात पर निर्भर करता है कि स्टेट का कोई हिस्सा कितने समय तक मौजूद रहना चाहिए और किन कॉम्पोनेंट को उसे ऐक्सेस करना है:
| राज्य की कैटगरी | जीवनकाल और दायरा | उदाहरण | Compose State के लिए सुझाए गए एपीआई |
|---|---|---|---|
| लाइव स्टेट | यह हमेशा मेमोरी में सेव रहता है. ऐप्लिकेशन के चालू होने पर, इसे फिर से क्वेरी किया जाता है. साथ ही, ऐप्लिकेशन की प्रोसेस बंद होने पर यह सेव नहीं रहता. | इंटरनेट कनेक्शन का स्टेटस, ब्लूटूथ कनेक्शन का स्टेटस, यूज़र इंटरफ़ेस (यूआई) की कुछ समय के लिए दिखने वाली स्थिति, जैसे कि आइटम चुनना. |
|
| यूज़र इंटरफ़ेस (यूआई) की कुछ समय तक रहने वाली स्थिति | यह कॉन्फ़िगरेशन में होने वाले बदलावों और सिस्टम की वजह से ऐप्लिकेशन की प्रोसेस बंद होने के बाद भी सेव रहता है. हालांकि, इसे हमेशा के लिए सेव नहीं किया जाता. यह सिर्फ़ एक स्क्रीन या गतिविधि के लिए सेव रहता है. | यूज़र इंटरफ़ेस (यूआई) के बड़े या छोटे किए गए एलिमेंट, स्क्रोल की पोज़िशन, स्क्रीन पर की गई सर्च क्वेरी या फ़िल्टर टॉगल. |
|
| ट्रांज़िएंट सेशन स्टेट | यह बैकग्राउंड में चल रही प्रोसेस बंद होने के बाद भी सेव रहता है. हालांकि, इसे हमेशा के लिए सेव नहीं किया जाता. इसे गतिविधियों, सेवाओं या विजेट के साथ शेयर किया जाता है. | चालू फ़ोकस टाइमर, कई चरणों वाला चेकआउट कार्ट या प्रोसेस में मौजूद फ़ॉर्म का ड्राफ़्ट. |
|
| स्थायी स्थिति | डिस्क पर हमेशा के लिए सेव रखा जाता है. | उपयोगकर्ता की प्राथमिकताएं और सेटिंग (DataStore), स्ट्रक्चर्ड ऐप्लिकेशन का डेटा, और मीडिया लाइब्रेरी (Room). | collectAsState का इस्तेमाल करके, transform में इकट्ठा किए गए DataStore या Room फ़्लो |
ViewModel में Compose स्टेट का इस्तेमाल करना
ViewModel में, इंडिपेंडेंट स्क्रीन इनपुट को ज़्यादा जानकारी वाली MutableState प्रॉपर्टी के तौर पर एलान किया जा सकता है. साथ ही, SavedStateHandle.saveable की मदद से, ऐप्लिकेशन की प्रोसेस बंद होने पर भी उन्हें सेव रखा जा सकता है. इसके अलावा, Snapshot.withMutableSnapshot की मदद से, एक साथ कई प्रॉपर्टी को अपडेट किया जा सकता है. साथ ही, transform में, उन्हें रिपॉज़िटरी Flow स्ट्रीम के साथ जोड़ा जा सकता है:
sealed interface TaskListState { data object Loading : TaskListState data class Success( val tasks: List<Task>, val selectedTaskIds: Set<String>, val isOnline: Boolean = true, ) : TaskListState } @HiltViewModel class TaskListViewModel @Inject constructor( private val tasksRepository: TasksRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { var selectedTaskIds by mutableStateOf(emptySet<String>()) private set var searchQuery by savedStateHandle.saveable { mutableStateOf("") } private set fun updateSearchQuery(query: String) { Snapshot.withMutableSnapshot { searchQuery = query selectedTaskIds = emptySet() } } val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, ) } }
इस ViewModel में मुख्य पैटर्न:
- रिएक्टिव सेव्ड स्टेट के लिए
SavedStateHandle.saveableका इस्तेमाल करना:mutableStateOf(androidx.lifecycle:lifecycle-viewmodel-composeसे) के साथsearchQueryकोSavedStateHandle.saveableपर डेलिगेट करने से, ऐप्लिकेशन की प्रोसेस बंद होने पर भी वैल्यूMutableStateके तौर पर बनी रहती है. इसलिए,transformअपने-आप रीड को ट्रैक करता है. सिर्फ़SavedStateHandle.savedका इस्तेमाल करने से बचें. इससे ComposeStateके बजाय, Kotlin की सामान्य प्रॉपर्टी मिलती है. साथ ही, बदलाव होने पर फिर से कंपोज़िशन ट्रिगर नहीं होता. Snapshot.withMutableSnapshotकी मदद से, एक से ज़्यादा प्रॉपर्टी में एक साथ बदलाव करना:Snapshot.withMutableSnapshotके अंदरsearchQueryऔरselectedTaskIdsको अपडेट करने से, बदलावों को अलग-अलग किया जाता है. साथ ही, दोनों बदलावों को एक ही स्नैपशॉट ट्रांज़ैक्शन में लागू किया जाता है. इससे, ऑब्ज़र्वर (इनमेंtransformभी शामिल है) को दोनों अपडेट एक साथ दिखते हैं. इसके लिए, उन्हें पुरानेselectedTaskIdsके साथ जोड़े गए नएsearchQueryको देखने के बजाय, एक ही रीकंपोज़िशन पास में दोनों अपडेट दिखते हैं.remember(key)की मदद से, रीकंपोज़िशन के दौरान फ़्लो को फिर से इस्तेमाल करना:searchQueryको कुंजी के तौर पर इस्तेमाल करके,tasksRepository.getTasks(searchQuery)कोrememberमें रैप करने से यह पक्का होता है कि जबselectedTaskIdsमें बदलाव होता है औरtransformब्लॉक को रीकंपोज़ किया जाता है, तो Compose, क्वेरी को रीस्टार्ट करने के बजाय, मौजूदा डेटाबेसFlowका फिर से इस्तेमाल करता है.
अपने ऐप्लिकेशन में लाइव सिस्टम स्टेट शेयर करना
कई ViewModel, गतिविधियों या सेवाओं के बीच शेयर किए गए लाइव सिस्टम स्टेटस के लिए, जैसे कि नेटवर्क या ब्लूटूथ कनेक्शन का स्टेटस, आपको प्रोसेस बंद होने पर, डिस्क पर पुराना स्टेटस सेव नहीं करना चाहिए. इसके बजाय, स्टार्टअप पर सिस्टम की मौजूदा स्थिति के बारे में क्वेरी करें. इसके बाद, ऐप्लिकेशन के दायरे में आने वाले सिंगलटन में, इसे निजी mutableStateOf प्रॉपर्टी में रखें. साथ ही, सिर्फ़ पढ़ने के लिए उपलब्ध State<T> को एक्सपोज़ करें:
interface NetworkMonitor { val isOnline: State<Boolean> } @Singleton class LiveNetworkMonitor @Inject constructor( @ApplicationContext context: Context, ) : NetworkMonitor { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java) private val _isOnline = mutableStateOf(checkCurrentConnectivity()) override val isOnline: State<Boolean> = _isOnline private val callback = object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { _isOnline.value = networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } override fun onLost(network: Network) { _isOnline.value = false } } init { connectivityManager.registerDefaultNetworkCallback(callback) } private fun checkCurrentConnectivity(): Boolean { val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } }
MutableState, किसी भी थ्रेड से लगातार पढ़ने और अपडेट करने की सुविधा देता है. इसलिए, NetworkCallback अपने बैकग्राउंड कॉलबैक थ्रेड से सीधे _isOnline.value को अपडेट कर सकता है. अगर कोई बैकग्राउंड कॉलबैक, एक साथ कई MutableState
प्रॉपर्टी अपडेट करता है, तो राइट को
Snapshot.withMutableSnapshot में रैप करें, ताकि वे
एक साथ लागू हों.
आपके ऐप्लिकेशन का कोई भी कॉम्पोनेंट networkMonitor.isOnline.value को पढ़ सकता है—चाहे वह एक बार की जांच के तौर पर हो, यूज़र इंटरफ़ेस (यूआई) कंपोज़ेबल के अंदर हो या listener, derivedStateOf या transform जैसे स्टेट-ऑब्ज़र्विंग ब्लॉक के अंदर हो. उदाहरण के लिए, TaskListViewModel अपने transform ब्लॉक में networkMonitor.isOnline.value को पढ़ सकता है:
val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, isOnline = networkMonitor.isOnline.value, ) }
transform, networkMonitor.isOnline.value के साथ-साथ searchQuery, selectedTaskIds, और रिपॉज़िटरी Flow को ट्रैक करता है. इसलिए, कनेक्टिविटी में कोई भी बदलाव होने पर, डेटाबेस से फिर से क्वेरी किए बिना TaskListState.Success का हिसाब अपने-आप फिर से लगाया जाता है.