আপনার Android অ্যাপে Kotlin Coroutines ব্যবহার করুন

এই কোডল্যাবে আপনি শিখবেন কিভাবে একটি Android অ্যাপে Kotlin Coroutines ব্যবহার করতে হয়—ব্যাকগ্রাউন্ড থ্রেড পরিচালনার একটি নতুন উপায় যা কলব্যাকের প্রয়োজনীয়তা কমিয়ে কোডকে সহজ করতে পারে। 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 সাথে পরিচিতি।
  • এক্সটেনশন ফাংশন এবং ল্যাম্বডাস সহ কোটলিন সিনট্যাক্সের অভিজ্ঞতা।
  • প্রধান থ্রেড, ব্যাকগ্রাউন্ড থ্রেড এবং কলব্যাক সহ Android এ থ্রেড ব্যবহার করার একটি প্রাথমিক ধারণা।

আপনি কি করবেন

  • কোরোটিন সহ লিখিত কল কোড এবং ফলাফল প্রাপ্ত.
  • অ্যাসিঙ্ক কোড ক্রমিক করতে সাসপেন্ড ফাংশন ব্যবহার করুন।
  • কোড কীভাবে কার্যকর হয় তা নিয়ন্ত্রণ করতে launch এবং runBlocking ব্যবহার করুন।
  • suspendCoroutine ব্যবহার করে বিদ্যমান এপিআইগুলিকে কোরোটিনে রূপান্তর করার কৌশলগুলি শিখুন৷
  • আর্কিটেকচার উপাদান সহ coroutines ব্যবহার করুন.
  • কোরোটিন পরীক্ষার জন্য সর্বোত্তম অনুশীলনগুলি শিখুন।

আপনি কি প্রয়োজন হবে

  • অ্যান্ড্রয়েড স্টুডিও 3. 5 (কোডল্যাব অন্যান্য সংস্করণের সাথে কাজ করতে পারে, তবে কিছু জিনিস অনুপস্থিত বা অন্যরকম দেখতে হতে পারে)।

আপনি যদি এই কোডল্যাবের মাধ্যমে কাজ করার সময় কোনো সমস্যায় পড়েন (কোড বাগ, ব্যাকরণগত ত্রুটি, অস্পষ্ট শব্দ, ইত্যাদি), তাহলে অনুগ্রহ করে কোডল্যাবের নীচের বাম কোণে একটি ভুল প্রতিবেদন করুন লিঙ্কের মাধ্যমে সমস্যাটি রিপোর্ট করুন৷

কোডটি ডাউনলোড করুন

এই কোডল্যাবের সমস্ত কোড ডাউনলোড করতে নিম্নলিখিত লিঙ্কে ক্লিক করুন:

জিপ ডাউনলোড করুন

... অথবা নিম্নলিখিত কমান্ড ব্যবহার করে কমান্ড লাইন থেকে GitHub সংগ্রহস্থল ক্লোন করুন:

$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git

প্রায়শই জিজ্ঞাসিত প্রশ্ন

প্রথমত, শুরুর নমুনা অ্যাপটি কেমন তা দেখা যাক। অ্যান্ড্রয়েড স্টুডিওতে নমুনা অ্যাপ খুলতে এই নির্দেশাবলী অনুসরণ করুন।

  1. আপনি যদি kotlin-coroutines zip ফাইলটি ডাউনলোড করে থাকেন, তাহলে ফাইলটি আনজিপ করুন।
  2. অ্যান্ড্রয়েড স্টুডিওতে coroutines-codelab প্রকল্পটি খুলুন।
  3. start অ্যাপ্লিকেশন মডিউল নির্বাচন করুন.
  4. ক্লিক করুন execute.png বোতাম চালান , এবং হয় একটি এমুলেটর চয়ন করুন বা আপনার অ্যান্ড্রয়েড ডিভাইসটি সংযুক্ত করুন, যা অবশ্যই Android ললিপপ চালানোর জন্য সক্ষম হতে হবে (সর্বনিম্ন SDK সমর্থিত 21)। Kotlin Coroutines পর্দা উপস্থিত হওয়া উচিত:

এই স্টার্টার অ্যাপ্লিকেশানটি থ্রেড ব্যবহার করে আপনি স্ক্রীন টিপানোর পরে অল্প বিলম্বের সংখ্যা বৃদ্ধি করতে। এটি নেটওয়ার্ক থেকে একটি নতুন শিরোনাম আনবে এবং এটি স্ক্রিনে প্রদর্শন করবে। এটি এখনই চেষ্টা করে দেখুন, এবং আপনি অল্প বিলম্বের পরে গণনা এবং বার্তা পরিবর্তন দেখতে পাবেন। এই কোডল্যাবে আপনি এই অ্যাপ্লিকেশনটিকে কোরোটিন ব্যবহার করতে রূপান্তর করবেন।

এই অ্যাপটি MainActivity এ UI কোডকে MainViewModel এর অ্যাপ্লিকেশন লজিক থেকে আলাদা করতে আর্কিটেকচার উপাদান ব্যবহার করে। প্রকল্পের কাঠামোর সাথে নিজেকে পরিচিত করতে কিছুক্ষণ সময় নিন।

  1. MainActivity UI প্রদর্শন করে, ক্লিক শ্রোতাদের নিবন্ধন করে এবং একটি Snackbar প্রদর্শন করতে পারে। এটি MainViewModel এ ইভেন্ট পাস করে এবং MainViewModelLiveData উপর ভিত্তি করে স্ক্রীন আপডেট করে।
  2. MainViewModel onMainViewClicked এ ইভেন্ট পরিচালনা করে এবং LiveData. ব্যবহার করে MainActivity এর সাথে যোগাযোগ করবে।
  3. Executors BACKGROUND, যা ব্যাকগ্রাউন্ড থ্রেডে জিনিস চালাতে পারে।
  4. TitleRepository নেটওয়ার্ক থেকে ফলাফল আনে এবং ডাটাবেসে সেভ করে।

একটি প্রকল্পে coroutine যোগ করা

Kotlin-এ coroutines ব্যবহার করতে, আপনাকে অবশ্যই আপনার প্রোজেক্টের build.gradle (Module: app) ফাইলে coroutines-core লাইব্রেরি অন্তর্ভুক্ত করতে হবে। কোডল্যাব প্রকল্পগুলি ইতিমধ্যে আপনার জন্য এটি করেছে, তাই কোডল্যাবটি সম্পূর্ণ করার জন্য আপনাকে এটি করার দরকার নেই।

