কো-রুটিন হলো একটি কনকারেন্সি ডিজাইন প্যাটার্ন যা অ্যান্ড্রয়েডে অ্যাসিঙ্ক্রোনাসভাবে এক্সিকিউট হওয়া কোডকে সহজ করার জন্য ব্যবহার করা যায়। কোটলিনের ১.৩ সংস্করণে কো-রুটিন যুক্ত করা হয়েছিল এবং এটি অন্যান্য ভাষার প্রতিষ্ঠিত ধারণার উপর ভিত্তি করে তৈরি।
অ্যান্ড্রয়েডে, কো-রুটিন দীর্ঘ সময় ধরে চলা কাজগুলো পরিচালনা করতে সাহায্য করে, যা অন্যথায় মূল থ্রেডকে ব্লক করে দিতে পারে এবং আপনার অ্যাপকে প্রতিক্রিয়াহীন করে তুলতে পারে। কো-রুটিন ব্যবহারকারী ৫০%-এরও বেশি পেশাদার ডেভেলপার তাদের উৎপাদনশীলতা বৃদ্ধির কথা জানিয়েছেন। এই আলোচনায় বর্ণনা করা হয়েছে কীভাবে আপনি কোটলিন কো-রুটিন ব্যবহার করে এই সমস্যাগুলো সমাধান করতে পারেন, যা আপনাকে আরও পরিচ্ছন্ন এবং সংক্ষিপ্ত অ্যাপ কোড লিখতে সক্ষম করবে।
বৈশিষ্ট্য
অ্যান্ড্রয়েডে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের জন্য আমাদের প্রস্তাবিত সমাধান হলো কোরাউটিন। এর উল্লেখযোগ্য বৈশিষ্ট্যগুলো হলো:
- লাইটওয়েট : সাসপেনশন সমর্থনের কারণে আপনি একটি একক থ্রেডে অনেক কো-রুটিন চালাতে পারেন, যা কো-রুটিন চলমান থ্রেডটিকে ব্লক করে না। সাসপেন্ডিং ব্লকিংয়ের তুলনায় মেমরি সাশ্রয় করে এবং একই সাথে অনেকগুলো অপারেশন সমর্থন করে।
- মেমরি লিক কম হয় : একটি স্কোপের মধ্যে অপারেশন চালানোর জন্য স্ট্রাকচার্ড কনকারেন্সি ব্যবহার করুন।
- অন্তর্নির্মিত বাতিলকরণ সমর্থন : বাতিলকরণ স্বয়ংক্রিয়ভাবে চলমান কো-রুটিন স্তরক্রম জুড়ে ছড়িয়ে পড়ে।
- জেটপ্যাক ইন্টিগ্রেশন : অনেক জেটপ্যাক লাইব্রেরিতে এমন এক্সটেনশন থাকে যা সম্পূর্ণ কো-রুটিন সাপোর্ট প্রদান করে। কিছু লাইব্রেরি তাদের নিজস্ব কো-রুটিন স্কোপও প্রদান করে, যা আপনি স্ট্রাকচার্ড কনকারেন্সির জন্য ব্যবহার করতে পারেন।
উদাহরণগুলির সংক্ষিপ্ত বিবরণ
অ্যাপ আর্কিটেকচার গাইডের উপর ভিত্তি করে, এই টপিকের উদাহরণগুলো একটি নেটওয়ার্ক রিকোয়েস্ট করে এবং ফলাফলটি মেইন থ্রেডে ফেরত পাঠায়, যেখানে অ্যাপটি পরবর্তীতে ব্যবহারকারীকে ফলাফলটি প্রদর্শন করতে পারে।
বিশেষত, ViewModel Architecture কম্পোনেন্টটি নেটওয়ার্ক রিকোয়েস্ট চালু করার জন্য মেইন থ্রেডে রিপোজিটরি লেয়ারকে কল করে। এই নির্দেশিকাটিতে এমন বিভিন্ন সমাধান আলোচনা করা হয়েছে, যা মেইন থ্রেডকে আনব্লকড রাখতে কো-রুটিন ব্যবহার করে।
ViewModel এক সেট KTX এক্সটেনশন অন্তর্ভুক্ত রয়েছে যা সরাসরি কো-রুটিনের সাথে কাজ করে। এই এক্সটেনশনগুলো হলো lifecycle-viewmodel-ktx লাইব্রেরি এবং এই নির্দেশিকায় এগুলোই ব্যবহৃত হয়েছে।
নির্ভরশীলতার তথ্য
আপনার অ্যান্ড্রয়েড প্রজেক্টে কো-রুটিন ব্যবহার করতে, আপনার অ্যাপের build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিটি যোগ করুন:
গ্রোভি
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
কোটলিন
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
ব্যাকগ্রাউন্ড থ্রেডে নির্বাহ করা হচ্ছে
মেইন থ্রেডে নেটওয়ার্ক রিকোয়েস্ট করলে, রেসপন্স না পাওয়া পর্যন্ত এটি অপেক্ষা করে বা ব্লক হয়ে যায় । যেহেতু থ্রেডটি ব্লক থাকে, তাই OS onDraw() কল করতে পারে না, যার ফলে আপনার অ্যাপটি ফ্রিজ হয়ে যায় এবং সম্ভবত একটি অ্যাপ্লিকেশন নট রেসপন্ডিং (ANR) ডায়ালগ বক্স দেখা যায়। ব্যবহারকারীর আরও ভালো অভিজ্ঞতার জন্য, এই অপারেশনটি একটি ব্যাকগ্রাউন্ড থ্রেডে চালানো যাক।
প্রথমে, চলুন আমাদের Repository ক্লাসটি দেখে নিই এবং এটি কীভাবে নেটওয়ার্ক রিকোয়েস্টটি করছে তা জেনে নিই:
sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() } private const val loginUrl = "https://example.com/login" class LoginRepository(private val responseParser: LoginResponseParser) { // Function that makes the network request, blocking the current thread fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { requestMethod = "POST" setRequestProperty("Content-Type", "application/json; utf-8") setRequestProperty("Accept", "application/json") doOutput = true outputStream.write(jsonBody.toByteArray()) return Result.Success(responseParser.parse(inputStream)) } return Result.Error(Exception("Cannot open HttpURLConnection")) } }
makeLoginRequest একটি সিনক্রোনাস ফাংশন এবং এটি কলিং থ্রেডকে ব্লক করে রাখে। নেটওয়ার্ক রিকোয়েস্টের রেসপন্স মডেল করার জন্য আমাদের নিজস্ব Result ক্লাস রয়েছে।
যখন ব্যবহারকারী, উদাহরণস্বরূপ, কোনো বাটনে ক্লিক করেন, তখন ViewModel নেটওয়ার্ক অনুরোধটি ট্রিগার করে:
class LoginViewModel( private val loginRepository: LoginRepository ) : ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) } }
পূর্ববর্তী কোডটিতে, নেটওয়ার্ক রিকোয়েস্ট করার সময় LoginViewModel UI থ্রেডকে ব্লক করে রাখছিল। মেইন থ্রেড থেকে এক্সিকিউশন সরিয়ে নেওয়ার সবচেয়ে সহজ সমাধান হলো একটি নতুন কো-রুটিন তৈরি করে I/O থ্রেডে নেটওয়ার্ক রিকোয়েস্টটি এক্সিকিউট করা:
class LoginViewModel( private val loginRepository: LoginRepository ) : ViewModel() { fun login(username: String, token: String) { // Create a new coroutine to move the execution off the UI thread viewModelScope.launch(Dispatchers.IO) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) } } }
চলুন login ফাংশনের কো-রুটিন কোডটি বিশ্লেষণ করা যাক:
-
viewModelScopeহলো একটি পূর্বনির্ধারিতCoroutineScopeযাViewModelKTX এক্সটেনশনের সাথে অন্তর্ভুক্ত থাকে। মনে রাখবেন যে, সমস্ত কোরাউটিন অবশ্যই একটি স্কোপের মধ্যে চলতে হবে। একটিCoroutineScopeএক বা একাধিক সম্পর্কিত কোরাউটিনকে পরিচালনা করে। -
launchহলো এমন একটি ফাংশন যা একটি কো-রুটিন তৈরি করে এবং এর ফাংশন বডির এক্সিকিউশন সংশ্লিষ্ট ডিসপ্যাচারের কাছে প্রেরণ করে। -
Dispatchers.IOনির্দেশ করে যে এই কো-রুটিনটি I/O অপারেশনের জন্য সংরক্ষিত একটি থ্রেডে নির্বাহ করা উচিত।
login ফাংশনটি নিম্নরূপে সম্পাদিত হয়:
- অ্যাপটি প্রধান থ্রেডে
Viewলেয়ার থেকেloginফাংশনটি কল করে। -
launchএকটি নতুন কো-রুটিন তৈরি করে, এবং নেটওয়ার্ক অনুরোধটি I/O অপারেশনের জন্য সংরক্ষিত একটি থ্রেডে স্বাধীনভাবে করা হয়। - কো-রুটিনটি চলার সময়,
loginফাংশনটি তার কার্য সম্পাদন করতে থাকে এবং রিটার্ন করে, সম্ভবত নেটওয়ার্ক অনুরোধটি শেষ হওয়ার আগেই। উল্লেখ্য যে, সরলতার জন্য, নেটওয়ার্ক প্রতিক্রিয়াটি আপাতত উপেক্ষা করা হচ্ছে।
যেহেতু এই কো-রুটিনটি viewModelScope দিয়ে শুরু করা হয়, তাই এটি ViewModel এর স্কোপের মধ্যে এক্সিকিউট হয়। যদি ব্যবহারকারী স্ক্রিন থেকে অন্য কোথাও চলে যাওয়ার কারণে ViewModel ডেস্ট্রয় হয়ে যায়, তাহলে viewModelScope স্বয়ংক্রিয়ভাবে বাতিল হয়ে যায় এবং এর সাথে চলমান সমস্ত কো-রুটিনও বাতিল হয়ে যায়।
পূর্ববর্তী উদাহরণটির একটি সমস্যা হলো, makeLoginRequest কল করে এমন যেকোনো কিছুকে অবশ্যই মনে রাখতে হবে যে এক্সিকিউশনটি মেইন থ্রেড থেকে সরিয়ে নিতে হবে। চলুন দেখি, এই সমস্যাটির সমাধান করার জন্য আমরা কীভাবে Repository পরিবর্তন করতে পারি।
মেইন-সেফটির জন্য কো-রুটিন ব্যবহার করুন
আমরা একটি ফাংশনকে মেইন-সেফ বলে মনে করি যখন এটি মেইন থ্রেডে UI আপডেটকে ব্লক করে না। makeLoginRequest ফাংশনটি মেইন-সেফ নয়, কারণ মেইন থ্রেড থেকে makeLoginRequest কল করলে UI ব্লক হয়ে যায়। একটি কো-রুটিনের এক্সিকিউশনকে অন্য থ্রেডে সরিয়ে নিতে `coroutines` লাইব্রেরির withContext() ফাংশনটি ব্যবহার করুন।
class LoginRepository( // ... ) { // ... suspend fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { // Move the execution of the coroutine to the I/O dispatcher return withContext(Dispatchers.IO) { // Blocking network request code } } }
withContext(Dispatchers.IO) ` কো-রুটিনের এক্সিকিউশনকে একটি I/O থ্রেডে স্থানান্তর করে, যা আমাদের কলিং ফাংশনটিকে মেইন-সেফ করে তোলে এবং প্রয়োজন অনুযায়ী UI আপডেট করতে সক্ষম করে।
makeLoginRequest suspend কীওয়ার্ড দিয়েও চিহ্নিত করা হয়। এই কীওয়ার্ডটি হলো কোটলিনের একটি পদ্ধতি, যার মাধ্যমে কোনো ফাংশনকে কো-রুটিনের ভেতর থেকে কল করার জন্য বাধ্য করা হয়।
নিচের উদাহরণে, কো-রুটিনটি LoginViewModel এ তৈরি করা হয়েছে। যেহেতু makeLoginRequest এক্সিকিউশনকে মেইন থ্রেড থেকে সরিয়ে নেয়, তাই login ফাংশনের ভেতরের কো-রুটিনটি এখন মেইন থ্রেডে এক্সিকিউট করা যাবে:
class LoginViewModel( private val loginRepository: LoginRepository ) : ViewModel() { fun login(username: String, token: String) { // Create a new coroutine on the UI thread viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // Make the network call and suspend execution until it finishes val result = loginRepository.makeLoginRequest(jsonBody) // Display result of the network request to the user when (result) { is Result.Success<LoginResponse> -> { /* Happy path */ } else -> { /* Show error in UI */ } } } } }
উল্লেখ্য যে, এখানেও কো-রুটিনের প্রয়োজন আছে, কারণ makeLoginRequest একটি suspend ফাংশন, এবং সকল suspend ফাংশন অবশ্যই একটি কো-রুটিনের মধ্যে সম্পাদন করতে হয়।
এই কোডটি আগের login উদাহরণ থেকে কয়েকটি দিক দিয়ে আলাদা:
-
launchDispatchers.IOপ্যারামিটারটি ব্যবহৃত হয় না। যখন আপনিlaunchএ কোনোDispatcherপাস করেন না, তখনviewModelScopeথেকে লঞ্চ করা যেকোনো coroutine মেইন থ্রেডে রান করে। - নেটওয়ার্ক অনুরোধের ফলাফল এখন সফল বা ব্যর্থ UI প্রদর্শনের জন্য পরিচালনা করা হয়।
লগইন ফাংশনটি এখন নিম্নরূপে কার্যকর হয়:
- অ্যাপটি প্রধান থ্রেডে
Viewলেয়ার থেকেlogin()ফাংশনটি কল করে। -
launchপ্রধান থ্রেডে একটি নতুন কো-রুটিন তৈরি করে এবং কো-রুটিনটি তার কার্য সম্পাদন শুরু করে। - কো-রুটিনের মধ্যে
loginRepository.makeLoginRequest()কলটি এখন কো-রুটিনের পরবর্তী এক্সিকিউশন স্থগিত করে রাখে, যতক্ষণ নাmakeLoginRequest()এর ভেতরেরwithContextব্লকটির এক্সিকিউশন শেষ হয়। -
withContextব্লকটির কাজ শেষ হয়ে গেলে,login()ফাংশনের ভেতরের কো-রুটিনটি নেটওয়ার্ক রিকোয়েস্টের ফলাফল নিয়ে মেইন থ্রেডে পুনরায় চালু হয়।
ব্যতিক্রম পরিচালনা
Repository লেয়ারের থ্রো করা এক্সেপশনগুলো হ্যান্ডেল করার জন্য, কোটলিনের বিল্ট-ইন এক্সেপশন সাপোর্ট ব্যবহার করুন। নিচের উদাহরণে, আমরা একটি try-catch ব্লক ব্যবহার করেছি:
class LoginViewModel( private val loginRepository: LoginRepository ) : ViewModel() { fun login(username: String, token: String) { viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" val result = try { loginRepository.makeLoginRequest(jsonBody) } catch (e: Exception) { Result.Error(Exception("Network request failed")) } when (result) { is Result.Success<LoginResponse> -> { /* Happy path */ } else -> { /* Show error in UI */ } } } } }
এই উদাহরণে, makeLoginRequest() কল দ্বারা নিক্ষিপ্ত যেকোনো অপ্রত্যাশিত ব্যতিক্রম UI-তে একটি ত্রুটি হিসাবে প্রদর্শিত হয়।
অতিরিক্ত কোরাউটিন রিসোর্স
অ্যান্ড্রয়েডে কো-রুটিন সম্পর্কে আরও বিস্তারিত জানতে, “Improve app performance with Kotlin coroutines” দেখুন।
কো-রুটিন সম্পর্কিত আরও তথ্যের জন্য, নিম্নলিখিত লিঙ্কগুলি দেখুন:
- কোরাউটিনগুলির সংক্ষিপ্ত বিবরণ (জেটব্রেইনস)
- কোরাউটিন গাইড (জেটব্রেইনস)
- কোটলিন কোরাউটিন এবং ফ্লো সম্পর্কিত অতিরিক্ত রিসোর্স