Android पर Kotlin कोरूटीन

कोरूटीन, एक साथ कई काम करने के लिए इस्तेमाल किया जाने वाला डिज़ाइन पैटर्न है. इसका इस्तेमाल Android पर किया जा सकता है. इससे एसिंक्रोनस तरीके से काम करने वाले कोड को आसान बनाया जा सकता है. कोरुटीन को Kotlin में वर्शन 1.3 में जोड़ा गया था. ये अन्य भाषाओं के स्थापित कॉन्सेप्ट पर आधारित हैं.

Android पर, कोरूटीन की मदद से लंबे समय तक चलने वाले टास्क मैनेज किए जा सकते हैं. ऐसा न करने पर, मुख्य थ्रेड ब्लॉक हो सकती है और आपका ऐप्लिकेशन काम करना बंद कर सकता है. को-रूटीन का इस्तेमाल करने वाले 50% से ज़्यादा प्रोफेशनल डेवलपर ने बताया है कि इससे उनकी प्रोडक्टिविटी बढ़ी है. इस विषय में बताया गया है कि इन समस्याओं को हल करने के लिए, Kotlin कोराउटीन का इस्तेमाल कैसे किया जा सकता है. इससे आपको ऐप्लिकेशन का कोड ज़्यादा साफ़ तौर पर और कम शब्दों में लिखने में मदद मिलती है.

सुविधाएं

Android पर एसिंक्रोनस प्रोग्रामिंग के लिए, कोरूटीन इस्तेमाल करने का सुझाव दिया जाता है. इनमें ये खास सुविधाएं शामिल हैं:

  • लाइटवेट: सस्पेंशन की सुविधा उपलब्ध होने की वजह से, एक ही थ्रेड पर कई कोरूटीन चलाए जा सकते हैं. इससे उस थ्रेड को ब्लॉक नहीं किया जाता जिस पर कोरूटीन चल रहा है. ब्लॉक करने के बजाय निलंबित करने से, मेमोरी सेव होती है. साथ ही, इससे एक साथ कई कार्रवाइयों को सपोर्ट किया जा सकता है.
  • मेमोरी लीक होने की कम समस्या: स्कोप में ऑपरेशन चलाने के लिए, स्ट्रक्चर्ड कंकरेंसी का इस्तेमाल करें.
  • रद्द करने की सुविधा पहले से मौजूद है: रद्द करने की सुविधा चालू कोराउटीन के क्रम के हिसाब से अपने-आप लागू हो जाती है.
  • Jetpack इंटिग्रेशन: कई Jetpack लाइब्रेरी में एक्सटेंशन शामिल होते हैं, जो कोरूटीन की पूरी सुविधा देते हैं. कुछ लाइब्रेरी, अपना को-रूटीन स्कोप भी उपलब्ध कराती हैं. इसका इस्तेमाल, स्ट्रक्चर्ड कंकरेंसी के लिए किया जा सकता है.

उदाहरणों की खास जानकारी

ऐप्लिकेशन के आर्किटेक्चर की गाइड के आधार पर, इस विषय में दिए गए उदाहरण नेटवर्क का अनुरोध करते हैं और नतीजे को मुख्य थ्रेड पर भेजते हैं. इसके बाद, ऐप्लिकेशन उपयोगकर्ता को नतीजा दिखा सकता है.

खास तौर पर, ViewModel आर्किटेक्चर कॉम्पोनेंट, नेटवर्क का अनुरोध ट्रिगर करने के लिए मुख्य थ्रेड पर रिपॉज़िटरी लेयर को कॉल करता है. इस गाइड में, ऐसे कई समाधानों के बारे में बताया गया है जिनमें मुख्य थ्रेड को अनब्लॉक रखने के लिए कोरूटीन का इस्तेमाल किया जाता है.

ViewModel में KTX एक्सटेंशन का एक सेट शामिल होता है, जो सीधे तौर पर कोरुटीन के साथ काम करता है. ये एक्सटेंशन, lifecycle-viewmodel-ktx लाइब्रेरी हैं और इस गाइड में इनका इस्तेमाल किया गया है.

डिपेंडेंसी की जानकारी

अपने Android प्रोजेक्ट में कोरूटीन का इस्तेमाल करने के लिए, अपने ऐप्लिकेशन की build.gradle फ़ाइल में यह डिपेंडेंसी जोड़ें:

ग्रूवी

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

बैकग्राउंड थ्रेड में एक्ज़ीक्यूट करना