অ্যান্ড্রয়েডে কোরোটিনগুলি একটি মূল লাইব্রেরি এবং অ্যান্ড্রয়েড নির্দিষ্ট এক্সটেনশন হিসাবে উপলব্ধ:

  • kotlinx-corountines-core — কোটলিনে কোরোটিন ব্যবহারের জন্য প্রধান ইন্টারফেস
  • kotlinx-coroutines-android — কোরোটিনে অ্যান্ড্রয়েড প্রধান থ্রেডের জন্য সমর্থন

স্টার্টার অ্যাপটি ইতিমধ্যেই build.gradle. একটি নতুন অ্যাপ প্রোজেক্ট তৈরি করার সময়, আপনাকে build.gradle (Module: app) খুলতে হবে এবং প্রোজেক্টে coroutines নির্ভরতা যোগ করতে হবে।

dependencies {
  ...
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}

অ্যান্ড্রয়েডে, মূল থ্রেড ব্লক করা এড়ানো অপরিহার্য। প্রধান থ্রেড হল একটি একক থ্রেড যা UI-তে সমস্ত আপডেট পরিচালনা করে। এটি সেই থ্রেড যা সমস্ত ক্লিক হ্যান্ডলার এবং অন্যান্য UI কলব্যাককে কল করে। যেমন, এটি একটি দুর্দান্ত ব্যবহারকারীর অভিজ্ঞতার গ্যারান্টি দিতে মসৃণভাবে চালাতে হবে।

কোনো দৃশ্যমান বিরতি ছাড়াই ব্যবহারকারীর কাছে আপনার অ্যাপ প্রদর্শনের জন্য, মূল থ্রেডটিকে প্রতি 16ms বা তার বেশি স্ক্রীন আপডেট করতে হবে, যা প্রতি সেকেন্ডে প্রায় 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 coroutines আপনাকে কলব্যাক-ভিত্তিক কোডকে ক্রমিক কোডে রূপান্তর করতে দেয়। ক্রমানুসারে লেখা কোড সাধারণত পড়া সহজ, এবং এমনকি ভাষা বৈশিষ্ট্য যেমন ব্যতিক্রম ব্যবহার করতে পারে।

শেষ পর্যন্ত, তারা ঠিক একই জিনিসটি করে: দীর্ঘস্থায়ী টাস্ক থেকে একটি ফলাফল পাওয়া পর্যন্ত অপেক্ষা করুন এবং সম্পাদন চালিয়ে যান। যাইহোক, কোডে তারা দেখতে খুব আলাদা।

কীওয়ার্ড suspend হল কোটলিনের একটি ফাংশন, বা ফাংশনের ধরন চিহ্নিত করার উপায়, যা করটিনে উপলব্ধ। যখন একটি coroutine একটি ফাংশনকে 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 স্টুডিওতে start আছে।

CoroutineScope বোঝা

কোটলিনে, সমস্ত করটিন একটি CoroutineScope মধ্যে চলে। একটি স্কোপ তার কাজের মাধ্যমে করোটিনের জীবনকাল নিয়ন্ত্রণ করে। আপনি যখন একটি সুযোগের কাজ বাতিল করেন, তখন এটি সেই সুযোগে শুরু হওয়া সমস্ত কোরোটিন বাতিল করে। অ্যান্ড্রয়েডে, আপনি সমস্ত চলমান কোরোটিন বাতিল করতে একটি সুযোগ ব্যবহার করতে পারেন যখন, উদাহরণস্বরূপ, ব্যবহারকারী একটি Activity বা Fragment থেকে দূরে নেভিগেট করেন৷ স্কোপগুলি আপনাকে একটি ডিফল্ট প্রেরণকারী নির্দিষ্ট করার অনুমতি দেয়। একটি প্রেরক নিয়ন্ত্রণ করে কোন থ্রেড একটি কোরোটিন চালায়।

UI দ্বারা শুরু করা কোরোটিনগুলির জন্য, এটি সাধারণত Dispatchers.Main শুরু করা সঠিক। মেইন যা Android এর প্রধান থ্রেড। Dispatchers.Main একটি কোরুটিন শুরু হয়েছে৷ স্থগিত থাকাকালীন মেইন মূল থ্রেডটিকে ব্লক করবে না৷ যেহেতু একটি ViewModel coroutine প্রায় সবসময়ই প্রধান থ্রেডে UI আপডেট করে, তাই মূল থ্রেডে coroutines শুরু করা আপনাকে অতিরিক্ত থ্রেড সুইচগুলি সংরক্ষণ করে। মূল থ্রেডে শুরু হওয়া একটি করুটিন এটি শুরু হওয়ার পরে যে কোনো সময় প্রেরকদের পরিবর্তন করতে পারে। উদাহরণস্বরূপ, এটি প্রধান থ্রেডের বাইরে একটি বড় JSON ফলাফল পার্স করতে অন্য প্রেরক ব্যবহার করতে পারে।

viewModelScope ব্যবহার করে

AndroidX lifecycle-viewmodel-ktx লাইব্রেরি ViewModels-এ একটি CoroutineScope যোগ করে যা UI-সম্পর্কিত coroutines শুরু করার জন্য কনফিগার করা হয়েছে। এই লাইব্রেরিটি ব্যবহার করতে, আপনাকে অবশ্যই এটিকে আপনার প্রকল্পের 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 বর্তমান থ্রেডকে ব্লক করে, এটি মূল থ্রেডে কল করা হলে এটি UI হিমায়িত করবে। ব্যবহারকারী মূল দৃশ্যে ক্লিক করার এক সেকেন্ড পরে, এটি একটি স্ন্যাকবার অনুরোধ করে।

আপনি কোড থেকে 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")
   }
}

