यूज़र इंटरफ़ेस (यूआई) के बाहर Compose State का इस्तेमाल करना

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 के लिए सुझाए गए एपीआई
लाइव स्टेट यह हमेशा मेमोरी में सेव रहता है. ऐप्लिकेशन के चालू होने पर, इसे फिर से क्वेरी किया जाता है. साथ ही, ऐप्लिकेशन की प्रोसेस बंद होने पर यह सेव नहीं रहता. इंटरनेट कनेक्शन का स्टेटस, ब्लूटूथ कनेक्शन का स्टेटस, यूज़र इंटरफ़ेस (यूआई) की कुछ समय के लिए दिखने वाली स्थिति, जैसे कि आइटम चुनना.
  • ViewModel या डीआई-स्कोप वाले @Singleton मैनेजर खाते में mutableStateOf
  • transform, ताकि राज्य की जानकारी हासिल की जा सके
  • listener, ताकि स्टेट को ऑब्ज़र्व किया जा सके
यूज़र इंटरफ़ेस (यूआई) की कुछ समय तक रहने वाली स्थिति यह कॉन्फ़िगरेशन में होने वाले बदलावों और सिस्टम की वजह से ऐप्लिकेशन की प्रोसेस बंद होने के बाद भी सेव रहता है. हालांकि, इसे हमेशा के लिए सेव नहीं किया जाता. यह सिर्फ़ एक स्क्रीन या गतिविधि के लिए सेव रहता है. यूज़र इंटरफ़ेस (यूआई) के बड़े या छोटे किए गए एलिमेंट, स्क्रोल की पोज़िशन, स्क्रीन पर की गई सर्च क्वेरी या फ़िल्टर टॉगल.
ट्रांज़िएंट सेशन स्टेट यह बैकग्राउंड में चल रही प्रोसेस बंद होने के बाद भी सेव रहता है. हालांकि, इसे हमेशा के लिए सेव नहीं किया जाता. इसे गतिविधियों, सेवाओं या विजेट के साथ शेयर किया जाता है. चालू फ़ोकस टाइमर, कई चरणों वाला चेकआउट कार्ट या प्रोसेस में मौजूद फ़ॉर्म का ड्राफ़्ट.
  • StateStore को किसी मैनेजर या रिपॉज़िटरी में शामिल किया गया है
  • PersistToDataStore को लागू करने वाली और syncToDataStore का इस्तेमाल करके सिंक की गई कुंजियां
स्थायी स्थिति डिस्क पर हमेशा के लिए सेव रखा जाता है. उपयोगकर्ता की प्राथमिकताएं और सेटिंग (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 का इस्तेमाल करने से बचें. इससे Compose State के बजाय, 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 का हिसाब अपने-आप फिर से लगाया जाता है.