Yaşam döngüsüne duyarlı bileşenlerle (Görünümler) Kotlin eş yordamlarını kullanma

Kavramlar ve Jetpack Compose uygulaması

Kotlin eş yordamları, eşzamansız kod yazmanıza olanak tanıyan bir API sağlar. Kotlin coroutine'leri ile CoroutineScope tanımlayabilirsiniz. Bu, coroutine'lerinizin ne zaman çalışması gerektiğini yönetmenize yardımcı olur. Her eşzamansız işlem belirli bir kapsamda çalışır.

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

KTX bağımlılıkları ekleme

Bu konuda açıklanan yerleşik coroutine kapsamları, ilgili her bileşenin KTX uzantılarında yer alı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 sonraki bir sürümü kullanın.
  • LifecycleScope için androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 veya sonraki bir sürümü kullanın.
  • liveData için androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 veya sonraki bir sürümü kullanın.

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

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

ViewModelScope

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. Burada, yalnızca ViewModel etkinse yapılması gereken işleriniz olduğunda eş yordamlar kullanışlıdır. Örneğin, bir düzen için bazı verileri hesaplıyorsanız çalışmayı ViewModel ile sınırlandırmanız gerekir. Böylece ViewModel temizlenirse kaynak tüketimini önlemek için çalışma otomatik olarak iptal edilir.

Aşağıdaki örnekte gösterildiği gibi, ViewModel öğesinin CoroutineScope özelliğine ViewModel öğesinin viewModelScope özelliği üzerinden erişebilirsiniz:

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

LifecycleScope

Her Lifecycle nesnesi için bir LifecycleScope tanımlanır. Bu kapsamda başlatılan tüm eş yordamlar, Lifecycle yok edildiğinde iptal edilir. CoroutineScope Lifecycle'e lifecycle.coroutineScope veya lifecycleOwner.lifecycleScope mülkleri üzerinden erişebilirsiniz.

Aşağıdaki örnekte, önceden hesaplanmış metni asenkron olarak oluşturmak için lifecycleOwner.lifecycleScope'ın 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ılabilen yaşam döngüsüne duyarlı coroutine'ler

lifecycleScope, Lifecycle DESTROYED olduğunda uzun süren işlemleri otomatik olarak iptal etmek için uygun bir yöntem sunsa da Lifecycle belirli bir durumdayken bir kod bloğunun yürütülmesini başlatmak ve başka bir durumdayken iptal etmek istediğiniz başka durumlar olabilir. Örneğin, Lifecycle STARTED olduğunda bir akışı toplamak ve STOPPED olduğunda toplamayı iptal etmek isteyebilirsiniz. Bu yaklaşım, akış emisyonlarını yalnızca kullanıcı arayüzü ekranda görünür olduğunda işler. Böylece kaynak tasarrufu sağlanır ve uygulama çökmeleri önlenebilir.

Bu durumlarda Lifecycle ve LifecycleOwner, tam olarak bunu yapan suspend repeatOnLifecycle API'sini sağlar. Aşağıdaki örnekte, ilişkili Lifecycle en azından STARTED durumunda olduğunda her seferinde çalışan ve Lifecycle STOPPED olduğunda iptal edilen bir kod bloğu yer almaktadır:

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 yapmanız 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, yaşam döngüsüne duyarlı koleksiyonu birden fazla akışta paralel olarak gerçekleştirmeniz gerekiyorsa her akışı farklı eş yordamlarda toplamanız gerekir. Bu durumda, repeatOnLifecycle() öğesini doğrudan kullanmak daha verimlidir:

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ı coroutine'leri askıya alma

CoroutineScope, uzun süren işlemleri otomatik olarak iptal etmek için uygun bir yöntem sunsa da Lifecycle belirli bir durumda olmadığı sürece kod bloğunun yürütülmesini askıya almak istediğiniz başka durumlar olabilir. Örneğin, FragmentTransaction yayınlamak için Lifecycle en az STARTED olana kadar beklemeniz gerekir. Bu durumlarda Lifecycle ek yöntemler sunar:lifecycle.whenCreated, lifecycle.whenStarted ve lifecycle.whenResumed. Bu blokların içinde çalıştırılan tüm eş yordamlar, Lifecycle en azından istenen minimum durumda değilse askıya alınır.

Aşağıdaki örnekte, yalnızca ilişkili Lifecycle en azından STARTED durumundayken çalışan bir kod bloğu yer almaktadır:

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 coroutine, when yöntemlerinden biri aracılığıyla etkin durumdayken Lifecycle yok edilirse coroutine otomatik olarak iptal edilir. Aşağıdaki örnekte, finally bloğu, Lifecycle durumu DESTROYED olduğunda bir kez çalışı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ılırken değerleri eşzamansız olarak hesaplamanız gerekebilir. Örneğin, kullanıcının tercihlerini alıp kullanıcı arayüzünüzde sunmak isteyebilirsiniz. Bu gibi durumlarda, sonucu LiveData nesnesi olarak sunan bir suspend fonksiyonunu çağırmak için liveData oluşturucu fonksiyonunu kullanabilirsiniz.

Aşağıdaki örnekte loadUser(), başka bir yerde tanımlanmış bir askıya alma işlevidir. liveData oluşturucu işlevini kullanarak loadUser() işlevini eşzamansız olarak çağırın, ardından sonucu yayınlamak 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şı, coroutine'ler ve LiveData arasında yapılandırılmış eşzamanlılık ilkelidir. Kod bloğu, LiveData etkinleştiğinde yürütülmeye başlar ve LiveData devre dışı kaldığında yapılandırılabilir bir zaman aşımından sonra otomatik olarak iptal edilir. Tamamlanmadan önce iptal edilirse LiveData tekrar etkinleştiğinde yeniden başlatılır. Önceki çalıştırmada başarıyla tamamlandıysa yeniden başlatılmaz. Yalnızca otomatik olarak iptal edilirse yeniden başlatılır. Blok başka bir nedenle (ör. CancellationException atılması) iptal edilirse yeniden başlatılmaz.

Bloktan birden fazla değer de yayınlayabilirsiniz. Her emit() çağrısı, ana iş parçacığında LiveData değeri ayarlanana kadar bloğun 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))
    }
}

Ayrıca, aşağıdaki örnekte gösterildiği gibi liveData ile Transformations öğesini de 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 LiveData öğesinden birden fazla değer yayınlayabilirsiniz. emit() veya emitSource() için yapılan her çağrının daha önce 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)
                }
            )
        }
    }
}

Daha fazla eş yordam bilgisi için aşağıdaki bağlantılara bakın:

Ek kaynaklar

Yaşam döngüsüne duyarlı bileşenlerle birlikte eş yordamları kullanma hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.

Örnekler

Bloglar