এই কোডটি একই কাজ করে, একটি স্ন্যাকবার দেখানোর আগে এক সেকেন্ড অপেক্ষা করে। যাইহোক, কিছু গুরুত্বপূর্ণ পার্থক্য আছে:

  1. viewModelScope. launch viewModelScope একটি করুটিন শুরু করবে। এর মানে হল যে কাজটি আমরা viewModelScope এ পাস করেছি তা বাতিল হয়ে গেলে, এই চাকরি/স্কোপের সমস্ত কোরোটিন বাতিল হয়ে যাবে। যদি ব্যবহারকারী delay ফিরে আসার আগে অ্যাক্টিভিটি ছেড়ে চলে যান, তাহলে ViewModel ধ্বংস করার জন্য onCleared বলা হলে এই coroutine স্বয়ংক্রিয়ভাবে বাতিল হয়ে যাবে।
  2. যেহেতু viewModelScope Dispatchers.Main এর একটি ডিফল্ট প্রেরক রয়েছে, এই coroutineটি মূল থ্রেডে চালু করা হবে। আমরা পরে দেখব কিভাবে বিভিন্ন থ্রেড ব্যবহার করতে হয়।
  3. ফাংশন delay একটি suspend ফাংশন। এটি অ্যান্ড্রয়েড স্টুডিওতে দেখানো হয়েছে বাম নর্দমায় আইকন। যদিও এই করোটিন মূল থ্রেডে চলে, delay এক সেকেন্ডের জন্য থ্রেডটিকে ব্লক করবে না। পরিবর্তে, প্রেরণকারী পরবর্তী বিবৃতিতে এক সেকেন্ডের মধ্যে পুনরায় শুরু করার জন্য কোরোটিন নির্ধারণ করবে।

এগিয়ে যান এবং এটি চালান. আপনি যখন মূল ভিউতে ক্লিক করেন তখন আপনি এক সেকেন্ড পরে একটি স্ন্যাকবার দেখতে পাবেন।

পরবর্তী বিভাগে আমরা বিবেচনা করব কিভাবে এই ফাংশনটি পরীক্ষা করা যায়।

এই অনুশীলনে আপনি যে কোডটি লিখেছেন তার জন্য একটি পরীক্ষা লিখবেন। kotlinx-coroutines-test লাইব্রেরি ব্যবহার করে Dispatchers.Main এ চলমান coroutines কিভাবে পরীক্ষা করবেন এই অনুশীলনটি আপনাকে দেখায়। পরে এই কোডল্যাবে আপনি একটি পরীক্ষা বাস্তবায়ন করবেন যা সরাসরি কোরোটিনের সাথে ইন্টারঅ্যাক্ট করে।

বিদ্যমান কোড পর্যালোচনা করুন

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 পরীক্ষা করার অনুমতি দেওয়ার জন্য দুটি নিয়ম ব্যবহার করা হয়:

  1. InstantTaskExecutorRule হল একটি JUnit নিয়ম যা প্রতিটি টাস্ক সিঙ্ক্রোনাসভাবে চালানোর জন্য LiveData কনফিগার করে
  2. MainCoroutineScopeRule হল এই কোডবেসের একটি কাস্টম নিয়ম যা Dispatchers.Main kotlinx-coroutines-test থেকে একটি TestCoroutineDispatcher ব্যবহার করতে কনফিগার করে। এটি পরীক্ষাগুলিকে পরীক্ষার জন্য একটি ভার্চুয়াল-ঘড়ি অগ্রসর করার অনুমতি দেয় এবং ইউনিট পরীক্ষায় Dispatchers.Main ব্যবহার করার অনুমতি দেয়।

setup পদ্ধতিতে, টেস্টিং ফেক ব্যবহার করে MainViewModel এর একটি নতুন দৃষ্টান্ত তৈরি করা হয়েছে - এগুলি আসল নেটওয়ার্ক বা ডাটাবেস ব্যবহার না করে পরীক্ষা লিখতে সাহায্য করার জন্য স্টার্টার কোডে সরবরাহ করা নেটওয়ার্ক এবং ডাটাবেসের জাল বাস্তবায়ন।

এই পরীক্ষার জন্য, জালগুলি শুধুমাত্র MainViewModel এর নির্ভরতা পূরণ করার জন্য প্রয়োজন। পরে এই কোড ল্যাবে আপনি coroutines সমর্থন করার জন্য জাল আপডেট করবেন।

কোরোটিন নিয়ন্ত্রণ করে এমন একটি পরীক্ষা লিখুন

একটি নতুন পরীক্ষা যোগ করুন যা নিশ্চিত করে যে মূল ভিউ ক্লিক করার পর ট্যাপগুলি এক সেকেন্ড আপডেট করা হয়েছে:

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 সেকেন্ড পরে এটি "1 ট্যাপস" এ আপডেট হয়।

এই পরীক্ষাটি onMainViewClicked দ্বারা চালু করা কোরোটিনের সম্পাদন নিয়ন্ত্রণ করতে ভার্চুয়াল-টাইম ব্যবহার করে। MainCoroutineScopeRule আপনাকে Dispatchers.Main এ লঞ্চ করা কোরোটিনগুলির সম্পাদনকে বিরতি, পুনরায় শুরু করতে বা নিয়ন্ত্রণ করতে দেয়। এখানে আমরা advanceTimeBy(1_000) কে কল করছি যার ফলে প্রধান প্রেরক অবিলম্বে 1 সেকেন্ড পরে পুনরায় শুরু করার জন্য নির্ধারিত কোরোটিনগুলি কার্যকর করবে৷

এই পরীক্ষাটি সম্পূর্ণরূপে নির্ধারক, যার মানে এটি সর্বদা একইভাবে কার্যকর হবে। এবং, কারণ এটি Dispatchers.Main চালু করা কোরোটিনগুলির সম্পাদনের উপর সম্পূর্ণ নিয়ন্ত্রণ রাখে৷ প্রধান এটি মান সেট করার জন্য এক সেকেন্ড অপেক্ষা করতে হবে না৷

বিদ্যমান পরীক্ষা চালান

  1. একটি প্রসঙ্গ মেনু খুলতে আপনার সম্পাদকের MainViewModelTest ক্লাসের নামটিতে ডান ক্লিক করুন।
  2. প্রসঙ্গ মেনুতে নির্বাচন করুন execute.png 'MainViewModelTest' চালান
  3. ভবিষ্যতের রানের জন্য আপনি কনফিগারেশনের পাশের কনফিগারেশনে এই পরীক্ষা কনফিগারেশনটি নির্বাচন করতে পারেন execute.png টুলবারে বোতাম। ডিফল্টরূপে, কনফিগারেশনটিকে MainViewModelTest বলা হবে।

আপনি পরীক্ষা পাস দেখতে হবে! এবং এটি চালানোর জন্য এক সেকেন্ডের চেয়ে কিছুটা কম সময় নেওয়া উচিত।