मुख्य थ्रेड पर नेटवर्क का अनुरोध करने से, उसे जवाब मिलने तक इंतज़ार करना पड़ता है या वह ब्लॉक हो जाती है. थ्रेड ब्लॉक होने की वजह से, ओएस onDraw() को कॉल नहीं कर पाता. इससे आपका ऐप्लिकेशन फ़्रीज़ हो जाता है और हो सकता है कि आपको 'ऐप्लिकेशन काम नहीं कर रहा है' (एएनआर) डायलॉग दिखे. उपयोगकर्ता को बेहतर अनुभव देने के लिए, इस कार्रवाई को बैकग्राउंड थ्रेड पर चलाते हैं.

सबसे पहले, हमारी Repository क्लास पर एक नज़र डालते हैं और देखते हैं कि यह नेटवर्क अनुरोध कैसे कर रही है:

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

private const val loginUrl = "https://example.com/login"

class LoginRepository(private val responseParser: LoginResponseParser) {
    // Function that makes the network request, blocking the current thread
    fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        val url = URL(loginUrl)
        (url.openConnection() as? HttpURLConnection)?.run {
            requestMethod = "POST"
            setRequestProperty("Content-Type", "application/json; utf-8")
            setRequestProperty("Accept", "application/json")
            doOutput = true
            outputStream.write(jsonBody.toByteArray())
            return Result.Success(responseParser.parse(inputStream))
        }
        return Result.Error(Exception("Cannot open HttpURLConnection"))
    }
}

makeLoginRequest सिंक्रोनस है और कॉलिंग थ्रेड को ब्लॉक करता है. नेटवर्क अनुरोध के जवाब को मॉडल बनाने के लिए, हमारे पास अपनी Result क्लास है.

जब उपयोगकर्ता किसी बटन पर क्लिक करता है, तो ViewModel नेटवर्क अनुरोध को ट्रिगर करता है. उदाहरण के लिए:

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody)
    }
}

पिछले कोड में, नेटवर्क का अनुरोध करते समय LoginViewModel, यूज़र इंटरफ़ेस (यूआई) थ्रेड को ब्लॉक कर रहा है. मुख्य थ्रेड से अलग थ्रेड में कोड को चलाने का सबसे आसान तरीका है कि एक नई को-रूटीन बनाई जाए और नेटवर्क अनुरोध को I/O थ्रेड पर चलाया जाए:

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {
        // Create a new coroutine to move the execution off the UI thread
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}

आइए, login फ़ंक्शन में मौजूद कोरूटीन कोड के बारे में जानते हैं:

  • viewModelScope, पहले से तय किया गया CoroutineScope है. यह ViewModel KTX एक्सटेंशन के साथ शामिल होता है. ध्यान दें कि सभी कोरूटीन, किसी स्कोप में ही चलने चाहिए. CoroutineScope एक या उससे ज़्यादा संबंधित कोराउटीन मैनेज करता है.
  • launch एक ऐसा फ़ंक्शन है जो एक को-रूटीन बनाता है और अपने फ़ंक्शन बॉडी के एक्ज़ीक्यूशन को संबंधित डिस्पैचर को भेजता है.
  • Dispatchers.IO से पता चलता है कि इस को-रूटीन को I/O कार्रवाइयों के लिए रिज़र्व की गई थ्रेड पर एक्ज़ीक्यूट किया जाना चाहिए.

login फ़ंक्शन को इस तरह लागू किया जाता है:

  • ऐप्लिकेशन, मुख्य थ्रेड पर View लेयर से login फ़ंक्शन को कॉल करता है.
  • launch एक नई कोरूटीन बनाता है, और नेटवर्क का अनुरोध I/O कार्रवाइयों के लिए रिज़र्व की गई थ्रेड पर स्वतंत्र रूप से किया जाता है.
  • कोरूटीन के चालू रहने के दौरान, login फ़ंक्शन काम करता रहता है और नेटवर्क का अनुरोध पूरा होने से पहले ही जवाब दे देता है. ध्यान दें कि आसान बनाने के लिए, फ़िलहाल नेटवर्क रिस्पॉन्स को अनदेखा किया गया है.

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

पिछले उदाहरण में एक समस्या यह है कि makeLoginRequest को कॉल करने वाले किसी भी कोड को यह याद रखना होगा कि एक्ज़ीक्यूशन को मुख्य थ्रेड से हटाना है. आइए, देखते हैं कि हम इस समस्या को हल करने के लिए, Repository में कैसे बदलाव कर सकते हैं.

मुख्य तौर पर सुरक्षा के लिए कोरूटीन इस्तेमाल करना

हम किसी फ़ंक्शन को मुख्य थ्रेड के लिए सुरक्षित तब मानते हैं, जब वह मुख्य थ्रेड पर यूआई अपडेट को ब्लॉक न करे. makeLoginRequest फ़ंक्शन, मुख्य थ्रेड के लिए सुरक्षित नहीं है, क्योंकि मुख्य थ्रेड से makeLoginRequest को कॉल करने पर यूज़र इंटरफ़ेस (यूआई) ब्लॉक हो जाता है. किसी को-रूटीन के एक्ज़ीक्यूशन को किसी दूसरे थ्रेड पर ले जाने के लिए, को-रूटीन लाइब्रेरी के withContext() फ़ंक्शन का इस्तेमाल करें:

class LoginRepository(
    // ...
) {
    // ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}

withContext(Dispatchers.IO), कोरूटीन के एक्ज़ीक्यूशन को I/O थ्रेड पर ले जाता है. इससे हमारा कॉलिंग फ़ंक्शन, मुख्य थ्रेड पर सुरक्षित तरीके से काम कर पाता है और यूज़र इंटरफ़ेस (यूआई) को ज़रूरत के हिसाब से अपडेट किया जा सकता है.

makeLoginRequest को भी suspend कीवर्ड से मार्क किया गया है. Kotlin में इस कीवर्ड का इस्तेमाल, किसी फ़ंक्शन को कोरूटीन में से कॉल करने के लिए किया जाता है.

यहां दिए गए उदाहरण में, LoginViewModel में कोरूटीन बनाया गया है. makeLoginRequest, एक्ज़ीक्यूशन को मुख्य थ्रेड से अलग कर देता है. इसलिए, login फ़ंक्शन में मौजूद को-रूटीन को अब मुख्य थ्रेड में एक्ज़ीक्यूट किया जा सकता है:

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {

        // Create a new coroutine on the UI thread
        viewModelScope.launch {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"

            // Make the network call and suspend execution until it finishes
            val result = loginRepository.makeLoginRequest(jsonBody)

            // Display result of the network request to the user
            when (result) {
                is Result.Success<LoginResponse> -> { /* Happy path */ }
                else -> { /* Show error in UI */ }
            }
        }
    }
}

ध्यान दें कि यहां अब भी कोरूटीन की ज़रूरत है, क्योंकि makeLoginRequest एक suspend फ़ंक्शन है. साथ ही, सभी suspend फ़ंक्शन को कोरूटीन में ही एक्ज़ीक्यूट किया जाना चाहिए.

यह कोड, पिछले login उदाहरण से कुछ मामलों में अलग है:

  • launch फ़ंक्शन में Dispatchers.IO पैरामीटर का इस्तेमाल नहीं किया जाता. launch को Dispatcher पास न करने पर, viewModelScope से लॉन्च की गई सभी को-रूटीन, मुख्य थ्रेड में चलती हैं.
  • नेटवर्क के अनुरोध के नतीजे को अब मैनेज किया जाता है, ताकि सफलता या गड़बड़ी वाला यूज़र इंटरफ़ेस (यूआई) दिखाया जा सके.

अब लॉग इन फ़ंक्शन इस तरह काम करता है:

  • ऐप्लिकेशन, मुख्य थ्रेड पर View लेयर से login() फ़ंक्शन को कॉल करता है.
  • launch मुख्य थ्रेड पर एक नई को-रूटीन बनाता है और को-रूटीन का एक्ज़ीक्यूशन शुरू होता है.
  • कोरूटीन के अंदर, loginRepository.makeLoginRequest() now सस्पेंड को कॉल करने पर, कोरूटीन का आगे का एक्ज़ीक्यूशन तब तक रुक जाता है, जब तक makeLoginRequest() में मौजूद withContext ब्लॉक चलना पूरा नहीं हो जाता.
  • withContext ब्लॉक खत्म होने के बाद, login() में मौजूद कोरूटीन, नेटवर्क के अनुरोध के नतीजे के साथ मुख्य थ्रेड पर फिर से शुरू हो जाती है.

अपवादों को मैनेज करना

Repository लेयर से मिलने वाले अपवादों को मैनेज करने के लिए, Kotlin के अपवादों के लिए बिल्ट-इन सपोर्ट का इस्तेमाल करें. यहां दिए गए उदाहरण में, हमने try-catch ब्लॉक का इस्तेमाल किया है:

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {
        viewModelScope.launch {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            val result = try {
                loginRepository.makeLoginRequest(jsonBody)
            } catch (e: Exception) {
                Result.Error(Exception("Network request failed"))
            }
            when (result) {
                is Result.Success<LoginResponse> -> { /* Happy path */ }
                else -> { /* Show error in UI */ }
            }
        }
    }
}

इस उदाहरण में, makeLoginRequest() कॉल से मिले किसी भी अनचाहे अपवाद को यूज़र इंटरफ़ेस (यूआई) में गड़बड़ी के तौर पर हैंडल किया जाता है.

को-रूटीन से जुड़े अन्य संसाधन

Android पर कोरूटीन के बारे में ज़्यादा जानकारी के लिए, Kotlin कोरूटीन की मदद से ऐप्लिकेशन की परफ़ॉर्मेंस बेहतर बनाना लेख पढ़ें.

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