Jetpack Compose Recomposition Yapısı | Kotlin Android Geliştirme #3

ibrahimcanerdogan
4 min readMar 23, 2024

--

Zorunlu bir UI modelinde, bir widget’ı değiştirmek için widget üzerinde bir setter çağırarak dahili durumunu değiştirirsiniz. Compose’da, composable fonksiyonu yeni verilerle tekrar çağırırsınız. Bunu yapmak, işlevin yeniden oluşturulmasına neden olur, işlev tarafından yayılan pencere öğeleri, gerekirse yeni verilerle yeniden çizilir. Compose çerçevesi akıllı bir şekilde yalnızca değişen bileşenleri yeniden oluşturabilir.

Recomposition Temeli

Örneğin, bir Button görüntüleyen bu oluşturulabilir işlevi düşünün:

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")
}
}

Düğmeye her tıklandığında, çağıran clicks değerini günceller. Compose, yeni değeri göstermek için lambda’yı Text işleviyle birlikte yeniden çağırır; bu işleme “recomposition” denir. Değere bağlı olmayan diğer işlevler yeniden birleştirilmez.

Tüm UI ağacını yeniden oluşturmak hesaplama açısından pahalı olabilir ve bu da hesaplama gücünü ve pil ömrünü tüketir. Compose, bu akıllı yeniden birleştirme ile bu sorunu çözer.

Recomposition, girdiler değiştiğinde oluşturulabilir işlevlerinizi yeniden çağırma işlemidir. Bu, işlevin girdileri değiştiğinde gerçekleşir. Compose yeni girdilere göre yeniden birleştirme yaparken, yalnızca değişmiş olabilecek fonksiyonları veya lambdaları çağırır ve diğerlerini atlar. Değişen parametrelere sahip olmayan tüm fonksiyonları veya lambdaları atlayarak, Compose verimli bir şekilde yeniden oluşturabilir.

Yapılmaması Gerekenler;

  • Shared Object özelliğine yazma,
  • ViewModel’de bir observable güncelleme,
  • Shared Preferences güncelleme gibi işlemler yapmanız gerekiyorsa, bunu bir arka plan coroutine’inde yapın ve değer sonucunu parametre olarak Composable fonksiyona aktarın.

Örnek olarak, bu kod SharedPreferences’daki bir değeri güncellemek için bir composable oluşturur. Composable, paylaşılan tercihlerin kendisini okumamalı veya yazmamalıdır. Bunun yerine, bu kod okuma ve yazma işlemlerini bir arka plan coroutine içerisinde bir ViewModel’e taşır. Uygulama mantığı, güncellemeyi tetiklemek için geçerli değeri bir callback ile iletir.

@Composable
fun SharedPrefsToggle(
text: String,
value: Boolean,
onValueChanged: (Boolean) -> Unit
) {
Row {
Text(text)
Checkbox(checked = value, onCheckedChange = onValueChanged)
}
}

Compose Çalışma Sırası

Composable bir fonksiyonun koduna bakarsanız, kodun göründüğü sırada çalıştırıldığını varsayabilirsiniz. Ancak bu her zaman doğru değildir. Composable bir fonksiyon diğer composable fonksiyonlara çağrılar içeriyorsa, bu fonksiyonlar herhangi bir sırada çalışabilir. Compose, bazı UI öğelerinin diğerlerinden daha yüksek önceliğe sahip olduğunu kabul etme ve önce onları çizme seçeneğine sahiptir.

Örneğin, bir sekme düzeninde üç ekran çizmek için aşağıdaki gibi bir kodunuz olduğunu varsayalım:

@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}

StartScreen, MiddleScreen ve EndScreen çağrıları herhangi bir sırada gerçekleşebilir. Bu, örneğin StartScreen() işlevinin global bir değişkeni ayarlamasını ve MiddleScreen() işlevinin bu değişiklikten yararlanmasını sağlayamayacağınız anlamına gelir. Bunun yerine, bu işlevlerin her birinin kendi içinde bağımsız olması gerekir.

Neden State Kullanılmalıdır?