পরবর্তী অনুশীলনে আপনি শিখবেন কিভাবে একটি বিদ্যমান কলব্যাক API থেকে coroutines ব্যবহার করতে রূপান্তর করতে হয়।

এই ধাপে, আপনি coroutines ব্যবহার করার জন্য একটি সংগ্রহস্থল রূপান্তর করা শুরু করবেন। এটি করার জন্য, আমরা ViewModel , Repository , Room এবং Retrofit এ coroutines যোগ করব।

স্থাপত্যের প্রতিটি অংশ কীসের জন্য দায়ী তা আমরা কোরোটিন ব্যবহারে স্যুইচ করার আগে বুঝে নেওয়া ভালো।

  1. MainDatabase রুম ব্যবহার করে একটি ডাটাবেস প্রয়োগ করে যা একটি Title সংরক্ষণ করে এবং লোড করে।
  2. MainNetwork একটি নেটওয়ার্ক API প্রয়োগ করে যা একটি নতুন শিরোনাম নিয়ে আসে। এটি শিরোনাম আনার জন্য Retrofit ব্যবহার করে। Retrofit এলোমেলোভাবে ত্রুটি বা উপহাস ডেটা ফেরত দেওয়ার জন্য কনফিগার করা হয়েছে, তবে অন্যথায় এমন আচরণ করে যেন এটি সত্যিকারের নেটওয়ার্ক অনুরোধ করছে।
  3. TitleRepository নেটওয়ার্ক এবং ডাটাবেস থেকে ডেটা একত্রিত করে শিরোনাম আনয়ন বা রিফ্রেশ করার জন্য একটি একক API প্রয়োগ করে।
  4. MainViewModel পর্দার অবস্থা উপস্থাপন করে এবং ঘটনাগুলি পরিচালনা করে। ব্যবহারকারী স্ক্রিনে ট্যাপ করলে এটি রিপোজিটরিকে শিরোনাম রিফ্রেশ করতে বলবে।

যেহেতু নেটওয়ার্ক অনুরোধটি UI-ইভেন্ট দ্বারা চালিত হয় এবং আমরা সেগুলির উপর ভিত্তি করে একটি করোটিন শুরু করতে চাই, তাই কোরোটিনগুলি ব্যবহার শুরু করার স্বাভাবিক জায়গাটি 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 ডাটাবেসে সমস্ত শিরোনাম লিখি, তাই Room দ্বারা আপডেট করা LiveData পর্যবেক্ষণ করে UI বর্তমান শিরোনাম আপডেট করে।

কোরোটিনের আপডেটে, আমরা ঠিক একই আচরণ রাখব। UI কে স্বয়ংক্রিয়ভাবে আপ টু ডেট রাখতে Room ডাটাবেসের মতো একটি পর্যবেক্ষণযোগ্য ডেটা উত্স ব্যবহার করা এটি একটি ভাল প্যাটার্ন।

coroutines সংস্করণ

চলুন coroutines সঙ্গে refreshTitle আবার লিখুন!

যেহেতু আমাদের এখনই এটির প্রয়োজন হবে, আসুন আমাদের সংগ্রহস্থলে একটি খালি সাসপেন্ড ফাংশন তৈরি করি ( TitleRespository.kt )। একটি নতুন ফাংশন সংজ্ঞায়িত করুন যা suspend অপারেটর ব্যবহার করে Kotlin কে জানাতে যে এটি coroutines এর সাথে কাজ করে৷

TitleRepository.kt

suspend fun refreshTitle() {
    // TODO: Refresh from network and write to database
    delay(500)
}

যখন আপনি এই কোডল্যাবটির সাথে কাজ শেষ করবেন, আপনি একটি নতুন শিরোনাম আনার জন্য রেট্রোফিট এবং রুম ব্যবহার করার জন্য এটি আপডেট করবেন এবং কোরোটিন ব্যবহার করে ডাটাবেসে লিখবেন৷ আপাতত, এটি কাজ করার ভান করে 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 একটি নেটওয়ার্ক অনুরোধ এবং ডাটাবেস ক্যোয়ারী করবে এটি একটি প্রধান-নিরাপদ ইন্টারফেস প্রকাশ করতে coroutines ব্যবহার করতে পারে। এর মানে মূল থ্রেড থেকে এটি কল করা নিরাপদ হবে।

কারণ আমরা viewModelScope ব্যবহার করছি, যখন ব্যবহারকারী এই স্ক্রীন থেকে দূরে সরে যায় তখন এই কোরোটিন দ্বারা শুরু করা কাজটি স্বয়ংক্রিয়ভাবে বাতিল হয়ে যাবে৷ এর মানে এটি অতিরিক্ত নেটওয়ার্ক অনুরোধ বা ডাটাবেস কোয়েরি করবে না।

কোডের পরবর্তী কয়েকটি লাইন আসলে repository refreshTitle কল করে।

try {
    _spinner.value = true
    repository.refreshTitle()
}

এই কোরোটিন কিছু করার আগে এটি লোডিং স্পিনার শুরু করে - তারপর এটি একটি নিয়মিত ফাংশনের মতো refreshTitle বলে। যাইহোক, যেহেতু refreshTitle একটি সাসপেন্ডিং ফাংশন, এটি একটি সাধারণ ফাংশনের চেয়ে ভিন্নভাবে কার্যকর করে।

আমাদের কলব্যাক পাস করতে হবে না। refreshTitle দ্বারা পুনরায় চালু না হওয়া পর্যন্ত coroutine স্থগিত থাকবে। যদিও এটি দেখতে একটি নিয়মিত ব্লকিং ফাংশন কলের মতো, এটি স্বয়ংক্রিয়ভাবে মূল থ্রেড ব্লক না করে পুনরায় শুরু করার আগে নেটওয়ার্ক এবং ডাটাবেস ক্যোয়ারী সম্পূর্ণ না হওয়া পর্যন্ত অপেক্ষা করবে।

} catch (error: TitleRefreshError) {
    _snackBar.value = error.message
} finally {
    _spinner.value = false
}

