Part 2 — Compose’da Lifecycle

Muratdayan
4 min readJul 8, 2024

--

Lifecycle In Compose

Merhabalar, serimizin 2.partıyla devam ediyoruz. Bu yazıda Compose’da lifecycle konusunu konuşacağız. Hazırsanız başlayalım.

Compose’da bileşenler composition işlemine tabi tutulur. Composition projemizin UI ağacıdır diyebiliriz. Projeyi ilk kez çalıştırdığınızda tüm bileşenler composition’a tabi tutulur ve ekranımızda çizilir.Ardından ise state’i değişen composable fonksiyonlar ekranda recomposition’a uğrayarak güncellenir. Composition’u değiştirmenin tek yolu recompositiondur. Bu yapıyı yukarıdaki resim ile daha güzel anlayabiliriz.

Bir composable fonksiyonunun yaşam döngüsü, bir bileşenin Composition’a eklenmesi, bir veya daha fazla kez yeniden çizilmesi (recomposition) ve son olarak Composition’dan çıkarılması ile tanımlanır.

Bu composition yapısı Activity veya fragmentlardaki lifecycle evrelerinden daha basit ve anlayışlıdır.

Bir composition’da her bileşen fonksiyondur bunu biliyoruz ve her bileşen çağrıldığında composition’da kendi yaşam döngüsü vardır.

@Composable
fun MyComposable() {
Column {
Text("Hello")
Text("World")
}
}

Yukarıdaki MyComposable fonksiyonu çağrıldığında, içindeki Column ve Text bileşenleri kendi yaşam döngülerini oluşturur. Her bir çağrı kendi instance'ını oluşturur ve yönetir.

Composition’da Composable Fonksiyonların Anatomisi

Call Site: Composition’da her bir Composable fonksiyonun örneği Call Site dediğimiz yapı tarafından tanımlanır.

Compose derleyicisi her Call Site’yi ayrı olarak değerlendirir. Birden çok Call Site tarafından çağrılan composable fonksiyonların her birinin birden çok örneği oluşturulur.

Call Site bir composable fonksiyonun kaynak kodunun yerini belirler ve UI ağacını yani compositionu direkt olarak etkiler.

Recomposition sırasında composable fonksiyonumuz ,farklı composable fonksiyonları çağırıyorsa, Compose hangi composable fonksiyonların çağrıldığını veya çağrılmadığını belirler ve önceki kompozisyonda çağrılan composable fonksiyonları atlar. Bu da recomposition’un önceki yazıda da söylediğimiz gibi iyimser olduğu özelliğidir.

@Composable
fun LoginScreen(showError: Boolean) {
if (showError) {
LoginError()
}
LoginInput()
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

Yukarıdaki örnekte, LoginError sadece showError true olduğunda çağrılırken, LoginInput ilk compositionda kesin olarak çağrılır. Ardından, showError false olduğunda bile LoginInput yeniden çizilmez. Her çağrının kendine ait benzersiz bir call site’si vardır.

Recomposition’u Akıllı Şekilde Ayarlamak

Bir composable fonksiyon aynı çağrı yerinden birkaç defa çağrılırsa, Compose bu çağrıları benzersiz yapmak için yürütme sırasını kullanır bu da uygulamada istenmeyen sonuçlar doğurabilir.

@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
MovieOverview(movie)
}

Yukarıdaki kodda film listesi ilk olarak composition’a uğradıktan sonra, listenin sonuna yeni bir veri eklendiğinde yeni veri için instance’ı oluşturulur ve önceki veriler tutulabilir. Ama liste sırası değiştirilir, liste ortasına veya başına yeni veri gelirse ya da listenin kendisi değişirse tüm instance’lar tekrardan composition’a tabi tutulur.O yüzden bu kodda bir side-effect kodunuz varsa effect işlemi devam ederken recomposition olursa efekt iptal olup tekrardan çalıştırılacak.

Compose’da bu durumu çözmek için key kullanabiliriz. key keyword’u ile listedeki verilerin sırasını vs.. değiştirirsek composition’da önceki instance’ları kullanarak sırası değiştirilir ve tekrardan recomposition’a uğratmaz. Bir key değerinin global olarak benzersiz olması gerekmez, yalnızca çağrı alanındaki composable fonksiyonların çağrıları arasında benzersiz olması gerekir. Yani bu örnekte, her filmin filmler arasında benzersiz bir key’e sahip olması gerekir; bu key’in uygulamanın başka bir yerindeki başka bir composable ile paylaşması sorun değildir.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
Column {
for (movie in movies) {
key(movie.id) { // Unique ID for this movie
MovieOverview(movie)
}
}
}
}

