लाइफ़साइकल की जानकारी वाले कॉम्पोनेंट (व्यू) के साथ Kotlin कोरूटीन का इस्तेमाल करना

सिद्धांत और Jetpack Compose को लागू करना

Kotlin कोरूटीन एक ऐसा एपीआई उपलब्ध कराते हैं जिसकी मदद से एसिंक्रोनस कोड लिखा जा सकता है. Kotlin कोरूटीन की मदद से, CoroutineScope को तय किया जा सकता है. इससे यह मैनेज करने में मदद मिलती है कि कोरूटीन कब चलने चाहिए. हर एसिंक्रोनस ऑपरेशन, किसी खास स्कोप में चलता है.

लाइफ़साइकल की जानकारी वाले कॉम्पोनेंट, आपके ऐप्लिकेशन में लॉजिकल स्कोप के लिए को-रूटीन की बेहतरीन सुविधा देते हैं. साथ ही, LiveData के साथ इंटरऑपरेबिलिटी लेयर भी देते हैं. इस विषय में, लाइफ़साइकल के बारे में जानकारी रखने वाले कॉम्पोनेंट के साथ, कोरूटीन का असरदार तरीके से इस्तेमाल करने का तरीका बताया गया है.

KTX डिपेंडेंसी जोड़ना

इस विषय में बताए गए, पहले से मौजूद कोराटीन स्कोप, हर कॉम्पोनेंट के लिए KTX एक्सटेंशन में शामिल होते हैं. इन स्कोप का इस्तेमाल करते समय, सही डिपेंडेंसी जोड़ना न भूलें.

  • ViewModelScope के लिए, androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 या इसके बाद का वर्शन इस्तेमाल करें.
  • LifecycleScope के लिए, androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 या इसके बाद का वर्शन इस्तेमाल करें.
  • liveData के लिए, androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 या इसके बाद के वर्शन का इस्तेमाल करें.

लाइफ़साइकल की जानकारी वाले को-रूटीन स्कोप

लाइफ़साइकल की जानकारी वाले कॉम्पोनेंट, इन बिल्ट-इन स्कोप के बारे में बताते हैं. इन्हें अपने ऐप्लिकेशन में इस्तेमाल किया जा सकता है.

ViewModelScope

आपके ऐप्लिकेशन में मौजूद हर ViewModel के लिए, एक ViewModelScope तय किया जाता है. अगर ViewModel मिटा दिया गया हो, तो इसके स्कोप में लॉन्च किया गया कोई भी कोरूटीन अपने-आप रद्द हो जाता है. कोरूटीन उस समय के लिए सही है, जब आपके पास ऐसा काम हो जिसे ViewModel ऐक्टिव होने पर ही करना ज़रूरी हो. उदाहरण के लिए, अगर आपको किसी लेआउट के लिए कुछ डेटा कैलकुलेट करना है, तो आपको अपना काम ViewModel के स्कोप में करना होगा, ताकि ViewModel मिटाए जाने पर, काम अपने-आप रद्द हो जाता है और रिसॉर्स के गैर-ज़रूरी इस्तेमाल से बचा जा सकता है.

ViewModel की CoroutineScope प्रॉपर्टी को ऐक्सेस करने के लिए, ViewModel की viewModelScope प्रॉपर्टी का इस्तेमाल किया जा सकता है. इसका उदाहरण यहां दिया गया है:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

LifecycleScope

हर Lifecycle ऑब्जेक्ट के लिए, एक LifecycleScope तय किया जाता है. अगर Lifecycle को बंद कर दिया जाता है, तो इसके स्कोप में लॉन्च किया गया कोई भी कोरूटीन रद्द हो जाता है. Lifecycle की CoroutineScope को lifecycle.coroutineScope या lifecycleOwner.lifecycleScope प्रॉपर्टी के ज़रिए ऐक्सेस किया जा सकता है.

नीचे दिए गए उदाहरण में, एसिंक्रोनस तरीके से पहले से तैयार किया गया टेक्स्ट बनाने के लिए lifecycleOwner.lifecycleScope का इस्तेमाल करने का तरीका बताया गया है:

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}

लाइफ़साइकल की जानकारी वाली कोरूटीन, जिन्हें फिर से शुरू किया जा सकता है

lifecycleScope की मदद से, लंबे समय तक चलने वाली कार्रवाइयों को अपने-आप रद्द किया जा सकता है. ऐसा तब होता है, जब Lifecycle DESTROYED पर सेट हो. हालांकि, ऐसे अन्य मामले भी हो सकते हैं जिनमें आपको Lifecycle के किसी खास स्टेट में होने पर, कोड ब्लॉक को एक्ज़ीक्यूट करना हो और किसी अन्य स्टेट में होने पर, उसे रद्द करना हो. उदाहरण के लिए, ऐसा हो सकता है कि आपको Lifecycle होने पर कोई फ़्लो इकट्ठा करना हो और STOPPED होने पर उसे रद्द करना हो.STARTED इस तरीके से, फ़्लो के उत्सर्जन को सिर्फ़ तब प्रोसेस किया जाता है, जब यूज़र इंटरफ़ेस (यूआई) स्क्रीन पर दिखता है. इससे संसाधनों को बचाया जा सकता है और ऐप्लिकेशन क्रैश होने से रोका जा सकता है.