সাসপেন্ড ফাংশনগুলির ব্যতিক্রমগুলি নিয়মিত ফাংশনের ত্রুটিগুলির মতোই কাজ করে। আপনি যদি একটি সাসপেন্ড ফাংশনে একটি ত্রুটি নিক্ষেপ করেন, তাহলে এটি কলারের কাছে নিক্ষেপ করা হবে। সুতরাং যদিও তারা বেশ ভিন্নভাবে চালায়, আপনি তাদের পরিচালনা করতে নিয়মিত চেষ্টা/ক্যাচ ব্লক ব্যবহার করতে পারেন। এটি দরকারী কারণ এটি আপনাকে প্রতিটি কলব্যাকের জন্য কাস্টম ত্রুটি পরিচালনা করার পরিবর্তে ত্রুটি পরিচালনার জন্য অন্তর্নির্মিত ভাষা সমর্থনের উপর নির্ভর করতে দেয়।

এবং, যদি আপনি একটি coroutine থেকে একটি ব্যতিক্রম ছুঁড়ে দেন - সেই coroutine ডিফল্টরূপে এটির অভিভাবক বাতিল করবে। তার মানে একসাথে একাধিক সম্পর্কিত কাজ বাতিল করা সহজ।

এবং তারপর, অবশেষে একটি ব্লকে, আমরা নিশ্চিত করতে পারি যে ক্যোয়ারী চালানোর পরে স্পিনার সবসময় বন্ধ থাকে।

স্টার্ট কনফিগারেশন নির্বাচন করে আবার টিপে অ্যাপ্লিকেশনটি চালান execute.png , আপনি যে কোনো জায়গায় ট্যাপ করার সময় একটি লোডিং স্পিনার দেখতে পাবেন। শিরোনাম একই থাকবে কারণ আমরা এখনও আমাদের নেটওয়ার্ক বা ডাটাবেস হুক আপ করিনি।

পরবর্তী অনুশীলনে আপনি আসলে কাজ করার জন্য সংগ্রহস্থলটি আপডেট করবেন।

এই অনুশীলনে আপনি শিখবেন কিভাবে TitleRepository এর একটি কার্যকরী সংস্করণ বাস্তবায়নের জন্য একটি coroutine রান করা থ্রেডটি পরিবর্তন করতে হয়।

রিফ্রেশটাইটেলে বিদ্যমান কলব্যাক কোড পর্যালোচনা করুন

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 একটি কলব্যাকের সাথে প্রয়োগ করা হয় যাতে লোডিং এবং ত্রুটির অবস্থা কলারের সাথে যোগাযোগ করা যায়।

রিফ্রেশ বাস্তবায়ন করার জন্য এই ফাংশনটি বেশ কয়েকটি জিনিস করে।

  1. BACKGROUND ExecutorService সহ অন্য থ্রেডে যান
  2. ব্লকিং execute() পদ্ধতি ব্যবহার করে fetchNextTitle নেটওয়ার্ক অনুরোধ চালান। এটি বর্তমান থ্রেডে নেটওয়ার্ক অনুরোধ চালাবে, এই ক্ষেত্রে BACKGROUND এর একটি থ্রেড।
  3. ফলাফল সফল হলে, insertTitle সহ ডাটাবেসে সংরক্ষণ করুন এবং onCompleted() পদ্ধতিতে কল করুন।
  4. যদি ফলাফল সফল না হয়, বা একটি ব্যতিক্রম আছে, ব্যর্থ রিফ্রেশ সম্পর্কে কলকারীকে জানাতে onError পদ্ধতিতে কল করুন।

এই কলব্যাক ভিত্তিক বাস্তবায়ন প্রধান-নিরাপদ কারণ এটি মূল থ্রেডকে ব্লক করবে না। কিন্তু, কাজ শেষ হলে কলারকে জানাতে এটি একটি কলব্যাক ব্যবহার করতে হবে। এটি BACKGROUND থ্রেডের কলব্যাকগুলিকেও কল করে যা এটি সুইচ করেছে৷

coroutines থেকে কল ব্লকিং কল

নেটওয়ার্ক বা ডাটাবেসে coroutines প্রবর্তন না করে, আমরা coroutines ব্যবহার করে এই কোডটিকে প্রধান-নিরাপদ করতে পারি। এটি আমাদের কলব্যাক থেকে পরিত্রাণ পেতে দেয় এবং আমাদেরকে প্রাথমিকভাবে যে থ্রেডটি বলা হয়েছিল সেখানে ফলাফলটি পাস করতে দেয়।

আপনি যেকোন সময় এই প্যাটার্নটি ব্যবহার করতে পারেন আপনাকে ব্লকিং বা CPU নিবিড় কাজ করার জন্য একটি কোরোটিনের ভিতর থেকে যেমন একটি বড় তালিকা সাজানো এবং ফিল্টার করা বা ডিস্ক থেকে পড়া।

যেকোন প্রেরণকারীর মধ্যে স্যুইচ করতে, coroutines ব্যবহার করে withContextwithContext কল করা শুধুমাত্র ল্যাম্বডার জন্য অন্য প্রেরককে সুইচ করে তারপর সেই ল্যাম্বডার ফলাফলের সাথে যে প্রেরককে ডেকেছিল তার কাছে ফিরে আসে।

ডিফল্টরূপে, Kotlin coroutines তিনটি প্রেরণকারী প্রদান করে: Main , IO , এবং Default । IO প্রেরণকারীকে IO কাজের জন্য অপ্টিমাইজ করা হয় যেমন নেটওয়ার্ক বা ডিস্ক থেকে পড়ার মতো, যখন ডিফল্ট প্রেরণকারীটি CPU নিবিড় কাজের জন্য অপ্টিমাইজ করা হয়।

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(...) কল করা উভয়ই থ্রেডটিকে ব্লক করবে যেটি এই coroutine চলছে। তবে, withContext ব্যবহার করে Dispatchers.IO তে স্যুইচ করার মাধ্যমে, আমরা IO প্রেরণকারীর একটি থ্রেড ব্লক করছি। যে coroutine এটিকে কল করেছে, সম্ভবত Dispatchers.Main এ চলমান, withContext lambda সম্পূর্ণ না হওয়া পর্যন্ত স্থগিত করা হবে।

কলব্যাক সংস্করণের তুলনায়, দুটি গুরুত্বপূর্ণ পার্থক্য রয়েছে:

  1. withContext এটির ফলাফল ফেরত পাঠায় যে এটিকে ডেকেছে, এই ক্ষেত্রে Dispatchers.Main । কলব্যাক সংস্করণটি BACKGROUND এক্সিকিউটর পরিষেবার একটি থ্রেডে কলব্যাককে বলে।
  2. কলকারীকে এই ফাংশনে কলব্যাক পাস করতে হবে না। ফলাফল বা ত্রুটি পেতে তারা স্থগিত এবং পুনরায় শুরু করার উপর নির্ভর করতে পারে।

