Yaşam döngüsüne duyarlı bileşenlerle Kotlin eş yordamlarını kullanın

Kotlin eş yordamları, eşzamansız kod yazmanıza olanak tanıyan bir API sağlar. Kotlin eş yordamlarıyla, eş yordamlarınızın ne zaman çalışması gerektiğini yönetmenize yardımcı olan bir CoroutineScope tanımlayabilirsiniz. Her eşzamansız işlem, belirli bir kapsam dahilinde çalışır.

Yaşam döngüsüne duyarlı bileşenler, LiveData ile birlikte çalışabilirlik katmanının yanı sıra uygulamanızdaki mantıksal kapsamlar için eş yordamlar için birinci sınıf destek sağlar. Bu konuda, eş yordamların yaşam döngüsüne duyarlı bileşenlerle etkili bir şekilde nasıl kullanılacağı açıklanmaktadır.

KTX bağımlılıkları ekleyin

Bu konuda açıklanan yerleşik eş yordam kapsamları, karşılık gelen her bileşen için KTX uzantılarında yer almaktadır. Bu kapsamları kullanırken uygun bağımlılıkları eklediğinizden emin olun.

  • ViewModelScope için androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 veya üstünü kullanın.
  • LifecycleScope için androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 veya üstünü kullanın.
  • liveData için androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 veya üstünü kullanın.

Yaşam döngüsüne duyarlı eş yordam kapsamları

Yaşam döngüsüne duyarlı bileşenler, uygulamanızda kullanabileceğiniz aşağıdaki yerleşik kapsamları tanımlar.

ModelKapsamı Görüntüleme

Uygulamanızdaki her ViewModel için bir ViewModelScope tanımlanır. Bu kapsamda başlatılan tüm eş yordamlar, ViewModel temizlenirse otomatik olarak iptal edilir. Eşler burada, yalnızca ViewModel etkinse yapılması gereken işler için kullanışlıdır. Örneğin, bir düzen için bazı verileri hesaplıyorsanız işin kapsamını ViewModel öğesine ayarlamanız gerekir. Böylece, ViewModel temizlenirse kaynak tüketiminin önüne geçmek için iş otomatik olarak iptal edilir.

Aşağıdaki örnekte gösterildiği gibi, bir ViewModel öğesinin CoroutineScope öğesine ViewModel'in viewModelScope özelliği aracılığıyla erişebilirsiniz:

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

Yaşam DöngüsüKapsamı

Her bir Lifecycle nesnesi için bir LifecycleScope tanımlanır. Bu kapsamda başlatılan tüm eş yordamlar, Lifecycle kaldırıldığında iptal edilir. Lifecycle öğesinin CoroutineScope öğesine lifecycle.coroutineScope veya lifecycleOwner.lifecycleScope mülkleri aracılığıyla erişebilirsiniz.

Aşağıdaki örnekte, önceden hesaplanmış metni eşzamansız olarak oluşturmak için lifecycleOwner.lifecycleScope özelliğinin nasıl kullanılacağı gösterilmektedir:

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

Yeniden başlatılabilir Yaşam Döngüsüne duyarlı eş yordamlar

lifecycleScope, Lifecycle DESTROYED olduğunda uzun süreli işlemleri otomatik olarak iptal etmek için uygun bir yol sağlasa da, Lifecycle belirli bir durumda olduğunda kod bloğunun yürütülmesini başlatmak ve başka bir durumda iptal etmek isteyebileceğiniz başka durumlarınız olabilir. Örneğin, Lifecycle STARTED olduğunda bir akış toplamak ve STOPPED olduğunda koleksiyonu iptal etmek isteyebilirsiniz. Bu yaklaşım, akış emisyonlarını yalnızca kullanıcı arayüzü ekranda göründüğünde işleyerek kaynaklardan tasarruf sağlar ve potansiyel olarak uygulama kilitlenmelerini önler.

Bu durumlarda Lifecycle ve LifecycleOwner, tam olarak bunu yapan askıya alma repeatOnLifecycle API'sini sağlar. Aşağıdaki örnek, ilişkilendirilen Lifecycle en az STARTED durumunda olduğunda her çalışan ve Lifecycle STOPPED olduğunda iptal edilen bir kod bloğu içerir:

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

