از کوروتین های کاتلین با اجزای آگاه از چرخه حیات استفاده کنید

کوروتین های Kotlin یک API را ارائه می دهند که به شما امکان می دهد کدهای ناهمزمان بنویسید. با کوروتین‌های Kotlin، می‌توانید یک 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 یا بالاتر استفاده کنید.

حوزه های کاری آگاه از چرخه زندگی

مؤلفه‌های مربوط به چرخه حیات، حوزه‌های داخلی زیر را تعریف می‌کنند که می‌توانید در برنامه خود استفاده کنید.

ViewModelScope

یک ViewModelScope برای هر ViewModel در برنامه شما تعریف شده است. اگر ViewModel پاک شود، هر برنامه‌ای که در این محدوده راه‌اندازی شود، به‌طور خودکار لغو می‌شود. Coroutine ها در اینجا برای زمانی مفید هستند که کاری دارید که فقط در صورت فعال بودن ViewModel باید انجام دهید. به عنوان مثال، اگر مقداری داده را برای یک طرح بندی محاسبه می کنید، باید کار را به ViewModel محدود کنید تا اگر ViewModel پاک شود، کار به طور خودکار لغو شود تا از مصرف منابع جلوگیری شود.

همانطور که در مثال زیر نشان داده شده است، می توانید از طریق ویژگی viewModelScope ViewModel به CoroutineScope یک ViewModel دسترسی داشته باشید:

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

LifecycleScope

یک LifecycleScope برای هر شیء Lifecycle تعریف شده است. هر برنامه‌ای که در این محدوده راه‌اندازی می‌شود، زمانی که Lifecycle از بین می‌رود، لغو می‌شود. شما می توانید از طریق lifecycle.coroutineScope یا lifecycleOwner.lifecycleScope به CoroutineScope از Lifecycle دسترسی داشته باشید.

مثال زیر نحوه استفاده از 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 روش مناسبی برای لغو خودکار عملیات طولانی مدت در هنگام DESTROYED Lifecycle ارائه می‌کند، ممکن است موارد دیگری داشته باشید که بخواهید اجرای یک بلوک کد را زمانی که Lifecycle در وضعیت خاصی است شروع کنید و زمانی که آن را لغو کنید، آن را لغو کنید. در حالت دیگری است برای مثال، ممکن است بخواهید زمانی که Lifecycle STARTED ، جریانی را جمع‌آوری کنید و پس از STOPPED آن، مجموعه را لغو کنید. این رویکرد انتشارات جریان را فقط زمانی پردازش می کند که رابط کاربری روی صفحه نمایش قابل مشاهده باشد، منابع را صرفه جویی می کند و احتمالاً از خرابی برنامه جلوگیری می کند.

برای این موارد، Lifecycle و LifecycleOwner API suspend repeatOnLifecycle را ارائه می دهند که دقیقاً این کار را انجام می دهد. مثال زیر حاوی یک بلوک کد است که هر بار که 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.

        }
    }
}

اگر Lifecycle در حالی که یک کوروتین فعال است از طریق یکی از روش‌های when از بین برود، کوروتین به طور خودکار لغو می‌شود. در مثال زیر، بلوک finally پس از DESTROYED حالت Lifecycle اجرا می‌شود:

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

بلوک سازنده liveData به عنوان یک همزمانی اولیه ساختاریافته بین کوروتین ها و LiveData عمل می کند. بلوک کد با فعال شدن LiveData شروع به اجرا می‌کند و پس از غیرفعال شدن LiveData پس از یک مهلت زمانی قابل تنظیم، به‌طور خودکار لغو می‌شود. اگر قبل از تکمیل لغو شود، اگر LiveData دوباره فعال شود دوباره راه اندازی می شود. اگر در اجرای قبلی با موفقیت کامل شد، راه اندازی مجدد نمی شود. توجه داشته باشید که فقط در صورت لغو خودکار، راه اندازی مجدد می شود. اگر بلوک به هر دلیل دیگری لغو شود (مثلاً پرتاب CancellationException )، دوباره راه اندازی نمی شود.

همچنین می توانید چندین مقدار را از بلوک منتشر کنید. هر فراخوانی 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)
                }
            )
        }
    }
}

برای اطلاعات بیشتر در رابطه با کوروتین ها، به لینک های زیر مراجعه کنید:

منابع اضافی

برای کسب اطلاعات بیشتر در مورد استفاده از کوروتین ها با مؤلفه های مربوط به چرخه حیات، به منابع اضافی زیر مراجعه کنید.

نمونه ها

وبلاگ ها

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}