অ্যাপটি আবার চালান

আপনি যদি আবার অ্যাপটি চালান, তাহলে আপনি দেখতে পাবেন যে নতুন কোরোটিন-ভিত্তিক বাস্তবায়ন নেটওয়ার্ক থেকে ফলাফল লোড করছে!

পরবর্তী ধাপে আপনি রুম এবং রেট্রোফিটে কোরোটিনগুলিকে একীভূত করবেন।

coroutines ইন্টিগ্রেশন চালিয়ে যেতে, আমরা রুম এবং রেট্রোফিটের স্থিতিশীল সংস্করণে সাসপেন্ড ফাংশনগুলির জন্য সমর্থন ব্যবহার করতে যাচ্ছি, তারপরে সাসপেন্ড ফাংশনগুলি ব্যবহার করে আমরা যে কোডটি যথেষ্ট পরিমাণে লিখেছি সেটিকে সরলীকরণ করব৷

রুমে corutines

প্রথমে MainDatabase.kt খুলুন এবং insertTitle একটি সাসপেন্ড ফাংশন করুন:

MainDatabase.kt

// add the suspend modifier to the existing insertTitle

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTitle(title: Title)

আপনি যখন এটি করবেন, রুম আপনার ক্যোয়ারীকে প্রধান-নিরাপদ করে তুলবে এবং এটি স্বয়ংক্রিয়ভাবে একটি ব্যাকগ্রাউন্ড থ্রেডে কার্যকর করবে। যাইহোক, এর মানে হল যে আপনি শুধুমাত্র একটি কোরাটিনের ভিতর থেকে এই ক্যোয়ারীটি কল করতে পারেন।

এবং - রুমে কোরোটিন ব্যবহার করার জন্য আপনাকে যা করতে হবে। বেশ নিফটি।

Retrofit মধ্যে Coroutines

এর পরে দেখা যাক রেট্রোফিটের সাথে কোরোটিনগুলিকে কীভাবে একীভূত করা যায়। 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 এর সাথে সাসপেন্ড ফাংশন ব্যবহার করতে আপনাকে দুটি জিনিস করতে হবে:

  1. ফাংশনে একটি সাসপেন্ড মডিফায়ার যোগ করুন
  2. রিটার্ন টাইপ থেকে Call র্যাপার সরান। এখানে আমরা String ফেরত দিচ্ছি, তবে আপনি জটিল json-ব্যাকড টাইপও ফিরিয়ে দিতে পারেন। আপনি যদি এখনও রেট্রোফিটের সম্পূর্ণ Result অ্যাক্সেস প্রদান করতে চান তবে আপনি সাসপেন্ড ফাংশন থেকে String পরিবর্তে Result<String> ফেরত দিতে পারেন।

Retrofit স্বয়ংক্রিয়ভাবে সাসপেন্ড ফাংশনগুলিকে প্রধান-নিরাপদ করে তুলবে যাতে আপনি সরাসরি Dispatchers.Main থেকে তাদের কল করতে পারেন।

রুম এবং রেট্রোফিট ব্যবহার করা

এখন যেহেতু রুম এবং রেট্রোফিট ফাংশনগুলিকে সাসপেন্ড করে, আমরা সেগুলিকে আমাদের সংগ্রহস্থল থেকে ব্যবহার করতে পারি৷ TitleRepository.kt খুলুন এবং দেখুন কিভাবে সাসপেন্ডিং ফাংশন ব্যবহার করে যুক্তিকে অনেক সহজ করে দেয়, এমনকি ব্লকিং সংস্করণের তুলনায়:

শিরোনাম Repository.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 থেকে পরিত্রাণ পেয়েছি। যেহেতু রুম এবং রেট্রোফিট উভয়ই প্রধান-নিরাপদ সাসপেন্ডিং ফাংশন প্রদান করে, তাই Dispatchers.Main থেকে এই অ্যাসিঙ্ক কাজটি অর্কেস্ট্রেট করা নিরাপদ।

কম্পাইলার ত্রুটি ঠিক করা

coroutines এ সরানো ফাংশনের স্বাক্ষর পরিবর্তন জড়িত কারণ আপনি একটি নিয়মিত ফাংশন থেকে একটি সাসপেন্ড ফাংশন কল করতে পারবেন না। আপনি যখন এই ধাপে suspend মডিফায়ার যোগ করেন, তখন কিছু কম্পাইলার ত্রুটি তৈরি হয়েছিল যা দেখায় যে আপনি যদি একটি বাস্তব প্রকল্পে সাসপেন্ড করার জন্য একটি ফাংশন পরিবর্তন করেন তাহলে কী হবে।

প্রজেক্টের মাধ্যমে যান এবং তৈরি করা স্থগিত করতে ফাংশন পরিবর্তন করে কম্পাইলার ত্রুটিগুলি ঠিক করুন। এখানে প্রতিটির জন্য দ্রুত রেজোলিউশন রয়েছে:

TestingFakes.kt

নতুন সাসপেন্ড মডিফায়ারকে সমর্থন করতে টেস্টিং জাল আপডেট করুন।

শিরোনাম DaoFake

  1. উত্তরাধিকারসূত্রে সমস্ত ফাংশনে সাসপেন্ড মডিফায়ার যুক্ত করুন alt-enter টিপুন

মেইননেটওয়ার্ক ফেক

  1. উত্তরাধিকারসূত্রে সমস্ত ফাংশনে সাসপেন্ড মডিফায়ার যুক্ত করুন alt-enter টিপুন
  2. এই ফাংশন দিয়ে fetchNextTitle প্রতিস্থাপন করুন
override suspend fun fetchNextTitle() = result

MainNetwork Completable Fake

  1. উত্তরাধিকারসূত্রে সমস্ত ফাংশনে সাসপেন্ড মডিফায়ার যুক্ত করুন alt-enter টিপুন
  2. এই ফাংশন দিয়ে fetchNextTitle প্রতিস্থাপন করুন
override suspend fun fetchNextTitle() = completable.await()

TitleRepository.kt

  • refreshTitleWithCallbacks ফাংশনটি মুছুন কারণ এটি আর ব্যবহার করা হয় না।

অ্যাপটি চালান