इन मामलों के लिए, Lifecycle और LifecycleOwner, suspend repeatOnLifecycle API उपलब्ध कराते हैं. यह एपीआई ठीक यही काम करता है. इस उदाहरण में, एक कोड ब्लॉक दिया गया है. यह कोड ब्लॉक, इससे जुड़े Lifecycle के कम से कम STARTED स्थिति में होने पर हर बार चलता है. साथ ही, Lifecycle के STOPPED स्थिति में होने पर बंद हो जाता है:

class MyFragment : Fragment() {

    val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Create a new coroutine in the lifecycleScope
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                viewModel.someDataFlow.collect {
                    // Process item
                }
            }
        }
    }
}

लाइफ़साइकल की जानकारी वाले फ़्लो का कलेक्शन

अगर आपको सिर्फ़ एक फ़्लो पर लाइफ़साइकल के बारे में जानकारी रखने वाला कलेक्शन लागू करना है, तो अपने कोड को आसान बनाने के लिए, Flow.flowWithLifecycle() तरीके का इस्तेमाल करें:

viewLifecycleOwner.lifecycleScope.launch {
    exampleProvider.exampleFlow()
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect {
            // Process the value.
        }
}

हालांकि, अगर आपको कई फ़्लो पर लाइफ़साइकल के बारे में जानकारी रखने वाला कलेक्शन एक साथ करना है, तो आपको हर फ़्लो को अलग-अलग को-रूटीन में इकट्ठा करना होगा. ऐसे में, सीधे repeatOnLifecycle() का इस्तेमाल करना ज़्यादा असरदार होता है:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Because collect is a suspend function, if you want to
        // collect multiple flows in parallel, you need to do so in
        // different coroutines.
        launch {
            flow1.collect { /* Process the value. */ }
        }

        launch {
            flow2.collect { /* Process the value. */ }
        }
    }
}

लाइफ़साइकल की जानकारी वाली को-रूटीन को निलंबित करना

CoroutineScope लंबे समय तक चलने वाले ऑपरेशनों को अपने-आप रद्द करने का सही तरीका है. हालांकि, ऐसे अन्य मामले भी हो सकते हैं जहां आपको किसी कोड ब्लॉक के एक्ज़ीक्यूशन को तब तक निलंबित करना हो, जब तक Lifecycle किसी खास स्थिति में न हो. उदाहरण के लिए, FragmentTransaction चलाने के लिए, आपको तब तक इंतज़ार करना होगा, जब तक Lifecycle कम से कम STARTED न हो जाए. इन मामलों के लिए, Lifecycle अतिरिक्त तरीके उपलब्ध कराता है: lifecycle.whenCreated, lifecycle.whenStarted और lifecycle.whenResumed. अगर Lifecycle कम से कम ज़रूरी स्थिति में नहीं है, तो इन ब्लॉक के अंदर चलने वाली किसी भी को-रूटीन को निलंबित कर दिया जाता है.

नीचे दिए गए उदाहरण में, एक कोड ब्लॉक है. यह सिर्फ़ तब चलता है, जब इससे जुड़ा Lifecycle कम से कम STARTED स्थिति में हो:

class MyFragment: Fragment {
    init { // Notice that we can safely launch in the constructor of the Fragment.
        lifecycleScope.launch {
            whenStarted {
                // The block inside will run only when Lifecycle is at least STARTED.
                // It will start executing when fragment is started and
                // can call other suspend methods.
                loadingView.visibility = View.VISIBLE
                val canAccess = withContext(Dispatchers.IO) {
                    checkUserAccess()
                }

                // When checkUserAccess returns, the next line is automatically
                // suspended if the Lifecycle is not *at least* STARTED.
                // We could safely run fragment transactions because we know the
                // code won't run unless the lifecycle is at least STARTED.
                loadingView.visibility = View.GONE
                if (canAccess == false) {
                    findNavController().popBackStack()
                } else {
                    showContent()
                }
            }

            // This line runs only after the whenStarted block above has completed.

        }
    }
}

अगर Lifecycle के किसी तरीके से कोरूटीन चालू होने के दौरान Lifecycle बंद हो जाता है, तो कोरूटीन अपने-आप रद्द हो जाता है.when यहां दिए गए उदाहरण में, Lifecycle स्थिति DESTROYED होने पर, finally ब्लॉक एक बार चलता है:

class MyFragment: Fragment {
    init {
        lifecycleScope.launchWhenStarted {
            try {
                // Call some suspend functions.
            } finally {
                // This line might execute after Lifecycle is DESTROYED.
                if (lifecycle.state >= STARTED) {
                    // Here, since we've checked, it is safe to run any
                    // Fragment transactions.
                }
            }
        }
    }
}

LiveData के साथ कोरूटीन का इस्तेमाल करना

LiveData का इस्तेमाल करते समय, आपको वैल्यू को एसिंक्रोनस तरीके से कैलकुलेट करने की ज़रूरत पड़ सकती है. उदाहरण के लिए, आपको किसी उपयोगकर्ता की प्राथमिकताएं वापस लानी हों और उन्हें अपने यूज़र इंटरफ़ेस (यूआई) पर दिखाना हो. ऐसे मामलों में, liveData बिल्डर फ़ंक्शन का इस्तेमाल करके, suspend फ़ंक्शन को कॉल किया जा सकता है. इससे, नतीजे को LiveData ऑब्जेक्ट के तौर पर दिखाया जा सकता है.

नीचे दिए गए उदाहरण में, loadUser() एक सस्पेंड फ़ंक्शन है, जिसे कहीं और घोषित किया गया है. loadUser() को एसिंक्रोनस तरीके से कॉल करने के लिए, liveData बिल्डर फ़ंक्शन का इस्तेमाल करें. इसके बाद, नतीजे को दिखाने के लिए emit() का इस्तेमाल करें:

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

liveData बिल्डिंग ब्लॉक, कोरूटीन और LiveData के बीच स्ट्रक्चर्ड कॉनकरेंसी प्रिमिटिव के तौर पर काम करता है. LiveData के चालू होने पर, कोड ब्लॉक काम करना शुरू कर देता है. LiveData के बंद होने पर, कॉन्फ़िगर किए जा सकने वाले टाइम आउट के बाद, यह अपने-आप रद्द हो जाता है. अगर यह प्रोसेस पूरी होने से पहले रद्द कर दी जाती है, तो LiveData के फिर से चालू होने पर यह प्रोसेस फिर से शुरू हो जाती है. अगर यह प्रोसेस पिछली बार पूरी हो गई थी, तो यह फिर से शुरू नहीं होगी. ध्यान दें कि यह सिर्फ़ तब फिर से शुरू होता है, जब इसे अपने-आप रद्द कर दिया जाता है. अगर किसी दूसरी वजह से ब्लॉक करने की सुविधा रद्द कर दी जाती है (जैसे, CancellationException फेंकना), तो यह सुविधा फिर से शुरू नहीं होती.

ब्लॉक से एक से ज़्यादा वैल्यू भी भेजी जा सकती हैं. हर emit() कॉल, ब्लॉक के एक्ज़ीक्यूशन को तब तक निलंबित करता है, जब तक मुख्य थ्रेड पर LiveData वैल्यू सेट नहीं हो जाती.

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

liveData को Transformations के साथ भी जोड़ा जा सकता है. जैसा कि यहां दिए गए उदाहरण में दिखाया गया है:

class MyViewModel: ViewModel() {
    private val userId: LiveData<String> = MutableLiveData()
    val user = userId.switchMap { id ->
        liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(database.loadUserById(id))
        }
    }
}

LiveData से कई वैल्यू भेजी जा सकती हैं. इसके लिए, जब भी आपको कोई नई वैल्यू भेजनी हो, तब emitSource() फ़ंक्शन को कॉल करें. ध्यान दें कि emit() या emitSource() को कॉल करने पर, पहले से जोड़ा गया सोर्स हट जाता है.

class UserDao: Dao {
    @Query("SELECT * FROM User WHERE id = :id")
    fun getUser(id: String): LiveData<User>
}

class MyRepository {
    fun getUser(id: String) = liveData<User> {
        val disposable = emitSource(
            userDao.getUser(id).map {
                Result.loading(it)
            }
        )
        try {
            val user = webservice.fetchUser(id)
            // Stop the previous emission to avoid dispatching the updated user
            // as `loading`.
            disposable.dispose()
            // Update the database.
            userDao.insert(user)
            // Re-establish the emission with success type.
            emitSource(
                userDao.getUser(id).map {
                    Result.success(it)
                }
            )
        } catch(exception: IOException) {
            // Any call to `emit` disposes the previous one automatically so we don't
            // need to dispose it here as we didn't get an updated value.
            emitSource(
                userDao.getUser(id).map {
                    Result.error(exception, it)
                }
            )
        }
    }
}

को-रूटीन से जुड़ी ज़्यादा जानकारी के लिए, यहां दिए गए लिंक देखें:

अन्य संसाधन

लाइफ़साइकल के बारे में जानकारी रखने वाले कॉम्पोनेंट के साथ को-रूटीन इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, यहां दिए गए अतिरिक्त संसाधन देखें.

सैंपल

ब्लॉग