Composable bir işlev çağrıldığında, çağrı, çağırandan farklı bir iş parçacığında gerçekleşebilir. Bu da, hem bu tür kodlar iş parçacığı güvenli olmadığından hem de iş parçacığı güvenli lambda’nın kabul edilemez bir side-effect olduğundan, composable bir lambda’daki değişkenleri değiştiren koddan kaçınılması gerektiği anlamına gelir.

@Composable
fun ListComposable(myList: List<String>) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
}
}
Text("Count: ${myList.size}")
}
}

Bu kod side-effectsizdir ve giriş listesini UI’ye dönüştürür. Bu, küçük bir listeyi görüntülemek için harika bir koddur. Ancak, işlev yerel bir değişkene yazıyorsa, bu kod iş parçacığı güvenli veya doğru olmayacaktır:

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // Avoid! Side-effect of the column recomposing.
}
}
Text("Count: $items")
}

Bu örnekte, öğeler her yeniden düzenlemede değiştirilir. Bu, bir animasyonun her karesi veya liste güncellendiğinde olabilir. Her iki durumda da kullanıcı arayüzü yanlış sayıyı gösterecektir. Bu nedenle, Compose’da bu gibi yazmalar desteklenmez; bu yazmaları yasaklayarak, çerçevenin iş parçacıklarını değiştirerek birleştirilebilir lambdaları yürütmesine izin veririz.

Recomposition Nasıl Kullanılır?

Jetpack Compose’da, UI öğelerinin verimli bir şekilde oluşturulmasını sağlamak için recomposition işlemi akıllıca optimize edilmiştir. UI’nin bazı kısımları geçersiz hale geldiğinde, Compose gereksiz yeniden değerlendirmeleri atlayarak yalnızca güncelleme gerektiren belirli kısımları yeniden oluşturmaya çalışır. Bu optimizasyon, özellikle karmaşık kullanıcı arayüzlerinde performansı artırmak için çok önemlidir.

Değişmeyen öğeler için yeniden birleştirmeyi atlama özelliği, verilen kod parçacığında örneklendirilmiştir. NamePicker @Composable fonksiyonu, Compose’un yalnızca başlık değiştiğinde isim listesini yeniden oluşturmayı nasıl atlayabileceğini göstermektedir. Benzer şekilde, LazyColumn içinde Compose, isim listesi değişmeden kalırsa tek tek öğeler için yeniden birleştirmeyi atlamayı seçebilir.

Bu yeniden birleştirmeyi atlama özelliği yalnızca performansı artırmakla kalmaz, aynı zamanda daha sorunsuz bir kullanıcı deneyimi sağlar. Gereksiz yeniden hesaplamaları en aza indiren Compose, kullanıcı arayüzü güncellemelerinin hızlı ve duyarlı olmasını sağlar.

Bununla birlikte, kullanıcı arayüzünün bütünlüğünü korumak için her composable fonksiyonun veya lambda’nın side-effectsiz olması gerektiğine dikkat etmek önemlidir. Side-effect, doğrudan composable fonksiyonun içinde değil, bu örnekte bir isme tıklandığında olduğu gibi geri aramalardan tetiklenmelidir. Bu uygulama yalnızca daha temiz ve bakımı daha kolay bir kodu desteklemekle kalmaz, aynı zamanda Compose’un UI mantığını yan etkilerden ayırma tasarım ilkeleriyle de uyumludur.

@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) -> Unit
) {
Column {
Text(header, style = MaterialTheme.typography.bodyLarge)
Divider()
LazyColumn {
items(names) { name ->
NamePickerItem(name, onNameClicked)
}
}
}
}

@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

Özetle, Compose’un değişmeyen UI öğeleri için yeniden birleştirme işlemini atlayabilmesi, performansı ve yanıt verebilirliği önemli ölçüde artırır. Geliştiriciler en iyi uygulamalara bağlı kalarak uygulamalarının doğruluğunu ve sağlamlığını sağlarken bu özellikten etkili bir şekilde faydalanabilirler.

Jetpack Compose Recomposition Yapısı | Kotlin Android Geliştirme #3

İbrahim Can Erdoğan

LINKEDIN

YOUTUBE

UDEMY

GITHUB

--

--

ibrahimcanerdogan
ibrahimcanerdogan

Written by ibrahimcanerdogan

Hi, My name is Ibrahim, I am developing ebebek android app within Ebebek. I publish various articles in the field of programming and self-improvement.

No responses yet