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 में कैसे बदलाव कर सकते हैं.

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

हम किसी फ़ंक्शन को main-safe तब मानते हैं, जब वह मुख्य थ्रेड पर यूआई अपडेट को ब्लॉक न करे. 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 कोरूटीन की मदद से ऐप्लिकेशन की परफ़ॉर्मेंस बेहतर बनाना लेख पढ़ें.

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