इस कोडलैब में, आपको Android ऐप्लिकेशन में Kotlin Coroutines का इस्तेमाल करने का तरीका बताया जाएगा. यह बैकग्राउंड थ्रेड को मैनेज करने का एक नया तरीका है. इससे कोड को आसान बनाया जा सकता है, क्योंकि इसमें कॉलबैक की ज़रूरत कम होती है. कोरूटीन, Kotlin की एक सुविधा है. यह लंबे समय तक चलने वाले टास्क के लिए, एसिंक कॉलबैक को सीक्वेंशियल कोड में बदलता है. जैसे, डेटाबेस या नेटवर्क ऐक्सेस करना.
यहां एक कोड स्निपेट दिया गया है, ताकि आपको पता चल सके कि आपको क्या करना है.
// Async callbacks
networkRequest { result ->
// Successful network request
databaseSave(result) { rows ->
// Result saved
}
}
कॉलबैक पर आधारित कोड को कोराटीन का इस्तेमाल करके सीक्वेंशियल कोड में बदल दिया जाएगा.
// The same code with coroutines
val result = networkRequest()
// Successful network request
databaseSave(result)
// Result saved
आपको आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल करके बनाए गए किसी मौजूदा ऐप्लिकेशन से शुरुआत करनी होगी. यह ऐप्लिकेशन, लंबे समय तक चलने वाले टास्क के लिए कॉलबैक स्टाइल का इस्तेमाल करता है.
इस कोडलैब के आखिर तक, आपको अपने ऐप्लिकेशन में नेटवर्क से डेटा लोड करने के लिए, कोरूटीन का इस्तेमाल करने का काफ़ी अनुभव मिल जाएगा. साथ ही, आपके पास किसी ऐप्लिकेशन में कोरूटीन को इंटिग्रेट करने की क्षमता होगी. आपको कोरूटीन के सबसे सही तरीकों के बारे में भी पता चल जाएगा. साथ ही, कोरूटीन का इस्तेमाल करने वाले कोड के ख़िलाफ़ टेस्ट लिखने का तरीका भी पता चल जाएगा.
ज़रूरी शर्तें
- आर्किटेक्चर कॉम्पोनेंट
ViewModel
,LiveData
,Repository
, औरRoom
के बारे में जानकारी होनी चाहिए. - Kotlin सिंटैक्स का इस्तेमाल करने का अनुभव होना चाहिए. इसमें एक्सटेंशन फ़ंक्शन और लैम्डा शामिल हैं.
- Android पर थ्रेड इस्तेमाल करने की बुनियादी जानकारी होनी चाहिए. इसमें मुख्य थ्रेड, बैकग्राउंड थ्रेड, और कॉलबैक शामिल हैं.
आपको क्या करना होगा
- कोरूटीन का इस्तेमाल करके कॉल कोड लिखें और नतीजे पाएं.
- एसिंक कोड को क्रम से चलाने के लिए, सस्पेंड फ़ंक्शन का इस्तेमाल करें.
launch
औरrunBlocking
का इस्तेमाल करके, यह कंट्रोल किया जा सकता है कि कोड कैसे काम करे.suspendCoroutine
का इस्तेमाल करके, मौजूदा एपीआई को को-रूटीन में बदलने की तकनीकें जानें.- आर्किटेक्चर कॉम्पोनेंट के साथ कोरूटीन का इस्तेमाल करें.
- को-रूटीन की जांच करने के सबसे सही तरीके जानें.
आपको किन चीज़ों की ज़रूरत होगी
- Android Studio 3.5 (यह कोडलैब अन्य वर्शन के साथ भी काम कर सकता है, लेकिन हो सकता है कि कुछ चीज़ें मौजूद न हों या अलग दिखें).
अगर आपको इस कोडलैब को पूरा करते समय कोई समस्या (कोड में गड़बड़ियां, व्याकरण से जुड़ी गलतियां, शब्दों का सही इस्तेमाल न होना वगैरह) आती है, तो कृपया कोडलैब के सबसे नीचे बाएं कोने में मौजूद गड़बड़ी की शिकायत करें लिंक के ज़रिए समस्या की शिकायत करें.
कोड डाउनलोड करें
इस कोडलैब के लिए पूरा कोड डाउनलोड करने के लिए, यहां दिए गए लिंक पर क्लिक करें:
... या कमांड लाइन से GitHub रिपॉज़िटरी को क्लोन करें. इसके लिए, यह कमांड इस्तेमाल करें:
$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git
अक्सर पूछे जाने वाले सवाल
सबसे पहले, देखते हैं कि शुरुआती सैंपल ऐप्लिकेशन कैसा दिखता है. Android Studio में सैंपल ऐप्लिकेशन खोलने के लिए, यह तरीका अपनाएं.
- अगर आपने
kotlin-coroutines
zip फ़ाइल डाउनलोड की है, तो उसे अनज़िप करें. - Android Studio में
coroutines-codelab
प्रोजेक्ट खोलें. start
ऐप्लिकेशन मॉड्यूल चुनें.चलाएं बटन पर क्लिक करें. इसके बाद, कोई एम्युलेटर चुनें या अपने Android डिवाइस को कनेक्ट करें. यह ज़रूरी है कि आपके डिवाइस में Android Lollipop (कम से कम एसडीके 21) चल रहा हो. आपको Kotlin Coroutines स्क्रीन दिखेगी:
इस स्टार्टर ऐप्लिकेशन में थ्रेड का इस्तेमाल किया जाता है. इससे स्क्रीन दबाने के कुछ समय बाद, गिनती बढ़ जाती है. यह नेटवर्क से नया टाइटल भी फ़ेच करेगा और उसे स्क्रीन पर दिखाएगा. इसे अभी आज़माएं. आपको कुछ समय बाद, संख्या और मैसेज में बदलाव दिखेगा. इस कोडलैब में, इस ऐप्लिकेशन को को-रूटीन का इस्तेमाल करने के लिए बदला जाएगा.
यह ऐप्लिकेशन, आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल करता है. इससे MainActivity
में मौजूद यूज़र इंटरफ़ेस (यूआई) कोड को MainViewModel
में मौजूद ऐप्लिकेशन लॉजिक से अलग किया जा सकता है. थोड़ा समय निकालकर, प्रोजेक्ट के स्ट्रक्चर के बारे में जानें.
MainActivity
यूज़र इंटरफ़ेस (यूआई) दिखाता है, क्लिक लिसनर रजिस्टर करता है, औरSnackbar
दिखा सकता है. यहMainViewModel
को इवेंट पास करता है औरMainViewModel
में मौजूदLiveData
के आधार पर स्क्रीन को अपडेट करता है.MainViewModel
,onMainViewClicked
में इवेंट मैनेज करता है औरLiveData.
का इस्तेमाल करकेMainActivity
से कम्यूनिकेट करेगाExecutors
,BACKGROUND,
को तय करता है. यह बैकग्राउंड थ्रेड पर काम कर सकता है.TitleRepository
नेटवर्क से नतीजे फ़ेच करता है और उन्हें डेटाबेस में सेव करता है.
किसी प्रोजेक्ट में को-रूटीन जोड़ना
Kotlin में कोरूटीन का इस्तेमाल करने के लिए, आपको अपने प्रोजेक्ट की build.gradle (Module: app)
फ़ाइल में coroutines-core
लाइब्रेरी शामिल करनी होगी. कोडलैब प्रोजेक्ट में यह काम पहले से ही किया जा चुका है. इसलिए, कोडलैब पूरा करने के लिए आपको यह काम करने की ज़रूरत नहीं है.
Android पर कोर रूटीन, कोर लाइब्रेरी और Android के लिए खास एक्सटेंशन के तौर पर उपलब्ध हैं:
- kotlinx-corountines-core — Kotlin में कोरूटीन इस्तेमाल करने के लिए मुख्य इंटरफ़ेस
- kotlinx-coroutines-android — कोरूटीन में Android की मुख्य थ्रेड के लिए सहायता
स्टार्टर ऐप्लिकेशन में, पहले से ही डिपेंडेंसी शामिल होती हैं. build.gradle.
नया ऐप्लिकेशन प्रोजेक्ट बनाते समय, आपको build.gradle (Module: app)
खोलना होगा और प्रोजेक्ट में कोराउटीन डिपेंडेंसी जोड़नी होंगी.
dependencies { ... implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x" }
Android पर, मुख्य थ्रेड को ब्लॉक होने से बचाना ज़रूरी है. मुख्य थ्रेड एक ऐसा सिंगल थ्रेड होता है जो यूज़र इंटरफ़ेस (यूआई) के सभी अपडेट को मैनेज करता है. यह थ्रेड, सभी क्लिक हैंडलर और अन्य यूज़र इंटरफ़ेस (यूआई) कॉलबैक को भी कॉल करता है. इसलिए, यह ज़रूरी है कि यह सुविधा बिना किसी रुकावट के काम करे, ताकि उपयोगकर्ताओं को बेहतर अनुभव मिल सके.
उपयोगकर्ता को आपका ऐप्लिकेशन बिना किसी रुकावट के दिखे, इसके लिए मुख्य थ्रेड को स्क्रीन को हर 16 मि॰से॰ या उससे ज़्यादा समय में अपडेट करना होगा. यह करीब 60 फ़्रेम प्रति सेकंड होता है. कई सामान्य टास्क में इससे ज़्यादा समय लगता है. जैसे, बड़े JSON डेटासेट को पार्स करना, डेटाबेस में डेटा लिखना या नेटवर्क से डेटा फ़ेच करना. इसलिए, मुख्य थ्रेड से इस तरह के कॉलिंग कोड को कॉल करने पर, ऐप्लिकेशन रुक सकता है, अटक सकता है या बंद भी हो सकता है. अगर मुख्य थ्रेड को बहुत देर तक ब्लॉक किया जाता है, तो ऐप्लिकेशन क्रैश भी हो सकता है. साथ ही, ऐप्लिकेशन काम नहीं कर रहा है डायलॉग दिख सकता है.
Android पर कोरूटीन, मुख्य थ्रेड की सुरक्षा को लागू करके इस समस्या को कैसे हल करती हैं, इस बारे में जानने के लिए यहां दिया गया वीडियो देखें.
कॉलबैक पैटर्न
मुख्य थ्रेड को ब्लॉक किए बिना लंबे समय तक चलने वाले टास्क को पूरा करने का एक तरीका कॉलबैक है. कॉलबैक का इस्तेमाल करके, बैकग्राउंड थ्रेड पर लंबे समय तक चलने वाले टास्क शुरू किए जा सकते हैं. टास्क पूरा होने पर, कॉलबैक को कॉल किया जाता है, ताकि आपको मुख्य थ्रेड पर नतीजे के बारे में बताया जा सके.
कॉलबैक पैटर्न का एक उदाहरण देखें.
// Slow request with callbacks
@UiThread
fun makeNetworkRequest() {
// The slow network request runs on another thread
slowFetch { result ->
// When the result is ready, this callback will get the result
show(result)
}
// makeNetworkRequest() exits after calling slowFetch without waiting for the result
}
इस कोड को @UiThread
के साथ एनोटेट किया गया है. इसलिए, इसे मुख्य थ्रेड पर एक्ज़ीक्यूट करने के लिए, तेज़ी से चलना चाहिए. इसका मतलब है कि इसे बहुत तेज़ी से वापस आना चाहिए, ताकि अगली स्क्रीन अपडेट में देरी न हो. हालांकि, slowFetch
को पूरा होने में कुछ सेकंड या मिनट लग सकते हैं. इसलिए, मुख्य थ्रेड नतीजे का इंतज़ार नहीं कर सकती. show(result)
कॉलबैक फ़ंक्शन की मदद से, slowFetch
को बैकग्राउंड थ्रेड पर चलाया जा सकता है. साथ ही, जब नतीजा तैयार हो जाए, तब उसे वापस भेजा जा सकता है.
कॉल बैक हटाने के लिए कोरूटीन का इस्तेमाल करना
कॉलबैक एक बेहतरीन पैटर्न है. हालांकि, इसमें कुछ कमियां हैं. ऐसे कोड को पढ़ना और समझना मुश्किल हो सकता है जिसमें कॉलबैक का ज़्यादा इस्तेमाल किया गया हो. इसके अलावा, कॉलबैक में कुछ भाषाओं की सुविधाओं का इस्तेमाल नहीं किया जा सकता. जैसे, अपवाद.
Kotlin कोरूटीन की मदद से, कॉलबैक पर आधारित कोड को सीक्वेंशियल कोड में बदला जा सकता है. क्रम से लिखे गए कोड को आम तौर पर पढ़ना आसान होता है. इसमें अपवादों जैसी भाषा की सुविधाओं का भी इस्तेमाल किया जा सकता है.
आखिर में, ये दोनों एक ही काम करते हैं: लंबे समय तक चलने वाले टास्क से नतीजा मिलने तक इंतज़ार करना और फिर एक्ज़ीक्यूशन जारी रखना. हालांकि, कोड में ये बहुत अलग दिखते हैं.
suspend
कीवर्ड, Kotlin का एक तरीका है. इससे किसी फ़ंक्शन या फ़ंक्शन टाइप को को-रूटीन के लिए उपलब्ध कराया जाता है. जब कोई कोरूटीन, suspend
के निशान वाले फ़ंक्शन को कॉल करता है, तो उस फ़ंक्शन को सामान्य फ़ंक्शन कॉल रिटर्न करने तक ब्लॉक करने के बजाय, यह नतीजा मिलने तक एक्ज़ीक्यूशन को सस्पेंड करता है. इसके बाद, एक्ज़ीक्यूशन को वहीं से फिर से शुरू करता है जहां वह नतीजों के साथ छोड़ा गया था. जब नतीजा मिलने तक यह सस्पेंड रहता है, तो यह उस थ्रेड को अनब्लॉक करता है जिस पर चलता है , ताकि अन्य फ़ंक्शन या कोरूटीन चल सकें.
उदाहरण के लिए, नीचे दिए गए कोड में makeNetworkRequest()
और slowFetch()
, दोनों suspend
फ़ंक्शन हैं.
// Slow request with coroutines
@UiThread
suspend fun makeNetworkRequest() {
// slowFetch is another suspend function so instead of
// blocking the main thread makeNetworkRequest will `suspend` until the result is
// ready
val result = slowFetch()
// continue to execute after the result is ready
show(result)
}
// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }
कॉलबैक वर्शन की तरह ही, makeNetworkRequest
को मुख्य थ्रेड से तुरंत वापस आना चाहिए, क्योंकि इसे @UiThread
के तौर पर मार्क किया गया है. इसका मतलब है कि आम तौर पर यह slowFetch
जैसे ब्लॉक करने के तरीकों को कॉल नहीं कर सकता. suspend
कीवर्ड यहां अपना जादू दिखाता है.
कॉलबैक पर आधारित कोड की तुलना में, कोरूटीन कोड से मौजूदा थ्रेड को अनब्लॉक करने का वही नतीजा मिलता है. हालांकि, इसके लिए कम कोड की ज़रूरत होती है. सीक्वेंशियल स्टाइल की वजह से, कई कॉलबैक बनाए बिना लंबे समय तक चलने वाले कई टास्क को आसानी से एक साथ जोड़ा जा सकता है. उदाहरण के लिए, दो नेटवर्क एंडपॉइंट से नतीजे पाने और उन्हें डेटाबेस में सेव करने वाले कोड को, कॉरटीन में बिना कॉलबैक के फ़ंक्शन के तौर पर लिखा जा सकता है. जैसे:
// Request data from network and save it to database with coroutines
// Because of the @WorkerThread, this function cannot be called on the
// main thread without causing an error.
@WorkerThread
suspend fun makeNetworkRequest() {
// slowFetch and anotherFetch are suspend functions
val slow = slowFetch()
val another = anotherFetch()
// save is a regular function and will block this thread
database.save(slow, another)
}
// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(): AnotherResult { ... }
अगले सेक्शन में, सैंपल ऐप्लिकेशन में कोरूटीन का इस्तेमाल किया जाएगा.
इस अभ्यास में, आपको कुछ समय बाद मैसेज दिखाने के लिए एक कोरूटीन लिखना होगा. शुरू करने के लिए, पक्का करें कि आपने Android Studio में start
मॉड्यूल खोला हो.
CoroutineScope को समझना
Kotlin में, सभी कोरूटीन CoroutineScope
में चलते हैं. स्कोप, अपने जॉब के ज़रिए कोरूटीन की लाइफ़टाइम को कंट्रोल करता है. किसी स्कोप के जॉब को रद्द करने पर, उस स्कोप में शुरू किए गए सभी कोरूटीन रद्द हो जाते हैं. Android पर, स्कोप का इस्तेमाल करके सभी चालू कोरूटीन को रद्द किया जा सकता है. उदाहरण के लिए, जब उपयोगकर्ता किसी Activity
या Fragment
से दूर नेविगेट करता है. स्कोप की मदद से, डिफ़ॉल्ट डिस्पैचर भी तय किया जा सकता है. डिसपैचर यह कंट्रोल करता है कि कौनसी थ्रेड, कोरूटीन को चलाती है.
यूज़र इंटरफ़ेस (यूआई) से शुरू की गई कोरूटीन को आम तौर पर Dispatchers.Main
पर शुरू करना सही होता है. यह Android पर मुख्य थ्रेड होती है. Dispatchers.Main
पर शुरू किया गया कोरूटीन, निलंबित होने के दौरान मुख्य थ्रेड को ब्लॉक नहीं करेगा. ViewModel
कोरूटीन, मुख्य थ्रेड पर यूआई को लगभग हमेशा अपडेट करता है. इसलिए, मुख्य थ्रेड पर कोरूटीन शुरू करने से, आपको थ्रेड स्विच करने की ज़रूरत नहीं पड़ती. मुख्य थ्रेड पर शुरू की गई कोरूटीन, शुरू होने के बाद किसी भी समय डिस्पैचर बदल सकती है. उदाहरण के लिए, यह मुख्य थ्रेड से बड़े JSON नतीजे को पार्स करने के लिए, किसी दूसरे डिस्पैचर का इस्तेमाल कर सकता है.
viewModelScope का इस्तेमाल करना
AndroidX lifecycle-viewmodel-ktx
लाइब्रेरी, ViewModels में एक CoroutineScope जोड़ती है. इसे यूज़र इंटरफ़ेस (यूआई) से जुड़े कोरूटीन शुरू करने के लिए कॉन्फ़िगर किया जाता है. इस लाइब्रेरी का इस्तेमाल करने के लिए, आपको इसे अपने प्रोजेक्ट की build.gradle (Module: start)
फ़ाइल में शामिल करना होगा. यह चरण, कोडलैब प्रोजेक्ट में पहले ही पूरा कर लिया गया है.
dependencies { ... implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x" }
यह लाइब्रेरी, ViewModel
क्लास के एक्सटेंशन फ़ंक्शन के तौर पर viewModelScope
को जोड़ती है. यह स्कोप Dispatchers.Main
से जुड़ा है. ViewModel
के बंद होने पर, यह अपने-आप रद्द हो जाएगा.
थ्रेड से कोरूटीन पर स्विच करना
MainViewModel.kt
में, इस कोड के साथ अगला TODO ढूंढो:
MainViewModel.kt
/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
// TODO: Convert updateTaps to use coroutines
tapCount++
BACKGROUND.submit {
Thread.sleep(1_000)
_taps.postValue("$tapCount taps")
}
}
यह कोड, बैकग्राउंड थ्रेड में चलने के लिए BACKGROUND ExecutorService
का इस्तेमाल करता है. इसे util/Executor.kt
में तय किया गया है. sleep
मौजूदा थ्रेड को ब्लॉक करता है. इसलिए, अगर इसे मुख्य थ्रेड पर कॉल किया जाता है, तो यह यूज़र इंटरफ़ेस (यूआई) को फ़्रीज़ कर देगा. जब उपयोगकर्ता मुख्य व्यू पर क्लिक करता है, तो एक सेकंड बाद यह स्नैकबार का अनुरोध करता है.
कोड से BACKGROUND को हटाकर और उसे फिर से चलाकर, ऐसा होते हुए देखा जा सकता है. लोडिंग स्पिनर नहीं दिखेगा और एक सेकंड बाद, सब कुछ फ़ाइनल स्थिति में "जंप" हो जाएगा.
MainViewModel.kt
/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
// TODO: Convert updateTaps to use coroutines
tapCount++
Thread.sleep(1_000)
_taps.postValue("$tapCount taps")
}
updateTaps
को इस को-रूटीन पर आधारित कोड से बदलें. यह कोड वही काम करता है. आपको launch
और delay
इंपोर्ट करना होगा.
MainViewModel.kt
/**
* Wait one second then display a snackbar.
*/
fun updateTaps() {
// launch a coroutine in viewModelScope
viewModelScope.launch {
tapCount++
// suspend this coroutine for one second
delay(1_000)
// resume in the main dispatcher
// _snackbar.value can be called directly from main thread
_taps.postValue("$tapCount taps")
}
}
यह कोड भी वही काम करता है. यह स्नैकबार दिखाने से पहले एक सेकंड तक इंतज़ार करता है. हालांकि, इनमें कुछ ज़रूरी अंतर हैं:
viewModelScope.
launch
,viewModelScope
में एक कोरूटीन शुरू करेगा. इसका मतलब है कि जबviewModelScope
को पास किया गया जॉब रद्द हो जाता है, तो इस जॉब/स्कोप में मौजूद सभी कोरूटीन रद्द हो जाएंगे. अगर उपयोगकर्ता नेdelay
के वापस आने से पहले ही ऐक्टिविटी छोड़ दी है, तो ViewModel के बंद होने पर यह कोरूटीन अपने-आप रद्द हो जाएगी.onCleared
viewModelScope
मेंDispatchers.Main
का डिफ़ॉल्ट डिस्पैचर होता है. इसलिए, यह कोरूटीन मुख्य थ्रेड में लॉन्च किया जाएगा. हम बाद में देखेंगे कि अलग-अलग थ्रेड का इस्तेमाल कैसे किया जाता है.delay
एकsuspend
फ़ंक्शन है. Android Studio में, बाईं ओर गटर में मौजूदआइकॉन से यह पता चलता है. यह कोरूटीन मुख्य थ्रेड पर चलता है. हालांकि,
delay
एक सेकंड के लिए थ्रेड को ब्लॉक नहीं करेगा. इसके बजाय, डिस्पैचर, अगली स्टेटमेंट पर एक सेकंड में फिर से शुरू होने के लिए, कोरूटीन को शेड्यूल करेगा.
इसे चलाएं. मुख्य व्यू पर क्लिक करने के एक सेकंड बाद, आपको एक स्नैकबार दिखेगा.
अगले सेक्शन में, हम इस फ़ंक्शन को टेस्ट करने के तरीके के बारे में जानेंगे.
इस एक्सरसाइज़ में, आपको अभी लिखे गए कोड के लिए एक टेस्ट लिखना होगा. इस एक्सरसाइज़ में, kotlinx-coroutines-test लाइब्रेरी का इस्तेमाल करके, Dispatchers.Main
पर चल रहे कोरूटीन को टेस्ट करने का तरीका बताया गया है. इस कोडलैब में बाद में, आपको एक ऐसा टेस्ट लागू करना होगा जो सीधे तौर पर कोरुटीन के साथ इंटरैक्ट करता है.
मौजूदा कोड की समीक्षा करना
androidTest
फ़ोल्डर में MainViewModelTest.kt
खोलें.
MainViewModelTest.kt
class MainViewModelTest {
@get:Rule
val coroutineScope = MainCoroutineScopeRule()
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
lateinit var subject: MainViewModel
@Before
fun setup() {
subject = MainViewModel(
TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("initial")
))
}
}
नियम, JUnit में किसी टेस्ट को चलाने से पहले और बाद में कोड चलाने का एक तरीका है. डिवाइस से बाहर किए गए टेस्ट में MainViewModel को टेस्ट करने की अनुमति देने के लिए, दो नियमों का इस्तेमाल किया जाता है:
InstantTaskExecutorRule
एक JUnit नियम है, जोLiveData
को कॉन्फ़िगर करता है, ताकि हर टास्क को सिंक्रोनस तरीके से एक्ज़ीक्यूट किया जा सकेMainCoroutineScopeRule
इस कोडबेस में एक कस्टम नियम है. यहDispatchers.Main
कोkotlinx-coroutines-test
सेTestCoroutineDispatcher
का इस्तेमाल करने के लिए कॉन्फ़िगर करता है. इससे टेस्ट के लिए वर्चुअल-क्लॉक को आगे बढ़ाया जा सकता है. साथ ही, कोड को यूनिट टेस्ट मेंDispatchers.Main
का इस्तेमाल करने की अनुमति मिलती है.
setup
तरीके में, टेस्टिंग फ़ेक का इस्तेमाल करके setup
का नया इंस्टेंस बनाया जाता है. ये स्टार्टर कोड में दिए गए नेटवर्क और डेटाबेस के फ़ेक इंप्लीमेंटेशन होते हैं. इनसे असली नेटवर्क या डेटाबेस का इस्तेमाल किए बिना टेस्ट लिखने में मदद मिलती है.MainViewModel
इस टेस्ट के लिए, फ़ेक ऑब्जेक्ट की ज़रूरत सिर्फ़ MainViewModel
की डिपेंडेंसी को पूरा करने के लिए होती है. इस कोड लैब में बाद में, फ़ेक को अपडेट करके को-रूटीन के साथ काम करने की सुविधा जोड़ी जाएगी.
कोरूटीन को कंट्रोल करने वाला टेस्ट लिखना
एक नया टेस्ट जोड़ें. इससे यह पक्का किया जा सकेगा कि मुख्य व्यू पर क्लिक करने के एक सेकंड बाद, टैप अपडेट हो गए हैं:
MainViewModelTest.kt
@Test
fun whenMainClicked_updatesTaps() {
subject.onMainViewClicked()
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("0 taps")
coroutineScope.advanceTimeBy(1000)
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("1 taps")
}
onMainViewClicked
को कॉल करने पर, अभी-अभी बनाई गई कोरूटीन लॉन्च हो जाएगी. इस जांच से पता चलता है कि onMainViewClicked
को कॉल करने के तुरंत बाद, टैप का टेक्स्ट "0 टैप" पर सेट हो जाता है. इसके बाद, एक सेकंड बाद यह "1 टैप" पर अपडेट हो जाता है.
यह टेस्ट, onMainViewClicked
से लॉन्च की गई को-रूटीन के एक्ज़ीक्यूशन को कंट्रोल करने के लिए, virtual-time का इस्तेमाल करता है. MainCoroutineScopeRule
की मदद से, Dispatchers.Main
पर लॉन्च की गई को-रूटीन को रोका जा सकता है, फिर से शुरू किया जा सकता है या कंट्रोल किया जा सकता है. यहां हम advanceTimeBy(1_000)
को कॉल कर रहे हैं. इससे मुख्य डिस्पैचर, उन को-रूटीन को तुरंत एक्ज़ीक्यूट कर देगा जिन्हें एक सेकंड बाद फिर से शुरू होना है.
यह टेस्ट पूरी तरह से डिटरमिनिस्टिक है. इसका मतलब है कि यह हमेशा एक ही तरीके से काम करेगा. साथ ही, Dispatchers.Main
पर लॉन्च की गई को-रूटीन के एक्ज़ीक्यूशन पर इसका पूरा कंट्रोल होता है. इसलिए, इसे वैल्यू सेट होने के लिए एक सेकंड तक इंतज़ार नहीं करना पड़ता.
मौजूदा टेस्ट चलाना
- संदर्भ मेन्यू खोलने के लिए, अपने एडिटर में क्लास के नाम
MainViewModelTest
पर राइट क्लिक करें. - कॉन्टेक्स्ट मेन्यू में जाकर,
'MainViewModelTest' चलाएं को चुनें
- आने वाले समय में टेस्ट चलाने के लिए, टूलबार में मौजूद
बटन के बगल में मौजूद कॉन्फ़िगरेशन में जाकर, इस टेस्ट कॉन्फ़िगरेशन को चुना जा सकता है. डिफ़ॉल्ट रूप से, कॉन्फ़िगरेशन को MainViewModelTest कहा जाएगा.
आपको टेस्ट पास होने का मैसेज दिखेगा! इसे चलने में एक सेकंड से भी कम समय लगता है.
अगले अभ्यास में, आपको मौजूदा कॉलबैक एपीआई से कोरूटीन का इस्तेमाल करने का तरीका बताया जाएगा.
इस चरण में, आपको कोरूटीन का इस्तेमाल करने के लिए, किसी रिपॉज़िटरी को बदलना होगा. इसके लिए, हम ViewModel
, Repository
, Room
, और Retrofit
में को-रूटीन जोड़ेंगे.
को-रूटीन का इस्तेमाल शुरू करने से पहले, यह समझना ज़रूरी है कि आर्किटेक्चर का हर हिस्सा किस काम के लिए ज़िम्मेदार है.
MainDatabase
, Room का इस्तेमाल करके डेटाबेस लागू करता है. यहTitle
को सेव और लोड करता है.MainNetwork
एक नेटवर्क एपीआई लागू करता है, जो नया टाइटल फ़ेच करता है. यह टाइटल फ़ेच करने के लिए, Retrofit का इस्तेमाल करता है.Retrofit
को इस तरह कॉन्फ़िगर किया गया है कि वह गड़बड़ियां या मॉक डेटा को रैंडम तरीके से दिखाता है. हालांकि, यह इस तरह काम करता है जैसे कि यह असली नेटवर्क अनुरोध कर रहा हो.TitleRepository
नेटवर्क और डेटाबेस से मिले डेटा को मिलाकर, टाइटल को फ़ेच करने या रीफ़्रेश करने के लिए एक ही एपीआई लागू करता है.MainViewModel
स्क्रीन की स्थिति को दिखाता है और इवेंट हैंडल करता है. इससे रिपॉज़िटरी को यह पता चलेगा कि जब उपयोगकर्ता स्क्रीन पर टैप करे, तब टाइटल को रीफ़्रेश करना है.
नेटवर्क अनुरोध, यूज़र इंटरफ़ेस (यूआई) इवेंट से ट्रिगर होता है. साथ ही, हमें इनके आधार पर कोरूटीन शुरू करना है. इसलिए, कोरूटीन का इस्तेमाल शुरू करने के लिए सबसे सही जगह ViewModel
है.
कॉलबैक वर्शन
refreshTitle
के एलान को देखने के लिए, MainViewModel.kt
खोलें.
MainViewModel.kt
/**
* Update title text via this LiveData
*/
val title = repository.title
// ... other code ...
/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle() {
// TODO: Convert refreshTitle to use coroutines
_spinner.value = true
repository.refreshTitleWithCallbacks(object: TitleRefreshCallback {
override fun onCompleted() {
_spinner.postValue(false)
}
override fun onError(cause: Throwable) {
_snackBar.postValue(cause.message)
_spinner.postValue(false)
}
})
}
जब भी उपयोगकर्ता स्क्रीन पर क्लिक करेगा, तब इस फ़ंक्शन को कॉल किया जाएगा. इससे रिपॉज़िटरी, टाइटल को रीफ़्रेश करेगी और नए टाइटल को डेटाबेस में लिखेगी.
इस तरीके में, कॉलबैक का इस्तेमाल इन कामों के लिए किया जाता है:
- क्वेरी शुरू करने से पहले, यह
_spinner.value = true
के साथ एक लोडिंग स्पिनर दिखाता है - नतीजा मिलने पर, यह
_spinner.value = false
के साथ लोडिंग स्पिनर को हटा देता है - अगर इसे कोई गड़बड़ी मिलती है, तो यह स्नैकबार को दिखाने के लिए कहता है और स्पिनर को हटा देता है
ध्यान दें कि onCompleted
कॉलबैक में title
पास नहीं किया जाता है. हम सभी टाइटल को Room
डेटाबेस में लिखते हैं. इसलिए, यूज़र इंटरफ़ेस (यूआई) मौजूदा टाइटल को अपडेट करता है. इसके लिए, वह LiveData
को देखता है, जिसे Room
अपडेट करता है.
कोरूटीन के अपडेट में, हम इस व्यवहार को पहले जैसा ही रखेंगे. यूज़र इंटरफ़ेस (यूआई) को अपने-आप अपडेट रखने के लिए, Room
डेटाबेस जैसे ऑब्ज़र्वेबल डेटा सोर्स का इस्तेमाल करना एक अच्छा तरीका है.
कोरूटीन वर्शन
आइए, कोरूटीन की मदद से refreshTitle
को फिर से लिखते हैं!
हमें इसकी तुरंत ज़रूरत होगी. इसलिए, अपनी रिपॉज़िटरी (TitleRespository.kt
) में एक खाली सस्पेंड फ़ंक्शन बनाते हैं. एक नया फ़ंक्शन तय करें, जो suspend
ऑपरेटर का इस्तेमाल करके Kotlin को यह बताए कि यह कोरूटीन के साथ काम करता है.
TitleRepository.kt
suspend fun refreshTitle() {
// TODO: Refresh from network and write to database
delay(500)
}
इस कोडलैब को पूरा करने के बाद, आपको इसे अपडेट करना होगा. इसके लिए, Retrofit और Room का इस्तेमाल करके नया टाइटल फ़ेच करें और कोरूटीन का इस्तेमाल करके उसे डेटाबेस में लिखें. फ़िलहाल, यह 500 मिलीसेकंड तक काम करने का नाटक करेगा और फिर आगे बढ़ेगा.
MainViewModel
में, refreshTitle
के कॉलबैक वर्शन को ऐसे वर्शन से बदलें जो नया कोरूटीन लॉन्च करता हो:
MainViewModel.kt
/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle() {
viewModelScope.launch {
try {
_spinner.value = true
repository.refreshTitle()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}
आइए, इस फ़ंक्शन के बारे में जानते हैं:
viewModelScope.launch {
टैप की संख्या को अपडेट करने वाले कोरूटीन की तरह ही, viewModelScope
में एक नया कोरूटीन लॉन्च करके शुरू करें. इससे Dispatchers.Main
का इस्तेमाल होगा, जो ठीक है. refreshTitle
, नेटवर्क अनुरोध और डेटाबेस क्वेरी करेगा. हालांकि, यह main-safe इंटरफ़ेस को दिखाने के लिए कोरूटीन का इस्तेमाल कर सकता है. इसका मतलब है कि इसे मुख्य थ्रेड से कॉल करना सुरक्षित होगा.
हम viewModelScope
का इस्तेमाल कर रहे हैं. इसलिए, जब उपयोगकर्ता इस स्क्रीन से हट जाएगा, तो इस कोरूटीन से शुरू किया गया काम अपने-आप रद्द हो जाएगा. इसका मतलब है कि यह अतिरिक्त नेटवर्क अनुरोध या डेटाबेस क्वेरी नहीं करेगा.
कोड की अगली कुछ लाइनें, repository
में refreshTitle
को कॉल करती हैं.
try {
_spinner.value = true
repository.refreshTitle()
}
यह कोरूटीन कोई भी कार्रवाई करने से पहले, लोडिंग स्पिनर शुरू करता है. इसके बाद, यह refreshTitle
को सामान्य फ़ंक्शन की तरह कॉल करता है. हालांकि, refreshTitle
एक सस्पेंडिंग फ़ंक्शन है. इसलिए, यह सामान्य फ़ंक्शन से अलग तरीके से काम करता है.
हमें कॉलबैक पास करने की ज़रूरत नहीं है. जब तक refreshTitle
इसे फिर से शुरू नहीं करता, तब तक कोरूटीन निलंबित रहेगा. यह सामान्य ब्लॉकिंग फ़ंक्शन कॉल की तरह दिखता है. हालांकि, यह मुख्य थ्रेड को बिना ब्लॉक किए, नेटवर्क और डेटाबेस क्वेरी पूरी होने तक अपने-आप इंतज़ार करेगा.
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
निलंबित किए गए फ़ंक्शन में अपवाद, सामान्य फ़ंक्शन में गड़बड़ियों की तरह ही काम करते हैं. अगर किसी निलंबित फ़ंक्शन में गड़बड़ी होती है, तो यह गड़बड़ी कॉल करने वाले को दिखेगी. इसलिए, भले ही ये दोनों अलग-अलग तरीके से काम करते हों, लेकिन इन्हें हैंडल करने के लिए, सामान्य try/catch ब्लॉक का इस्तेमाल किया जा सकता है. यह सुविधा इसलिए काम की है, क्योंकि इससे आपको गड़बड़ी ठीक करने के लिए, भाषा की इन-बिल्ट सुविधा का इस्तेमाल करने का विकल्प मिलता है. इसके लिए, आपको हर कॉलबैक के लिए गड़बड़ी ठीक करने की कस्टम सुविधा बनाने की ज़रूरत नहीं होती.
इसके अलावा, अगर किसी कोरूटीन से कोई अपवाद थ्रो किया जाता है, तो वह कोरूटीन डिफ़ॉल्ट रूप से अपने पैरंट को रद्द कर देगा. इसका मतलब है कि मिलते-जुलते कई टास्क एक साथ रद्द किए जा सकते हैं.
इसके बाद, फ़ाइनली ब्लॉक में हम यह पक्का कर सकते हैं कि क्वेरी चलने के बाद, स्पिनर हमेशा बंद रहे.
start कॉन्फ़िगरेशन को चुनकर और फिर दबाकर, ऐप्लिकेशन को फिर से चलाएँ. किसी भी जगह पर टैप करने पर, आपको लोडिंग स्पिनर दिखेगा. टाइटल में कोई बदलाव नहीं होगा, क्योंकि हमने अब तक अपने नेटवर्क या डेटाबेस को कनेक्ट नहीं किया है.
अगले अभ्यास में, आपको रिपॉज़िटरी को अपडेट करना होगा, ताकि वह काम कर सके.
इस एक्सरसाइज़ में, आपको यह पता चलेगा कि TitleRepository
का वर्किंग वर्शन लागू करने के लिए, किसी कोरूटीन के थ्रेड को कैसे स्विच किया जाता है.
refreshTitle में मौजूद मौजूदा कॉलबैक कोड की समीक्षा करें
TitleRepository.kt
खोलें और कॉलबैक पर आधारित मौजूदा तरीके को देखें.
TitleRepository.kt
// TitleRepository.kt
fun refreshTitleWithCallbacks(titleRefreshCallback: TitleRefreshCallback) {
// This request will be run on a background thread by retrofit
BACKGROUND.submit {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle().execute()
if (result.isSuccessful) {
// Save it to database
titleDao.insertTitle(Title(result.body()!!))
// Inform the caller the refresh is completed
titleRefreshCallback.onCompleted()
} else {
// If it's not successful, inform the callback of the error
titleRefreshCallback.onError(
TitleRefreshError("Unable to refresh title", null))
}
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
titleRefreshCallback.onError(
TitleRefreshError("Unable to refresh title", cause))
}
}
}
TitleRepository.kt
में, refreshTitleWithCallbacks
तरीके को लागू किया जाता है. इसमें कॉलबैक का इस्तेमाल किया जाता है, ताकि कॉलर को लोडिंग और गड़बड़ी की स्थिति के बारे में बताया जा सके.
रीफ़्रेश करने की सुविधा को लागू करने के लिए, यह फ़ंक्शन कई काम करता है.
BACKGROUND
ExecutorService
की मदद से, किसी दूसरे थ्रेड पर स्विच करना- ब्लॉक करने की
execute()
विधि का इस्तेमाल करके,fetchNextTitle
नेटवर्क अनुरोध चलाएं. इससे मौजूदा थ्रेड में नेटवर्क अनुरोध चलेगा. इस मामले में,BACKGROUND
में मौजूद थ्रेड में से कोई एक थ्रेड. - अगर नतीजा सही है, तो उसे
insertTitle
की मदद से डेटाबेस में सेव करें औरonCompleted()
तरीके को कॉल करें. - अगर नतीजे में कोई गड़बड़ी हुई है या कोई अपवाद है, तो onError तरीके को कॉल करें. इससे कॉल करने वाले को रीफ़्रेश नहीं होने के बारे में सूचना मिलेगी.
यह कॉलबैक आधारित लागू करने का तरीका main-safe है, क्योंकि इससे मुख्य थ्रेड ब्लॉक नहीं होगी. हालांकि, काम पूरा होने पर कॉलर को इसकी सूचना देने के लिए, इसे कॉलबैक का इस्तेमाल करना होगा. यह उस BACKGROUND
थ्रेड पर कॉलबैक भी कॉल करता है जिस पर यह स्विच हुआ है.
कोरूटीन से कॉल ब्लॉक करना
नेटवर्क या डेटाबेस में कोरूटीन का इस्तेमाल किए बिना, हम कोरूटीन का इस्तेमाल करके इस कोड को main-safe बना सकते हैं. इससे हमें कॉलबैक से छुटकारा मिल जाएगा. साथ ही, हम उस थ्रेड को नतीजे वापस भेज पाएंगे जिसने इसे शुरू में कॉल किया था.
इस पैटर्न का इस्तेमाल तब किया जा सकता है, जब आपको किसी को-रूटीन के अंदर से सीपीयू का ज़्यादा इस्तेमाल करने वाले काम करने हों. जैसे, बड़ी सूची को क्रम से लगाना और फ़िल्टर करना या डिस्क से डेटा पढ़ना.
किसी भी डिस्पैचर के बीच स्विच करने के लिए, कोरूटीन withContext
का इस्तेमाल करती हैं. withContext
को कॉल करने पर, सिर्फ़ lambda के लिए दूसरे डिस्पैचर पर स्विच किया जाता है. इसके बाद, यह उस डिस्पैचर पर वापस आ जाता है जिसने इसे lambda के नतीजे के साथ कॉल किया था.
डिफ़ॉल्ट रूप से, Kotlin कोरूटीन तीन डिस्पैचर उपलब्ध कराता है: Main
, IO
, और Default
. IO डिसपैचर को नेटवर्क या डिस्क से डेटा पढ़ने जैसे IO टास्क के लिए ऑप्टिमाइज़ किया गया है. वहीं, डिफ़ॉल्ट डिसपैचर को सीपीयू इंटेंसिव टास्क के लिए ऑप्टिमाइज़ किया गया है.
TitleRepository.kt
suspend fun refreshTitle() {
// interact with *blocking* network and IO calls from a coroutine
withContext(Dispatchers.IO) {
val result = try {
// Make network request using a blocking call
network.fetchNextTitle().execute()
} catch (cause: Throwable) {
// If the network throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
if (result.isSuccessful) {
// Save it to database
titleDao.insertTitle(Title(result.body()!!))
} else {
// If it's not successful, inform the callback of the error
throw TitleRefreshError("Unable to refresh title", null)
}
}
}
इस तरीके में, नेटवर्क और डेटाबेस के लिए ब्लॉकिंग कॉल का इस्तेमाल किया जाता है. हालांकि, यह कॉलबैक वर्शन से थोड़ा आसान है.
यह कोड अब भी ब्लॉकिंग कॉल का इस्तेमाल करता है. execute()
और insertTitle(...)
को कॉल करने पर, वह थ्रेड ब्लॉक हो जाएगी जिसमें यह कोरूटीन चल रहा है. हालांकि, Dispatchers.IO
का इस्तेमाल करके withContext
पर स्विच करने से, हम IO डिस्पैचर में मौजूद एक थ्रेड को ब्लॉक कर रहे हैं. इस फ़ंक्शन को कॉल करने वाला कोरूटीन, Dispatchers.Main
पर चल रहा होगा. यह तब तक निलंबित रहेगा, जब तक withContext
लैम्ब्डा पूरा नहीं हो जाता.
कॉल बैक वाले वर्शन की तुलना में, इसमें दो मुख्य अंतर हैं:
withContext
, नतीजे को वापस उस डिसपैचर को भेजता है जिसने इसे कॉल किया था. इस मामले में, यह डिसपैचरDispatchers.Main
है. कॉलबैक वर्शन,BACKGROUND
एक्ज़ीक्यूटर सेवा में थ्रेड पर कॉलबैक को कॉल करता है.- कॉलर को इस फ़ंक्शन में कॉलबैक पास करने की ज़रूरत नहीं है. नतीजा या गड़बड़ी की जानकारी पाने के लिए, वे निलंबित करने और फिर से शुरू करने की सुविधा का इस्तेमाल कर सकते हैं.
ऐप्लिकेशन को फिर से चलाएं
ऐप्लिकेशन को फिर से चलाने पर, आपको दिखेगा कि नई को-रूटीन पर आधारित सुविधा, नेटवर्क से नतीजे लोड कर रही है!
अगले चरण में, आपको Room और Retrofit में को-रूटीन इंटिग्रेट करने होंगे.
कोरूटीन इंटिग्रेशन को जारी रखने के लिए, हम Room और Retrofit के स्टेबल वर्शन में, निलंबन फ़ंक्शन के लिए सहायता का इस्तेमाल करेंगे. इसके बाद, निलंबन फ़ंक्शन का इस्तेमाल करके, अभी-अभी लिखे गए कोड को काफ़ी हद तक आसान बना देंगे.
Room में कोरूटीन
सबसे पहले MainDatabase.kt
को खोलें और insertTitle
को सस्पेंड फ़ंक्शन बनाएं:
MainDatabase.kt
// add the suspend modifier to the existing insertTitle
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTitle(title: Title)
ऐसा करने पर, Room आपकी क्वेरी को main-safe बना देगा और उसे बैकग्राउंड थ्रेड पर अपने-आप लागू कर देगा. हालांकि, इसका यह भी मतलब है कि इस क्वेरी को सिर्फ़ कोरूटीन के अंदर से कॉल किया जा सकता है.
बस, Room में कोरूटीन का इस्तेमाल करने के लिए आपको इतना ही करना है. यह काफ़ी अच्छा है.
Retrofit में कोरूटीन
अब देखते हैं कि Retrofit के साथ कोरूटीन को कैसे इंटिग्रेट किया जाता है. MainNetwork.kt
खोलें और fetchNextTitle
को सस्पेंड फ़ंक्शन में बदलें.
MainNetwork.kt
// add suspend modifier to the existing fetchNextTitle
// change return type from Call<String> to String
interface MainNetwork {
@GET("next_title.json")
suspend fun fetchNextTitle(): String
}
Retrofit के साथ निलंबन फ़ंक्शन इस्तेमाल करने के लिए, आपको ये दो काम करने होंगे:
- फ़ंक्शन में सस्पेंड मॉडिफ़ायर जोड़ना
- रिटर्न टाइप से
Call
रैपर हटाएं. यहां हमString
को वापस ला रहे हैं. हालांकि, आपके पास JSON-बैक किए गए कॉम्प्लेक्स टाइप को भी वापस लाने का विकल्प है. अगर आपको अब भी रेट्रोफ़िट के पूरेResult
का ऐक्सेस देना है, तो निलंबित करने के फ़ंक्शन सेString
के बजायResult<String>
वापस किया जा सकता है.
Retrofit, सस्पेंड फ़ंक्शन को अपने-आप main-safe बना देगा, ताकि उन्हें सीधे Dispatchers.Main
से कॉल किया जा सके.
Room और Retrofit का इस्तेमाल करना
Room और Retrofit में अब suspend फ़ंक्शन काम करते हैं. इसलिए, हम इनका इस्तेमाल अपनी रिपॉज़िटरी से कर सकते हैं. TitleRepository.kt
खोलें और देखें कि सस्पेंडिंग फ़ंक्शन का इस्तेमाल करने से लॉजिक कितना आसान हो जाता है. यह ब्लॉकिंग वर्शन की तुलना में भी आसान है:
TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle()
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}
वाह, यह तो बहुत छोटा है. क्या हुआ? निलंबित करने और फिर से शुरू करने की सुविधा का इस्तेमाल करने से, कोड छोटा हो जाता है. Retrofit की मदद से, यहां Call
के बजाय String
या User
ऑब्जेक्ट जैसे रिटर्न टाइप का इस्तेमाल किया जा सकता है. ऐसा करना सुरक्षित है, क्योंकि सस्पेंड फ़ंक्शन के अंदर, Retrofit
बैकग्राउंड थ्रेड पर नेटवर्क अनुरोध को चला सकता है. साथ ही, कॉल पूरा होने पर कोरूटीन को फिर से शुरू कर सकता है.
इससे भी अच्छी बात यह है कि हमने withContext
को हटा दिया है. Room और Retrofit, दोनों ही main-safe सस्पेंडिंग फ़ंक्शन उपलब्ध कराते हैं. इसलिए, Dispatchers.Main
से इस एसिंक काम को मैनेज करना सुरक्षित है.
कंपाइलर से जुड़ी गड़बड़ियां ठीक करना
कोरूटीन पर स्विच करने के लिए, फ़ंक्शन के सिग्नेचर में बदलाव करना ज़रूरी है. ऐसा इसलिए, क्योंकि किसी सामान्य फ़ंक्शन से सस्पेंड फ़ंक्शन को कॉल नहीं किया जा सकता. इस चरण में suspend
मॉडिफ़ायर जोड़ने पर, कंपाइलर से जुड़ी कुछ गड़बड़ियां जनरेट हुई हैं. इनसे पता चलता है कि किसी फ़ंक्शन को असली प्रोजेक्ट में निलंबित करने पर क्या होगा.
प्रोजेक्ट पर जाएं और बनाए गए फ़ंक्शन को निलंबित करने के लिए, कंपाइलर से जुड़ी गड़बड़ियों को ठीक करें. यहां हर समस्या को तुरंत हल करने के तरीके दिए गए हैं:
TestingFakes.kt
निलंबन के नए मॉडिफ़ायर के साथ काम करने के लिए, फ़ेक खातों की जांच करने की सुविधा को अपडेट करें.
TitleDaoFake
- Alt-Enter दबाकर, हैरारकी में मौजूद सभी फ़ंक्शन में सस्पेंड किए गए मॉडिफ़ायर जोड़ें
MainNetworkFake
- Alt-Enter दबाकर, हैरारकी में मौजूद सभी फ़ंक्शन में सस्पेंड किए गए मॉडिफ़ायर जोड़ें
fetchNextTitle
को इस फ़ंक्शन से बदलें
override suspend fun fetchNextTitle() = result
MainNetworkCompletableFake
- Alt-Enter दबाकर, हैरारकी में मौजूद सभी फ़ंक्शन में सस्पेंड किए गए मॉडिफ़ायर जोड़ें
fetchNextTitle
को इस फ़ंक्शन से बदलें
override suspend fun fetchNextTitle() = completable.await()
TitleRepository.kt
refreshTitleWithCallbacks
फ़ंक्शन को मिटा दें, क्योंकि अब इसका इस्तेमाल नहीं किया जाता.
ऐप्लिकेशन चलाना
ऐप्लिकेशन को फिर से चलाएं. कंपाइल होने के बाद, आपको दिखेगा कि यह ViewModel से लेकर Room और Retrofit तक, हर जगह कोरूटीन का इस्तेमाल करके डेटा लोड कर रहा है!
बधाई हो, आपने इस ऐप्लिकेशन को पूरी तरह से को-रूटीन का इस्तेमाल करने के लिए स्वैप कर दिया है! आखिर में, हम इस बारे में बात करेंगे कि हमने जो काम किया है उसे कैसे टेस्ट किया जाए.
इस एक्सरसाइज़ में, आपको एक ऐसा टेस्ट लिखना है जो सीधे तौर पर suspend
फ़ंक्शन को कॉल करता है.
refreshTitle
को सार्वजनिक एपीआई के तौर पर दिखाया जाता है. इसलिए, इसे सीधे तौर पर टेस्ट किया जाएगा. इससे यह पता चलेगा कि टेस्ट से कोरूटीन फ़ंक्शन को कैसे कॉल किया जाता है.
यहां refreshTitle
फ़ंक्शन दिया गया है, जिसे आपने पिछली कसरत में लागू किया था:
TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle()
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}
ऐसा टेस्ट लिखें जो सस्पेंड फ़ंक्शन को कॉल करता हो
test
फ़ोल्डर में मौजूद TitleRepositoryTest.kt
को खोलो. इस फ़ोल्डर में दो TODO हैं.
पहले टेस्ट whenRefreshTitleSuccess_insertsRows
से refreshTitle
को कॉल करने की कोशिश करें.
@Test
fun whenRefreshTitleSuccess_insertsRows() {
val subject = TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("title")
)
subject.refreshTitle()
}
refreshTitle
एक suspend
फ़ंक्शन है. इसलिए, Kotlin को यह नहीं पता कि इसे कोरूटीन या किसी अन्य सस्पेंड फ़ंक्शन के अलावा, किसी और तरीके से कैसे कॉल किया जाए. साथ ही, आपको कंपाइलर से जुड़ी गड़बड़ी का यह मैसेज मिलेगा: "Suspend function refreshTitle should be called only from a coroutine or another suspend function."
टेस्ट रनर को कोरूटीन के बारे में कुछ भी नहीं पता होता. इसलिए, हम इस टेस्ट को सस्पेंड फ़ंक्शन नहीं बना सकते. हम ViewModel
में CoroutineScope
का इस्तेमाल करके, launch
को शुरू कर सकते हैं. हालांकि, टेस्ट को पूरा होने से पहले, को-रूटीन चलाने की ज़रूरत होती है. टेस्ट फ़ंक्शन के नतीजे मिलने के बाद, टेस्ट खत्म हो जाता है. launch
से शुरू होने वाली को-रूटीन, एसिंक्रोनस कोड होती हैं. ये आने वाले समय में कभी भी पूरी हो सकती हैं. इसलिए, एसिंक्रोनस कोड की जांच करने के लिए, आपको टेस्ट को यह बताने का कोई तरीका चाहिए कि आपकी कोराउटीन पूरी होने तक इंतज़ार करें. launch
एक नॉन-ब्लॉकिंग कॉल है. इसका मतलब है कि यह तुरंत जवाब देता है और फ़ंक्शन के जवाब देने के बाद भी को-रूटीन को चलाना जारी रख सकता है. इसका इस्तेमाल टेस्ट में नहीं किया जा सकता. उदाहरण के लिए:
@Test
fun whenRefreshTitleSuccess_insertsRows() {
val subject = TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("title")
)
// launch starts a coroutine then immediately returns
GlobalScope.launch {
// since this is asynchronous code, this may be called *after* the test completes
subject.refreshTitle()
}
// test function returns immediately, and
// doesn't see the results of refreshTitle
}
यह टेस्ट कभी-कभी फ़ेल हो जाएगा. launch
को किया गया कॉल तुरंत वापस आ जाएगा और टेस्ट केस के बाकी हिस्सों के साथ ही एक्ज़ीक्यूट हो जाएगा. टेस्ट को यह पता नहीं चलता कि refreshTitle
अभी तक चला है या नहीं. साथ ही, डेटाबेस अपडेट होने की जांच जैसे कोई भी दावे भरोसेमंद नहीं होंगे. अगर refreshTitle
ने कोई अपवाद दिया है, तो टेस्ट कॉल स्टैक में उसे नहीं दिखाया जाएगा. इसके बजाय, इसे GlobalScope
के अनकॉट एक्सेप्शन हैंडलर में थ्रो किया जाएगा.
लाइब्रेरी kotlinx-coroutines-test
में runBlockingTest
फ़ंक्शन होता है. यह सस्पेंड फ़ंक्शन को कॉल करते समय ब्लॉक करता है. जब runBlockingTest
, किसी सस्पेंड फ़ंक्शन या नई कोरूटीन को कॉल करता है, तो यह डिफ़ॉल्ट रूप से उसे तुरंत एक्ज़ीक्यूट करता है.launches
इसे सस्पेंड फ़ंक्शन और कोरूटीन को सामान्य फ़ंक्शन कॉल में बदलने के तरीके के तौर पर देखा जा सकता है.
इसके अलावा, runBlockingTest
आपके लिए, बिना हैंडल किए गए अपवादों को फिर से थ्रो करेगा. इससे यह जांच करना आसान हो जाता है कि कोई कोरूटीन कब अपवाद दे रहा है.
एक ही कोरूटीन के साथ टेस्ट लागू करना
कॉल को refreshTitle
से runBlockingTest
में रैप करें और subject.refreshTitle() से GlobalScope.launch
रैपर हटाएं.
TitleRepositoryTest.kt
@Test
fun whenRefreshTitleSuccess_insertsRows() = runBlockingTest {
val titleDao = TitleDaoFake("title")
val subject = TitleRepository(
MainNetworkFake("OK"),
titleDao
)
subject.refreshTitle()
Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK")
}
यह टेस्ट, फ़ेक डेटा का इस्तेमाल करके यह जांच करता है कि refreshTitle
ने डेटाबेस में "OK" डाला है या नहीं.
जब टेस्ट कॉल runBlockingTest
करता है, तो यह तब तक ब्लॉक रहेगा, जब तक runBlockingTest
से शुरू की गई कोराटीन पूरी नहीं हो जाती. इसके बाद, जब हम refreshTitle
को कॉल करते हैं, तो यह निलंबित करने और फिर से शुरू करने के सामान्य तरीके का इस्तेमाल करता है. इससे डेटाबेस की लाइन को हमारे फ़ेक में जोड़ने का इंतज़ार किया जाता है.
टेस्ट को-रूटीन पूरा होने के बाद, runBlockingTest
वापस आ जाता है.
टाइमआउट टेस्ट लिखना
हमें नेटवर्क अनुरोध के लिए, कम समय वाला टाइमआउट जोड़ना है. आइए, पहले टेस्ट लिखते हैं. इसके बाद, टाइमआउट लागू करते हैं. नया टेस्ट बनाएं:
TitleRepositoryTest.kt
@Test(expected = TitleRefreshError::class)
fun whenRefreshTitleTimeout_throws() = runBlockingTest {
val network = MainNetworkCompletableFake()
val subject = TitleRepository(
network,
TitleDaoFake("title")
)
launch {
subject.refreshTitle()
}
advanceTimeBy(5_000)
}
इस टेस्ट में, दिए गए फ़र्ज़ी MainNetworkCompletableFake
का इस्तेमाल किया जाता है. यह एक नेटवर्क फ़र्ज़ी है, जिसे कॉल करने वालों को तब तक निलंबित करने के लिए डिज़ाइन किया गया है, जब तक टेस्ट उन्हें जारी नहीं रखता. जब refreshTitle
नेटवर्क अनुरोध करने की कोशिश करेगा, तो यह हमेशा के लिए रुक जाएगा. ऐसा इसलिए, क्योंकि हमें टाइमआउट की जांच करनी है.
इसके बाद, यह refreshTitle
को कॉल करने के लिए एक अलग कोरूटीन लॉन्च करता है. यह टाइमआउट की जांच करने का एक अहम हिस्सा है. टाइमआउट, उस कोराउटीन से अलग कोराउटीन में होना चाहिए जिसे runBlockingTest
बनाता है. ऐसा करने से, हम अगली लाइन को कॉल कर सकते हैं. advanceTimeBy(5_000)
इससे समय 5 सेकंड आगे बढ़ जाएगा और दूसरी को-रूटीन का समय खत्म हो जाएगा.
यह पूरी तरह से टाइम आउट टेस्ट है. टाइम आउट लागू करने के बाद, यह टेस्ट पास हो जाएगा.
इसे अभी चलाकर देखें कि क्या होता है:
Caused by: kotlinx.coroutines.test.UncompletedCoroutinesError: Test finished with active jobs: ["...]
runBlockingTest
की एक सुविधा यह है कि यह जांच पूरी होने के बाद, आपको कोरूटीन लीक नहीं करने देगा. अगर टेस्ट के आखिर में कोई भी ऐसी को-रूटीन मौजूद है जो पूरी नहीं हुई है, जैसे कि हमारी लॉन्च को-रूटीन, तो टेस्ट पूरा नहीं होगा.
टाइम आउट जोड़ना
TitleRepository
खोलें और नेटवर्क फ़ेच में पांच सेकंड का टाइमआउट जोड़ें. इसके लिए, withTimeout
फ़ंक्शन का इस्तेमाल करें:
TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = withTimeout(5_000) {
network.fetchNextTitle()
}
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}
जांच करें. टेस्ट चलाने पर, आपको सभी टेस्ट पास होने चाहिए!
अगली कसरत में, आपको कोरूटीन का इस्तेमाल करके हाई ऑर्डर फ़ंक्शन लिखने का तरीका बताया जाएगा.
इस एक्सरसाइज़ में, आपको MainViewModel
में मौजूद refreshTitle
को फिर से फ़ैक्टर करना होगा, ताकि डेटा लोड करने वाले सामान्य फ़ंक्शन का इस्तेमाल किया जा सके. इससे आपको कोरूटीन का इस्तेमाल करने वाले हाई-ऑर्डर फ़ंक्शन बनाने का तरीका पता चलेगा.
refreshTitle
का मौजूदा वर्शन काम करता है. हालांकि, हम डेटा लोड करने वाली एक सामान्य को-रूटीन बना सकते हैं, जो हमेशा स्पिनर दिखाती है. यह उस कोडबेस में मददगार हो सकता है जो कई इवेंट के जवाब में डेटा लोड करता है. साथ ही, यह पक्का करना चाहता है कि लोडिंग स्पिनर लगातार दिखे.
मौजूदा कोड की हर लाइन की समीक्षा करें. repository.refreshTitle()
को छोड़कर, बाकी सभी लाइनें स्पिनर दिखाने और गड़बड़ियां दिखाने के लिए बॉयलरप्लेट कोड हैं.
// MainViewModel.kt
fun refreshTitle() {
viewModelScope.launch {
try {
_spinner.value = true
// this is the only part that changes between sources
repository.refreshTitle()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}
हाई ऑर्डर फ़ंक्शन में कोरूटीन का इस्तेमाल करना
इस कोड को MainViewModel.kt में जोड़ें
MainViewModel.kt
private fun launchDataLoad(block: suspend () -> Unit): Job {
return viewModelScope.launch {
try {
_spinner.value = true
block()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}
अब इस हाई ऑर्डर फ़ंक्शन का इस्तेमाल करने के लिए, refreshTitle()
को फिर से व्यवस्थित करें.
MainViewModel.kt
fun refreshTitle() {
launchDataLoad {
repository.refreshTitle()
}
}
लोडिंग स्पिनर दिखाने और गड़बड़ियां दिखाने के लॉजिक को अलग करके, हमने डेटा लोड करने के लिए ज़रूरी अपने कोड को आसान बना दिया है. स्पिनर दिखाना या गड़बड़ी का मैसेज दिखाना, डेटा लोड करने की किसी भी प्रोसेस के लिए आसानी से सामान्य बनाया जा सकता है. हालांकि, असल डेटा सोर्स और डेस्टिनेशन को हर बार तय करना होता है.
इस ऐब्स्ट्रैक्शन को बनाने के लिए, launchDataLoad
एक आर्ग्युमेंट block
लेता है, जो एक सस्पेंड लैम्डा होता है. सस्पेंड lambda की मदद से, सस्पेंड फ़ंक्शन को कॉल किया जा सकता है. Kotlin, कोडलैब में इस्तेमाल किए गए को-रूटीन बिल्डर launch
और runBlocking
को इसी तरह लागू करता है.
// suspend lambda
block: suspend () -> Unit
सस्पेंड लैम्डा बनाने के लिए, suspend
कीवर्ड से शुरू करें. फ़ंक्शन ऐरो और रिटर्न टाइप Unit
, एलान को पूरा करते हैं.
आपको अक्सर अपने निलंबित लैम्डा के बारे में जानकारी देने की ज़रूरत नहीं होती. हालांकि, इस तरह के ऐब्स्ट्रैक्शन बनाने में ये मददगार हो सकते हैं, जो बार-बार इस्तेमाल होने वाले लॉजिक को शामिल करते हैं!
इस एक्सरसाइज़ में, आपको WorkManager से को-रूटीन पर आधारित कोड इस्तेमाल करने का तरीका बताया जाएगा.
WorkManager क्या है
Android पर, बैकग्राउंड में होने वाले ऐसे काम को कुछ समय के लिए रोकने के कई विकल्प मौजूद हैं. इस एक्सरसाइज़ में, WorkManager को कोरूटीन के साथ इंटिग्रेट करने का तरीका बताया गया है. WorkManager, बैकग्राउंड में किए जाने वाले टाले जा सकने वाले टास्क के लिए, एक ऐसी लाइब्रेरी है जो अलग-अलग वर्शन के साथ काम करती है. साथ ही, इसका इस्तेमाल ज़रूरत के हिसाब से किया जा सकता है और यह इस्तेमाल करने में आसान है. Android पर इन इस्तेमाल के उदाहरणों के लिए, WorkManager का इस्तेमाल करने का सुझाव दिया जाता है.
WorkManager, Android Jetpack का हिस्सा है. यह बैकग्राउंड में किए जाने वाले काम के लिए एक आर्किटेक्चर कॉम्पोनेंट है. इसके लिए, अवसर के हिसाब से और गारंटीड एक्ज़ीक्यूशन, दोनों की ज़रूरत होती है. अवसर के हिसाब से एक्ज़ीक्यूशन का मतलब है कि WorkManager आपका बैकग्राउंड काम जल्दी से जल्दी करेगा. गारंटी के साथ एक्ज़ीक्यूशन का मतलब है कि WorkManager, अलग-अलग स्थितियों में आपके काम को शुरू करने के लिए लॉजिक का इस्तेमाल करेगा. भले ही, आपने ऐप्लिकेशन बंद कर दिया हो.
इसलिए, WorkManager उन टास्क के लिए एक अच्छा विकल्प है जिन्हें पूरा करना ज़रूरी है.
WorkManager का इस्तेमाल करके किए जा सकने वाले कुछ टास्क के उदाहरण:
- लॉग अपलोड करना
- इमेज पर फ़िल्टर लगाना और इमेज सेव करना
- स्थानीय डेटा को समय-समय पर नेटवर्क के साथ सिंक करना
WorkManager के साथ कोरूटीन का इस्तेमाल करना
WorkManager, इस्तेमाल के अलग-अलग उदाहरणों के लिए, अपनी बेस ListanableWorker
क्लास के अलग-अलग वर्शन उपलब्ध कराता है.
सबसे आसान वर्कर क्लास की मदद से, WorkManager को कुछ सिंक्रोनस ऑपरेशन लागू करने की अनुमति दी जा सकती है. हालांकि, अब तक हमने अपने कोडबेस को कोरूटीन और सस्पेंड फ़ंक्शन का इस्तेमाल करने के लिए बदल दिया है. इसलिए, WorkManager का इस्तेमाल करने का सबसे अच्छा तरीका CoroutineWorker
क्लास है. इससे हम अपने doWork()
फ़ंक्शन को सस्पेंड फ़ंक्शन के तौर पर तय कर सकते हैं.
शुरू करने के लिए, RefreshMainDataWork
खोलें. यह पहले से ही CoroutineWorker
को बढ़ाता है. आपको doWork
लागू करना होगा.
suspend
doWork
फ़ंक्शन में, रिपॉज़िटरी से refreshTitle()
को कॉल करें और सही नतीजा दिखाएं!
TODO पूरा करने के बाद, कोड ऐसा दिखेगा:
override suspend fun doWork(): Result {
val database = getDatabase(applicationContext)
val repository = TitleRepository(network, database.titleDao)
return try {
repository.refreshTitle()
Result.success()
} catch (error: TitleRefreshError) {
Result.failure()
}
}
ध्यान दें कि CoroutineWorker.doWork()
एक सस्पेंडिंग फ़ंक्शन है. आसान Worker
क्लास के उलट, यह कोड आपके WorkManager कॉन्फ़िगरेशन में तय किए गए Executor पर नहीं चलता. इसके बजाय, यह coroutineContext
मेंबर (डिफ़ॉल्ट रूप से Dispatchers.Default
) में डिस्पैचर का इस्तेमाल करता है.
CoroutineWorker की जांच करना
टेस्टिंग के बिना कोई भी कोडबेस पूरा नहीं होना चाहिए.
WorkManager, आपकी Worker
क्लास को टेस्ट करने के लिए कई तरीके उपलब्ध कराता है. टेस्टिंग के ओरिजनल इन्फ़्रास्ट्रक्चर के बारे में ज़्यादा जानने के लिए, दस्तावेज़ पढ़ें.
WorkManager v2.1 में, एपीआई का एक नया सेट पेश किया गया है. इससे ListenableWorker
क्लास और CoroutineWorker को आसानी से टेस्ट किया जा सकता है. हम अपने कोड में इनमें से किसी एक नए एपीआई का इस्तेमाल करेंगे: TestListenableWorkerBuilder
.
हमारे नए टेस्ट को जोड़ने के लिए, androidTest
फ़ोल्डर में मौजूद RefreshMainDataWorkTest
फ़ाइल को अपडेट करें.
फ़ाइल का कॉन्टेंट यह है:
package com.example.android.kotlincoroutines.main
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestListenableWorkerBuilder
import com.example.android.kotlincoroutines.fakes.MainNetworkFake
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class RefreshMainDataWorkTest {
@Test
fun testRefreshMainDataWork() {
val fakeNetwork = MainNetworkFake("OK")
val context = ApplicationProvider.getApplicationContext<Context>()
val worker = TestListenableWorkerBuilder<RefreshMainDataWork>(context)
.setWorkerFactory(RefreshMainDataWork.Factory(fakeNetwork))
.build()
// Start the work synchronously
val result = worker.startWork().get()
assertThat(result).isEqualTo(Result.success())
}
}
टेस्ट शुरू करने से पहले, हम WorkManager
को फ़ैक्ट्री के बारे में बताते हैं, ताकि हम फ़र्ज़ी नेटवर्क इंजेक्ट कर सकें.
इस टेस्ट में, TestListenableWorkerBuilder
का इस्तेमाल करके वर्कर बनाया जाता है. इसके बाद, startWork()
तरीके को कॉल करके वर्कर को चलाया जा सकता है.
WorkManager, एपीआई के डिज़ाइन को आसान बनाने के लिए कोरूटीन का इस्तेमाल करने का सिर्फ़ एक उदाहरण है.
इस कोडलैब में, हमने बुनियादी बातों के बारे में बताया है. इनकी मदद से, अपने ऐप्लिकेशन में को-रूटीन का इस्तेमाल शुरू किया जा सकता है!
हमने इन विषयों पर बात की:
- एसिंक्रोनस प्रोग्रामिंग को आसान बनाने के लिए, यूज़र इंटरफ़ेस (यूआई) और WorkManager, दोनों के कामों से Android ऐप्लिकेशन में कोरूटीन को इंटिग्रेट करने का तरीका,
ViewModel
के अंदर कोरूटीन का इस्तेमाल करके, नेटवर्क से डेटा फ़ेच करने और उसे डेटाबेस में सेव करने का तरीका. इससे मुख्य थ्रेड ब्लॉक नहीं होती.- साथ ही,
ViewModel
के खत्म होने पर सभी कोरूटीन को रद्द करने का तरीका.
कोरूटीन पर आधारित कोड की जांच करने के लिए, हमने जांच के व्यवहार के साथ-साथ सीधे तौर पर टेस्ट से suspend
फ़ंक्शन को कॉल करने की सुविधा भी शामिल की है.
ज़्यादा जानें
Android पर कोरूटीन का बेहतर तरीके से इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, "Kotlin Flow और LiveData के साथ ऐडवांस कोरूटीन" कोडलैब देखें.
Kotlin कोरूटीन में कई ऐसी सुविधाएं हैं जिनके बारे में इस कोडलैब में नहीं बताया गया है. अगर आपको Kotlin कोरूटीन के बारे में ज़्यादा जानना है, तो JetBrains की ओर से पब्लिश की गई कोरूटीन गाइड पढ़ें. Android पर कोरूटीन के इस्तेमाल के ज़्यादा पैटर्न के लिए, "Kotlin कोरूटीन की मदद से ऐप्लिकेशन की परफ़ॉर्मेंस बेहतर बनाना" लेख भी पढ़ें.