Yaşam döngüsüne duyarlı akış toplama

Yalnızca tek bir akışta yaşam döngüsüne duyarlı toplama işlemi gerçekleştirmeniz gerekiyorsa kodunuzu basitleştirmek için Flow.flowWithLifecycle() yöntemini kullanabilirsiniz:

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

Ancak paralel olarak birden çok akışta yaşam döngüsüne duyarlı toplama işlemi gerçekleştirmeniz gerekiyorsa her bir akışı farklı eş yordamlarda toplamanız gerekir. Bu durumda, doğrudan repeatOnLifecycle() kullanmak daha verimli olur:

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. */ }
        }
    }
}

Yaşam döngüsüne duyarlı eş yordamları askıya alma

CoroutineScope, uzun süreli işlemleri otomatik olarak iptal etmek için uygun bir yol sağlasa da, Lifecycle belirli bir durumda değilse kod bloğunun yürütülmesini askıya almak isteyebileceğiniz başka durumlarınız olabilir. Örneğin, bir FragmentTransaction çalıştırmak için Lifecycle en az STARTED olana kadar beklemeniz gerekir. Lifecycle, bu durumlarda ek yöntemler sunar: lifecycle.whenCreated, lifecycle.whenStarted ve lifecycle.whenResumed. Lifecycle en azından istenen minimum durumda değilse bu blokların içinde çalışan tüm eş yordam askıya alınır.

Aşağıdaki örnek, yalnızca ilişkilendirilen Lifecycle en az STARTED durumunda olduğunda çalışan bir kod bloğu içerir:

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.

        }
    }
}

Bir eş yordam, when yöntemlerinden biri kullanılarak etkinken Lifecycle kaldırılırsa eş yorda otomatik olarak iptal edilir. Aşağıdaki örnekte, finally bloğu Lifecycle durumu DESTROYED olduğunda çalıştırılır:

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 ile eş yordamları kullanma

LiveData kullanırken değerleri eşzamansız olarak hesaplamanız gerekebilir. Örneğin, bir kullanıcının tercihlerini alıp kullanıcı arayüzünüze sunmak isteyebilirsiniz. Bu durumlarda, liveData oluşturucu işlevini kullanarak bir suspend işlevini çağırabilir ve sonucu LiveData nesnesi olarak sunabilirsiniz.

Aşağıdaki örnekte loadUser(), başka bir yerde tanımlanan bir askıya alma işlevidir. loadUser() öğesini eşzamansız olarak çağırmak için liveData derleyici işlevini, ardından sonucu ortaya çıkarmak için emit() işlevini kullanın:

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

liveData yapı taşı, eş yordamlar ve LiveData arasında yapılandırılmış bir eş zamanlı temel öğe görevi görür. Kod bloğu, LiveData etkin hale geldiğinde yürütülmeye başlar ve LiveData etkin olmadığında, yapılandırılabilir bir zaman aşımının ardından otomatik olarak iptal edilir. İşlem tamamlanmadan önce iptal edilirse LiveData tekrar etkin hale gelirse yeniden başlatılır. Önceki bir çalıştırmada başarılı bir şekilde tamamlanırsa yeniden başlatılmaz. Yalnızca otomatik olarak iptal edildiğinde yeniden başlatıldığını unutmayın. Engelleme başka bir nedenden dolayı (ör. CancellationException gönderilmesi) iptal edilirse engelleme yeniden başlatılmaz.

Ayrıca, bloktan birden çok değer de yayınlayabilirsiniz. Her emit() çağrısı, ana iş parçacığında LiveData değeri ayarlanana kadar engellemenin yürütülmesini askıya alır.

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

Aşağıdaki örnekte gösterildiği gibi liveData ile Transformations kodunu da birleştirebilirsiniz:

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

Yeni bir değer yayınlamak istediğinizde emitSource() işlevini çağırarak bir LiveData öğesinden birden fazla değer yayınlayabilirsiniz. emit() veya emitSource() çağrılarının önceden eklenen kaynağı kaldırdığını unutmayın.

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

Eş yordamlarla ilgili daha fazla bilgi için aşağıdaki bağlantılara bakın:

Ek kaynaklar

Yaşam döngüsüne duyarlı bileşenlere sahip eş yordamları kullanma hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara başvurun.

Sana Özel

Bloglar