লাইফসাইকেল-সচেতন কম্পোনেন্ট (ভিউ) এর সাথে কোটলিন কোরাউটিন ব্যবহার করুন

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

কোটলিন কোরাউটিন একটি এপিআই প্রদান করে যা আপনাকে অ্যাসিঙ্ক্রোনাস কোড লিখতে দেয়। কোটলিন কোরাউটিনের সাহায্যে, আপনি একটি CoroutineScope নির্ধারণ করতে পারেন, যা আপনার কোরাউটিনগুলো কখন রান করবে তা পরিচালনা করতে সাহায্য করে। প্রতিটি অ্যাসিঙ্ক্রোনাস অপারেশন একটি নির্দিষ্ট স্কোপের মধ্যে চলে।

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

KTX নির্ভরতা যোগ করুন

এই টপিকে বর্ণিত বিল্ট-ইন কো-রুটিন স্কোপগুলো প্রতিটি সংশ্লিষ্ট কম্পোনেন্টের KTX এক্সটেনশনের মধ্যে অন্তর্ভুক্ত থাকে। এই স্কোপগুলো ব্যবহার করার সময় যথাযথ ডিপেন্ডেন্সিগুলো যোগ করতে ভুলবেন না।

  • ViewModelScope জন্য androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 বা উচ্চতর সংস্করণ ব্যবহার করুন।
  • LifecycleScope এর জন্য androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 বা উচ্চতর সংস্করণ ব্যবহার করুন।
  • liveData জন্য androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 বা উচ্চতর সংস্করণ ব্যবহার করুন।

লাইফসাইকেল-সচেতন কোরাউটিন স্কোপ

লাইফসাইকেল-অ্যাওয়ার কম্পোনেন্টগুলো নিম্নলিখিত বিল্ট-ইন স্কোপগুলো সংজ্ঞায়িত করে, যা আপনি আপনার অ্যাপে ব্যবহার করতে পারেন।

ভিউমডেলস্কোপ

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

নিচের উদাহরণে দেখানো অনুযায়ী, আপনি ViewModel এর viewModelScope প্রপার্টির মাধ্যমে সেই ViewModel এর CoroutineScope অ্যাক্সেস করতে পারেন:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

জীবনচক্র পরিধি

প্রতিটি Lifecycle অবজেক্টের জন্য একটি LifecycleScope সংজ্ঞায়িত করা থাকে। Lifecycle ধ্বংস হয়ে গেলে এই স্কোপের মধ্যে চালু হওয়া যেকোনো কো-রুটিন বাতিল হয়ে যায়। আপনি lifecycle.coroutineScope অথবা lifecycleOwner.lifecycleScope প্রপার্টির মাধ্যমে Lifecycle CoroutineScope অ্যাক্সেস করতে পারেন।

নিচের উদাহরণটি দেখায় কিভাবে অ্যাসিঙ্ক্রোনাসভাবে প্রি-কম্পিউটেড টেক্সট তৈরি করতে lifecycleOwner.lifecycleScope ব্যবহার করতে হয়:

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}

পুনরায় চালুযোগ্য লাইফসাইকেল-সচেতন কোরাউটিন

যদিও lifecycleScope Lifecycle DESTROYED হলে দীর্ঘ সময় ধরে চলা অপারেশনগুলো স্বয়ংক্রিয়ভাবে বাতিল করার একটি সঠিক উপায় প্রদান করে, তবুও আপনার এমন অন্যান্য পরিস্থিতিও থাকতে পারে যেখানে আপনি Lifecycle একটি নির্দিষ্ট অবস্থায় থাকাকালীন কোনো কোড ব্লকের এক্সিকিউশন শুরু করতে চান এবং অন্য অবস্থায় গেলে তা বাতিল করতে চান। উদাহরণস্বরূপ, আপনি Lifecycle STARTED অবস্থায় একটি ফ্লো সংগ্রহ করতে চাইতে পারেন এবং এটি STOPPED হলে সংগ্রহটি বাতিল করতে চাইতে পারেন। এই পদ্ধতিটি শুধুমাত্র স্ক্রিনে UI দৃশ্যমান থাকাকালীন ফ্লো এমিশনগুলো প্রসেস করে, যা রিসোর্স সাশ্রয় করে এবং সম্ভাব্য অ্যাপ ক্র্যাশ এড়াতে সাহায্য করে।