Yukarıdaki kodda liste elemanlarının yeri değişse bile compose MovieOverview’a yapılan çağrıları key üzerinden tanır ve tekrardan recomposition işlemine tabi tutmaz. Bu da uygulamamıza daha çok hız ve akıcılık sağlar performansı çoğaltır. Bu sayede side-effect kodlar varsa da çalışmaya devam eder ve uygulamamızda stabilite sağlar.

NOT: Bazı composable fonksiyonlar kendi içinde key değerine zaten sahiptirler. Örneğin LazyColumn fonksiyonunda aşağıdaki gibi kullanabiliriz.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
LazyColumn {
items(movies, key = { movie -> movie.id }) { movie ->
MovieOverview(movie)
}
}
}

Recomposition Neleri Atlar?

Recomposition sırasında bazı fonksiyonlar çizilmeden atlanır dedik. Aşağıdaki durumlar haricinde fonksiyon recompositiondan atlanabilir.

1- Composable Fonksiyonun dönüş tipi Unit değilse,

2-Fonksiyon @NonRestartableComposable veya @NonSkippableComposable anotasyonları ile işaretlenmişse,

3- Gerekli bir parametre stabil olmayan bir tipteyse

fonksiyonumuz recompositiona uğramak zorundadır.

Bir Property’nin Stabil Olarak Kabul Edilmesi

1- Aynı instance’ları equals ile karşılaştırıldığında sonucu her zaman aynı olmalıdır.

2- Türün bir özelliği değişirse, Örneğin mutableState türlerinde türün bir özelliği değişirse compose’a bildirim verilmelidir.

3- Tüm public property’ler stabildir.

NOT: Tüm değişmez immutable tipler stable olarak kabul edilir.

Stabil Olarak Kabul Edilen Türler

1- Primitive Tipler,

2- String,

3- Lambda fonksiyonları da stabil olarak kabul edilir çünkü fonksiyon referansları değişmez.

NOT: Compose, MutableState türünü stabil olarak kabul eder. MutableState, içindeki değerin (value'sinin) değiştiğinde Composition'a bildirimde bulunur. Bu nedenle, MutableState türündeki bir değişkenin değeri değiştiğinde, Composition bu değişikliği fark eder ve gerekli yeniden kompozisyonları gerçekleştirir.

Stabil Olarak Kabul Edilmeyen Türler

1- Interfaceler,

2- Eğer bir türün public özellikleri değişebilir ise, bu tür stabil olarak kabul edilmez çünkü bu özelliklerin değerleri değişebilir ve bu da Composition’a bildirilmelidir.

@Stable Özelliği

Eğer Compose bir türün stabil olup olmadığını belirleyemiyorsa, bu türü @Stable anotasyonu ile işaretleyerek Compose'a stabil olduğunu belirtebilirsiniz.

@Stable
interface UiState<T : Result<T>> {
val value: T?
val exception: Throwable?

val hasError: Boolean
get() = exception != null
}

Yukarıdaki kod örneğinde, UiState interface'i @Stable anotasyonu ile işaretlenmiştir. Bu, Compose'a UiState interface’sinin stabil olduğunu belirtir ve bu interfacein kullanıldığı composable fonksiyonlarının recomposition sırasında daha akıllıca davranmasını sağlar. Bu sayede, UiState interfacesinin tüm uygulamaları da stabil olarak kabul edilir.

Sonuç

Bu bölümde Jetpack Compose’da lifecycle ve composition konularını ele aldık. Composable fonksiyonların nasıl yeniden çizildiğini ve performansı artırmak için nelere dikkat etmemiz gerektiğini öğrendik. Ayrıca, stabil türlerin önemini, @Stable anotasyonunun kullanımını ve call site kavramını keşfettik.

Bu bilgilerle, Jetpack Compose ile daha verimli ve performanslı uygulamalar geliştirebilirsiniz. Bir sonraki bölümde görüşmek üzere!

Mutlu kodlamalar! 😊

KAYNAKLAR

https://developer.android.com/develop/ui/compose/lifecycle

  • String: String de stabil olarak kabul edilir çünkü immutable'dır.
  • Fonksiyon türleri (lambdalar): Lambda fonksiyonları da stabil olarak kabul edilir çünkü fonksiyon referansları değişmez.

--

--