অ্যাপটি আবার চালান, একবার এটি কম্পাইল হয়ে গেলে, আপনি দেখতে পাবেন যে এটি ভিউমডেল থেকে রুম এবং রেট্রোফিট পর্যন্ত কোরোটিন ব্যবহার করে ডেটা লোড করছে!

অভিনন্দন, আপনি coroutines ব্যবহার করে এই অ্যাপটিকে সম্পূর্ণরূপে অদলবদল করেছেন! গুটিয়ে রাখতে আমরা কীভাবে করেছি তা পরীক্ষা করব সে সম্পর্কে আমরা কিছুটা কথা বলব।

এই অনুশীলনে, আপনি একটি পরীক্ষা লিখবেন যা সরাসরি একটি 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 খুলুন।

প্রথম পরীক্ষা থেকে refreshTitle কল করার চেষ্টা করুন whenRefreshTitleSuccess_insertsRows

@Test
fun whenRefreshTitleSuccess_insertsRows() {
   val subject = TitleRepository(
       MainNetworkFake("OK"),
       TitleDaoFake("title")
   )

   subject.refreshTitle()
}

যেহেতু refreshTitle একটি suspend ফাংশন কোটলিন কোনও করুটিন বা অন্য কোনও সাসপেন্ড ফাংশন ব্যতীত কীভাবে এটি কল করতে জানেন না এবং আপনি একটি সংকলক ত্রুটি পাবেন যেমন, "সাসপেন্ড ফাংশন রিফ্রেশটাইটলকে কেবল একটি করুটাইন বা অন্য কোনও সাসপেন্ড ফাংশন থেকে কল করা উচিত।"

টেস্ট রানার করুটাইন সম্পর্কে কিছুই জানে না তাই আমরা এই পরীক্ষাটি স্থগিত ফাংশন করতে পারি না। আমরা 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 আপনার জন্য অনাবৃত ব্যতিক্রমগুলি পুনর্বিবেচনা করবে। যখন কোনও করুটিন ব্যতিক্রম ছুঁড়ে দেয় তখন এটি পরীক্ষা করা সহজ করে তোলে।

একটি করুটাইন দিয়ে একটি পরীক্ষা বাস্তবায়ন করুন

runBlockingTest সাথে refreshTitle কলটি মোড়ানো এবং GlobalScope.launch সরিয়ে ফেলুন ra

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 দ্বারা ডাটাবেসে serted োকানো হয়েছে তা যাচাই করার জন্য প্রদত্ত জালগুলি ব্যবহার করে।

যখন পরীক্ষাটি 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
       }
   }
}

উচ্চতর অর্ডার ফাংশনগুলিতে করুটাইন ব্যবহার করা

এই কোডটি মাইনভিউমোডেল.কেটি যুক্ত করুন

মেইনভিউমোডেল.কেটি

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()

মেইনভিউমোডেল.কেটি

fun refreshTitle() {
   launchDataLoad {
       repository.refreshTitle()
   }
}

লোডিং স্পিনার দেখানো এবং ত্রুটিগুলি দেখানোর আশেপাশে যুক্তি বিমূর্ত করে, আমরা ডেটা লোড করার জন্য প্রয়োজনীয় আমাদের প্রকৃত কোডটি সহজ করে দিয়েছি। স্পিনার দেখানো বা ত্রুটি প্রদর্শন করা এমন কিছু যা কোনও ডেটা লোডিংয়ের জন্য সাধারণীকরণ করা সহজ, যখন প্রকৃত ডেটা উত্স এবং গন্তব্যটি প্রতিবার নির্দিষ্ট করা দরকার।

এই বিমূর্ততাটি তৈরি করতে, launchDataLoad একটি আর্গুমেন্ট block নেয় যা একটি স্থগিত ল্যাম্বদা। একটি সাসপেন্ড ল্যাম্বদা আপনাকে সাসপেন্ড ফাংশনগুলিতে কল করতে দেয়। এইভাবে কোটলিন এই কোডল্যাবটিতে আমরা ব্যবহার করে চলেছি করুটাইন বিল্ডার্স launch এবং runBlocking প্রয়োগ করে।

// suspend lambda

block: suspend () -> Unit

স্থগিত ল্যাম্বদা তৈরি করতে, suspend কীওয়ার্ড দিয়ে শুরু করুন। ফাংশন তীর এবং রিটার্ন টাইপ Unit ঘোষণাটি সম্পূর্ণ করে।

আপনাকে প্রায়শই নিজের স্থগিত ল্যাম্বডাস ঘোষণা করতে হবে না, তবে তারা এই জাতীয় বিমূর্ততা তৈরি করতে সহায়ক হতে পারে যা পুনরাবৃত্তি যুক্তিকে আবদ্ধ করে দেয়!

এই অনুশীলনে আপনি কীভাবে ওয়ার্কম্যানেজারের কাছ থেকে করুটাইন ভিত্তিক কোড ব্যবহার করবেন তা শিখবেন।

ওয়ার্কম্যানেজার কী?

স্থগিতযোগ্য ব্যাকগ্রাউন্ড কাজের জন্য অ্যান্ড্রয়েডে অনেকগুলি বিকল্প রয়েছে। এই অনুশীলনটি আপনাকে দেখায় যে কীভাবে কর্সুটাইনগুলির সাথে ওয়ার্কম্যানেজারকে সংহত করা যায়। ওয়ার্কম্যানেজার স্থগিতযোগ্য ব্যাকগ্রাউন্ড কাজের জন্য একটি সামঞ্জস্যপূর্ণ, নমনীয় এবং সাধারণ গ্রন্থাগার। ওয়ার্কম্যানেজার হ'ল অ্যান্ড্রয়েডে এই ব্যবহারের ক্ষেত্রে প্রস্তাবিত সমাধান।

ওয়ার্কম্যানেজার অ্যান্ড্রয়েড জেটপ্যাকের অংশ এবং পটভূমি কাজের জন্য একটি আর্কিটেকচার উপাদান যা সুবিধাবাদী এবং গ্যারান্টিযুক্ত সম্পাদনের সংমিশ্রণ প্রয়োজন। সুযোগবাদী সম্পাদনের অর্থ হ'ল ওয়ার্কম্যানেজার যত তাড়াতাড়ি সম্ভব আপনার পটভূমির কাজটি করবে। গ্যারান্টিযুক্ত সম্পাদনের অর্থ হ'ল ওয়ার্কম্যানেজার আপনার অ্যাপ্লিকেশন থেকে দূরে চলাচল করলেও বিভিন্ন পরিস্থিতিতে আপনার কাজ শুরু করার জন্য যুক্তির যত্ন নেবে।