এইসব ক্ষেত্রে, Lifecycle এবং LifecycleOwner `suspend repeatOnLifecycle API প্রদান করে যা ঠিক এই কাজটিই করে। নিম্নলিখিত উদাহরণটিতে একটি কোড ব্লক রয়েছে যা সংশ্লিষ্ট Lifecycle অন্তত ` STARTED অবস্থায় থাকলেই চলে এবং Lifecycle STOPPED অবস্থায় গেলে বাতিল হয়ে যায়:

class MyFragment : Fragment() {

    val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Create a new coroutine in the lifecycleScope
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                viewModel.someDataFlow.collect {
                    // Process item
                }
            }
        }
    }
}

জীবনচক্র-সচেতন প্রবাহ সংগ্রহ

যদি আপনাকে শুধুমাত্র একটি ফ্লো-এর উপর লাইফসাইকেল-সচেতন ডেটা সংগ্রহ করতে হয়, তাহলে আপনার কোডকে সরল করার জন্য আপনি Flow.flowWithLifecycle() মেথডটি ব্যবহার করতে পারেন:

viewLifecycleOwner.lifecycleScope.launch {
    exampleProvider.exampleFlow()
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect {
            // Process the value.
        }
}

তবে, যদি আপনাকে সমান্তরালভাবে একাধিক ফ্লো-তে লাইফসাইকেল-সচেতন কালেকশন করতে হয়, তাহলে আপনাকে অবশ্যই প্রতিটি ফ্লো আলাদা আলাদা কো-রুটিনে কালেক্ট করতে হবে। সেক্ষেত্রে, সরাসরি repeatOnLifecycle() ব্যবহার করা আরও বেশি কার্যকর:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Because collect is a suspend function, if you want to
        // collect multiple flows in parallel, you need to do so in
        // different coroutines.
        launch {
            flow1.collect { /* Process the value. */ }
        }

        launch {
            flow2.collect { /* Process the value. */ }
        }
    }
}

লাইফসাইকেল-সচেতন কোরাউটিন স্থগিত করুন

যদিও CoroutineScope দীর্ঘ সময় ধরে চলা অপারেশনগুলো স্বয়ংক্রিয়ভাবে বাতিল করার একটি সঠিক উপায় প্রদান করে, তবুও এমন অন্যান্য পরিস্থিতিও থাকতে পারে যেখানে আপনি Lifecycle একটি নির্দিষ্ট অবস্থায় না আসা পর্যন্ত কোনো কোড ব্লকের এক্সিকিউশন স্থগিত রাখতে চান। উদাহরণস্বরূপ, একটি FragmentTransaction চালানোর জন্য, আপনাকে অবশ্যই Lifecycle অন্তত STARTED না হওয়া পর্যন্ত অপেক্ষা করতে হবে। এই ধরনের পরিস্থিতির জন্য, Lifecycle অতিরিক্ত কিছু মেথড প্রদান করে: lifecycle.whenCreated, lifecycle.whenStarted , এবং lifecycle.whenResumed । এই ব্লকগুলোর ভেতরে চালিত যেকোনো কো-রুটিন স্থগিত হয়ে যায় যদি Lifecycle ন্যূনতম কাঙ্ক্ষিত অবস্থায় না থাকে।

নীচের উদাহরণটিতে এমন একটি কোড ব্লক রয়েছে যা শুধুমাত্র তখনই চলে যখন সংশ্লিষ্ট Lifecycle অন্তত STARTED অবস্থায় থাকে:

class MyFragment: Fragment {
    init { // Notice that we can safely launch in the constructor of the Fragment.
        lifecycleScope.launch {
            whenStarted {
                // The block inside will run only when Lifecycle is at least STARTED.
                // It will start executing when fragment is started and
                // can call other suspend methods.
                loadingView.visibility = View.VISIBLE
                val canAccess = withContext(Dispatchers.IO) {
                    checkUserAccess()
                }

                // When checkUserAccess returns, the next line is automatically
                // suspended if the Lifecycle is not *at least* STARTED.
                // We could safely run fragment transactions because we know the
                // code won't run unless the lifecycle is at least STARTED.
                loadingView.visibility = View.GONE
                if (canAccess == false) {
                    findNavController().popBackStack()
                } else {
                    showContent()
                }
            }

            // This line runs only after the whenStarted block above has completed.

        }
    }
}

যদি কোনো কো-রুটিন সক্রিয় থাকা অবস্থায় when মেথডগুলোর কোনো একটির মাধ্যমে Lifecycle ডেস্ট্রয় করা হয়, তাহলে কো-রুটিনটি স্বয়ংক্রিয়ভাবে বাতিল হয়ে যায়। নিচের উদাহরণে, Lifecycle স্টেটটি DESTROYED হওয়ার পর finally ব্লকটি রান করে:

class MyFragment: Fragment {
    init {
        lifecycleScope.launchWhenStarted {
            try {
                // Call some suspend functions.
            } finally {
                // This line might execute after Lifecycle is DESTROYED.
                if (lifecycle.state >= STARTED) {
                    // Here, since we've checked, it is safe to run any
                    // Fragment transactions.
                }
            }
        }
    }
}

LiveData-এর সাথে কো-রুটিন ব্যবহার করুন

LiveData ব্যবহার করার সময়, আপনার অ্যাসিঙ্ক্রোনাসভাবে ভ্যালু গণনা করার প্রয়োজন হতে পারে। উদাহরণস্বরূপ, আপনি একজন ব্যবহারকারীর পছন্দগুলি সংগ্রহ করে আপনার UI-তে পরিবেশন করতে চাইতে পারেন। এই ধরনের ক্ষেত্রে, আপনি liveData বিল্ডার ফাংশন ব্যবহার করে একটি suspend ফাংশন কল করতে পারেন এবং ফলাফলটি একটি LiveData অবজেক্ট হিসাবে পরিবেশন করতে পারেন।

নীচের উদাহরণে, loadUser() হলো অন্য কোথাও ঘোষিত একটি `suspend` ফাংশন। liveData বিল্ডার ফাংশনটি ব্যবহার করে loadUser() অ্যাসিঙ্ক্রোনাসভাবে কল করুন, এবং তারপর emit() ব্যবহার করে ফলাফলটি ইমিট করুন:

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

The liveData building block serves as a structured concurrency primitive between coroutines and LiveData . The code block starts executing when LiveData becomes active and is automatically canceled after a configurable timeout when the LiveData becomes inactive. If it is canceled before completion, it is restarted if the LiveData becomes active again. If it completed successfully in a previous run, it doesn't restart. Note that it is restarted only if canceled automatically. If the block is canceled for any other reason (eg throwing a CancellationException ), it is not restarted.

আপনি ব্লকটি থেকে একাধিক মানও নির্গত করতে পারেন। প্রতিটি emit() কল ব্লকটির নির্বাহ স্থগিত রাখে, যতক্ষণ না প্রধান থ্রেডে LiveData মানটি সেট করা হয়।

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

আপনি liveData Transformations সাথেও একত্রিত করতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

class MyViewModel: ViewModel() {
    private val userId: LiveData<String> = MutableLiveData()
    val user = userId.switchMap { id ->
        liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(database.loadUserById(id))
        }
    }
}

যখনই আপনি একটি নতুন ভ্যালু নির্গত করতে চান, তখন emitSource() ফাংশনটি কল করে একটি LiveData থেকে একাধিক ভ্যালু নির্গত করতে পারেন। মনে রাখবেন যে, emit() বা emitSource() এর প্রতিটি কল পূর্বে যোগ করা সোর্সটিকে মুছে দেয়।

class UserDao: Dao {
    @Query("SELECT * FROM User WHERE id = :id")
    fun getUser(id: String): LiveData<User>
}

class MyRepository {
    fun getUser(id: String) = liveData<User> {
        val disposable = emitSource(
            userDao.getUser(id).map {
                Result.loading(it)
            }
        )
        try {
            val user = webservice.fetchUser(id)
            // Stop the previous emission to avoid dispatching the updated user
            // as `loading`.
            disposable.dispose()
            // Update the database.
            userDao.insert(user)
            // Re-establish the emission with success type.
            emitSource(
                userDao.getUser(id).map {
                    Result.success(it)
                }
            )
        } catch(exception: IOException) {
            // Any call to `emit` disposes the previous one automatically so we don't
            // need to dispose it here as we didn't get an updated value.
            emitSource(
                userDao.getUser(id).map {
                    Result.error(exception, it)
                }
            )
        }
    }
}

কো-রুটিন সম্পর্কিত আরও তথ্যের জন্য, নিম্নলিখিত লিঙ্কগুলি দেখুন:

অতিরিক্ত সম্পদ

লাইফসাইকেল-অ্যাওয়ার কম্পোনেন্টের সাথে কো-রুটিন ব্যবহার সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্সগুলো দেখুন।

নমুনা

ব্লগ