এ কারণে, ওয়ার্কম্যানেজার এমন কাজগুলির জন্য একটি ভাল পছন্দ যা শেষ পর্যন্ত শেষ করতে হবে।

কাজের কয়েকটি উদাহরণ যা ওয়ার্কম্যানেজারের ভাল ব্যবহার:

  • লগ আপলোড করা
  • চিত্রগুলিতে ফিল্টার প্রয়োগ করা এবং চিত্রটি সংরক্ষণ করা
  • পর্যায়ক্রমে নেটওয়ার্কের সাথে স্থানীয় ডেটা সিঙ্ক করে

ওয়ার্কম্যানেজারের সাথে করুটাইন ব্যবহার করা

ওয়ার্কম্যানেজার বিভিন্ন ব্যবহারের ক্ষেত্রে তার বেস ListanableWorker ক্লাসের বিভিন্ন বাস্তবায়ন সরবরাহ করে।

সহজ শ্রমিক শ্রেণি আমাদের ওয়ার্কম্যানেজার দ্বারা সম্পাদিত কিছু সিঙ্ক্রোনাস অপারেশন করতে দেয়। যাইহোক, আমাদের কোডবেসকে করুটাইনগুলি ব্যবহার এবং স্থগিত ফাংশনগুলি ব্যবহার করার জন্য রূপান্তর করতে এতদূর কাজ করার পরে, ওয়ার্কম্যানেজার ব্যবহারের সর্বোত্তম উপায় হ'ল CoroutineWorker ক্লাসের মাধ্যমে যা আমাদের doWork() ফাংশনটিকে স্থগিত ফাংশন হিসাবে সংজ্ঞায়িত করতে দেয়।

শুরু করার জন্য, RefreshMainDataWork খুলুন। এটি ইতিমধ্যে CoroutineWorker প্রসারিত করেছে এবং আপনাকে doWork প্রয়োগ করতে হবে।

suspend doWork ফাংশনের অভ্যন্তরে, সংগ্রহস্থল থেকে refreshTitle() কল করুন এবং উপযুক্ত ফলাফলটি ফিরিয়ে দিন!

আপনি টোডো শেষ করার পরে কোডটি এর মতো দেখতে হবে:

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 শ্রেণীর বিপরীতে, এই কোডটি আপনার ওয়ার্কম্যানেজার কনফিগারেশনে নির্দিষ্ট এক্সিকিউটারের উপর চালিত হয় না, তবে পরিবর্তে coroutineContext সদস্য (ডিফল্ট Dispatchers.Default দ্বারা) ডিফল্ট দ্বারা প্রেরণকারীকে ব্যবহার করুন।

আমাদের করুটাইন ওয়ার্কার পরীক্ষা করা

কোনও কোডবেস পরীক্ষা না করে সম্পূর্ণ হওয়া উচিত নয়।

ওয়ার্কম্যানেজার আপনার Worker ক্লাসগুলি পরীক্ষা করার জন্য কয়েকটি বিভিন্ন উপায় উপলব্ধ করে, মূল পরীক্ষার অবকাঠামো সম্পর্কে আরও জানতে, আপনি ডকুমেন্টেশনটি পড়তে পারেন।

ওয়ার্কম্যানেজার ভি 2.1 ListenableWorker শ্রেণীর পরীক্ষা করার জন্য একটি সহজ উপায় সমর্থন করার জন্য এপিআইগুলির একটি নতুন সেট পরিচয় করিয়ে দেয় এবং ফলস্বরূপ, করুটাইন ওয়ার্কার। আমাদের কোডে আমরা এই নতুন এপিআইগুলির একটি ব্যবহার করতে যাচ্ছি: 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() পদ্ধতিটি কলিং চালাতে পারি।

ওয়ার্কম্যানেজার এপিআই ডিজাইনকে সহজ করার জন্য কীভাবে করুটাইনগুলি ব্যবহার করা যেতে পারে তার একটি উদাহরণ।

এই কোডল্যাবটিতে আমরা আপনার অ্যাপ্লিকেশনটিতে করুটাইন ব্যবহার শুরু করতে হবে এমন বেসিকগুলি কভার করেছি!

আমরা কভার করেছি:

  • অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংকে সহজ করার জন্য ইউআই এবং ওয়ার্কম্যানেজার উভয় কাজের জন্য অ্যান্ড্রয়েড অ্যাপ্লিকেশনগুলিতে কীভাবে করুটাইনগুলি সংহত করবেন,
  • নেটওয়ার্ক থেকে ডেটা আনতে এবং মূল থ্রেডটি অবরুদ্ধ না করে এটি একটি ডাটাবেসে সংরক্ষণ করতে কীভাবে ViewModel ভিতরে করুটাইনগুলি ব্যবহার করবেন।
  • এবং ViewModel শেষ হয়ে গেলে কীভাবে সমস্ত করুটাইনগুলি বাতিল করতে হয়।

করুটাইন ভিত্তিক কোড পরীক্ষা করার জন্য, আমরা পরীক্ষার আচরণের পাশাপাশি পরীক্ষাগুলি থেকে সরাসরি suspend ফাংশনগুলি কল করে উভয়কেই কভার করি।

আরও জানুন

অ্যান্ড্রয়েডে আরও উন্নত করুটাইন ব্যবহার শিখতে " কোটলিন ফ্লো এবং লাইভডাটা" কোডেল্যাবের সাথে "অ্যাডভান্সড করুটাইনস " দেখুন।

কোটলিন করুটাইনগুলির অনেকগুলি বৈশিষ্ট্য রয়েছে যা এই কোডল্যাব দ্বারা আচ্ছাদিত ছিল না। আপনি যদি কোটলিন করুটাইনগুলি সম্পর্কে আরও জানতে আগ্রহী হন তবে জেটব্রাইনস দ্বারা প্রকাশিত কোরুটাইন গাইডগুলি পড়ুন। অ্যান্ড্রয়েডে করুটাইনগুলির আরও ব্যবহারের ধরণগুলির জন্য " কোটলিন করুটাইনগুলির সাথে অ্যাপের পারফরম্যান্স উন্নত করুন " দেখুন।