Yazılı Düşünme

Jetpack Compose, Android için modern bir bildirime dayalı kullanıcı arayüzü araç setidir. Compose, ön uç görünümlerini zorunlu olarak değiştirmeden uygulama kullanıcı arayüzünüzü oluşturmanıza olanak tanıyan bir tanımlayıcı API sağlayarak uygulama kullanıcı arayüzünüzü yazmayı ve sürdürmeyi kolaylaştırır. Bu terminolojinin açıklanması gerekir ancak bu terimlerin uygulama tasarımınız açısından önemi büyüktür.

Açıklayıcı programlama paradigması

Android görünüm hiyerarşisi geçmişte kullanıcı arayüzü widget'larından oluşan bir ağaç olarak temsil edilebilirdi. Uygulamanın durumu, kullanıcı etkileşimleri gibi nedenlerle değiştikçe kullanıcı arayüzü hiyerarşisinin mevcut verileri göstermek için güncellenmesi gerekir. Kullanıcı arayüzünü güncellemenin en yaygın yolu, findViewById() gibi işlevleri kullanarak ağaçta gezinmek ve button.setText(String), container.addChild(View) veya img.setImageBitmap(Bitmap) gibi yöntemleri çağırarak düğümleri değiştirmektir. Bu yöntemler, widget'ın dahili durumunu değiştirir.

Görüntülemelerle manuel olarak oynamak hata olasılığını artırır. Bir veri birden fazla yerde oluşturuluyorsa bu veriyi gösteren görünümlerden birini güncellemeyi unutabilirsiniz. İki güncelleme beklenmedik bir şekilde çakıştığında yasa dışı durumlar oluşturmak da kolaydır. Örneğin, bir güncelleme, kullanıcı arayüzünden yeni kaldırılmış bir düğümün değerini ayarlamaya çalışabilir. Genel olarak, yazılım bakımının karmaşıklığı, güncellenmesi gereken görünümlerin sayısıyla birlikte artar.

Son birkaç yıldır tüm sektör, kullanıcı arayüzleri oluşturma ve güncellemeyle ilgili mühendislik çalışmalarını büyük ölçüde basitleştiren açıklayıcı bir kullanıcı arayüzü modeline geçmeye başladı. Bu teknik, ekranın tamamını sıfırdan yeniden oluşturarak ve ardından yalnızca gerekli değişiklikleri uygulayarak çalışır. Bu yaklaşım, durum bilgisine sahip bir görünüm hiyerarşisini manuel olarak güncellemenin karmaşıklığını önler. Compose, bildirime dayalı bir kullanıcı arayüzü çerçevesidir.

Ekranın tamamını yeniden oluşturmanın bir zorluğu, zaman, bilgi işlem gücü ve pil kullanımı açısından pahalı olmasıdır. Bu maliyeti azaltmak için Compose, kullanıcı arayüzünün hangi bölümlerinin belirli bir zamanda yeniden çizilmesi gerektiğini akıllıca seçer. Bu, Yeniden oluşturma bölümünde ele alındığı gibi kullanıcı arayüzü bileşenlerinizi nasıl tasarladığınızla ilgili bazı sonuçlar doğurur.

Basit bir composable işlevi

Compose'u kullanarak, veri alan ve kullanıcı arayüzü öğeleri yayınlayan bir dizi birleştirilebilir işlev tanımlayarak kullanıcı arayüzünüzü oluşturabilirsiniz. Basit bir örnek olarak, bir String alan ve karşılama mesajı gösteren bir Text widget yayınlayan bir Greeting widget verilebilir.

"Merhaba Dünya" metnini ve bu kullanıcı arayüzünü oluşturan basit Composable işlevinin kodunu gösteren bir telefonun ekran görüntüsü

Şekil 1. Veri alan ve ekranda bir metin widget'ı oluşturmak için bu verileri kullanan basit bir birleştirilebilir işlev.

Bu işlevle ilgili dikkate değer birkaç nokta:

  • İşlev, @Composable ek açıklamasıyla ek açıklamaya sahiptir. Tüm Composable işlevlerinde bu ek açıklama bulunmalıdır. Bu ek açıklama, Compose derleyicisine bu işlevin verilerin kullanıcı arayüzüne dönüştürülmesi amacıyla tasarlandığını bildirir.

  • İşlev verileri alır. Birleştirilebilir işlevler, uygulama mantığının kullanıcı arayüzünü tanımlamasına olanak tanıyan parametreleri kabul edebilir. Bu durumda widget'ımız, kullanıcıyı isme göre selamlayabilmek için String değerini kabul eder.

  • İşlev, kullanıcı arayüzünde metin gösterir. Bunu, metin kullanıcı arayüzü öğesini oluşturan Text()composable işlevini çağırarak yapar. Composable işlevler, diğer composable işlevleri çağırarak kullanıcı arayüzü hiyerarşisi oluşturur.

  • İşlev hiçbir şey döndürmez. Kullanıcı arayüzü widget'ları oluşturmak yerine istenen ekran durumunu tanımladıkları için kullanıcı arayüzü yayınlayan oluşturma işlevlerinin herhangi bir değer döndürmesi gerekmez.

  • Bu işlev hızlı, idempotent ve yan etkisizdir.

    • İşlev, aynı bağımsız değişkenle birden çok kez çağrıldığında aynı şekilde davranır ve genel değişkenler veya random() çağrıları gibi diğer değerleri kullanmaz.
    • İşlev, kullanıcı arayüzünü özellikleri veya genel değişkenleri değiştirme gibi yan etkiler olmadan tanımlar.

    Genel olarak, tüm birleştirilebilir işlevler Yeniden derleme bölümünde açıklanan nedenlerden dolayı bu özelliklerle yazılmalıdır.

Beyanla ilgili paradigma değişikliği

Nesne yönelimli birçok zorunlu kullanıcı arayüzü araç setinde, bir widget ağacı oluşturarak kullanıcı arayüzünü başlatırsınız. Bunu genellikle bir XML düzen dosyasını şişirerek yaparsınız. Her widget kendi dahili durumunu korur ve uygulama mantığının widget ile etkileşim kurmasına olanak tanıyan alıcı ve ayarlayıcı yöntemleri gösterir.

Compose'un açıklayıcı yaklaşımında widget'lar nispeten durumsuzdur ve ayarlayıcı veya alıcı işlevleri göstermez. Aslında widget'lar nesne olarak gösterilmez. Aynı birleştirilebilir işlevi farklı bağımsız değişkenlerle çağırarak kullanıcı arayüzünü güncelleyebilirsiniz. Bu, Uygulama mimarisi kılavuzunda açıklandığı gibi ViewModel gibi mimari kalıplara durum eklemeyi kolaylaştırır. Ardından, gözlemlenebilir veriler her güncellendiğinde mevcut uygulama durumunu kullanıcı arayüzüne dönüştürmekten bileşenleriniz sorumludur.

Oluşturma kullanıcı arayüzündeki veri akışının, üst düzey nesnelerden alt öğelerine kadar olan kısmını gösteren görsel.

Şekil 2. Uygulama mantığı, üst düzey birleştirilebilir işleve veri sağlar. Bu işlev, diğer derlenebilir öğeleri çağırarak kullanıcı arayüzünü açıklamak için verileri kullanır ve uygun verileri bu derlenebilir öğelere ve hiyerarşinin alt kısmına iletir.

Kullanıcı kullanıcı arayüzüyle etkileşimde bulunduğunda kullanıcı arayüzü onClick gibi etkinlikler oluşturur. Bu etkinlikler, uygulama mantığını bilgilendirmelidir. Uygulama mantığı da uygulamanın durumunu değiştirebilir. Durum değiştiğinde, birleştirilebilir işlevler yeni verilerle tekrar çağrılır. Bu, kullanıcı arayüzü öğelerinin yeniden çizilmesine neden olur. Bu sürece yeniden oluşturma denir.

Kullanıcı arayüzü öğelerinin, uygulama mantığı tarafından yönetilen etkinlikleri tetikleyerek etkileşime nasıl yanıt verdiğini gösteren görsel.

Şekil 3. Kullanıcı bir kullanıcı arayüzü öğesiyle etkileşime geçerek bir etkinliğin tetiklenmesine neden oldu. Uygulama mantığı etkinliğe yanıt verir, ardından derlenebilir işlevler gerekirse yeni parametrelerle otomatik olarak tekrar çağrılır.

Dinamik içerik

Birleştirilebilir işlevler XML yerine Kotlin'de yazıldığı için diğer Kotlin kodları kadar dinamik olabilir. Örneğin, bir kullanıcı listesini karşılayan bir kullanıcı arayüzü oluşturmak istediğinizi varsayalım:

@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}

Bu işlev, bir ad listesi alır ve her kullanıcı için bir karşılama mesajı oluşturur. Kompozit işlevler oldukça karmaşık olabilir. Belirli bir kullanıcı arayüzü öğesini göstermek isteyip istemediğinize karar vermek için if ifadelerini kullanabilirsiniz. Döngüleri kullanabilirsiniz. Yardımcı işlevleri çağırabilirsiniz. Temel dilin tüm esnekliğine sahip olursunuz. Bu güç ve esneklik, Jetpack Compose'un en önemli avantajlarından biridir.

Yeniden derleme

Zorunlu bir kullanıcı arayüzü modelinde, bir widget'ı değiştirmek için widget'ın dahili durumunu değiştirmek üzere bir ayarlayıcıyı çağırırsınız. Oluştur'da, derlenebilir işlevi yeni verilerle tekrar çağırırsınız. Bu işlem, işlevin yeniden derlenmesine neden olur. İşlev tarafından yayınlanan widget'lar gerekirse yeni verilerle yeniden çizilir. Oluşturma çerçevesi, yalnızca değişen bileşenleri akıllıca yeniden oluşturabilir.

Örneğin, bir düğme görüntüleyen bu derlenebilir işlevi düşünün:

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

Düğme her tıklandığında, arayan clicks değerini günceller. Oluştur, yeni değeri göstermek için lambda'yı Text işleviyle tekrar çağırır. Bu sürece yeniden oluşturma denir. Değere bağlı olmayan diğer işlevler yeniden derlenmez.

Daha önce de belirttiğimiz gibi, kullanıcı arayüzü ağacının tamamını yeniden oluşturmak, hesaplama gücü ve pil ömrü kullanan, hesaplama açısından pahalı olabilir. Oluşturma, bu akıllı yeniden oluşturma özelliğiyle bu sorunu çözer.

Yeniden derleme, girişler değiştiğinde birleştirilebilir işlevlerinizi tekrar çağırma işlemidir. Bu durum, işlevin girişleri değiştiğinde gerçekleşir. Oluştur işlevi, yeni girişlere göre yeniden oluştururken yalnızca değişmiş olabilecek işlevleri veya lambdaları çağırır, geri kalanını atlar. Compose, parametreleri değişmeyen tüm işlevleri veya lambdaları atlayarak verimli bir şekilde yeniden derleme yapabilir.

Bir işlevin yeniden derlenmesi atlanabilir. Bu nedenle, birleştirilebilir işlevlerin yürütülmesinden kaynaklanan yan etkilere asla güvenmeyin. Bunu yaparsanız kullanıcılar uygulamanızda garip ve öngörülemeyen davranışlarla karşılaşabilir. Yan etki, uygulamanızın geri kalanı tarafından görülebilen tüm değişikliklerdir. Örneğin, aşağıdaki işlemlerin tümü tehlikeli yan etkilerdir:

  • Paylaşılan bir nesnenin mülküne yazma
  • ViewModel'te bir gözlemlenebiliri güncelleme
  • Paylaşılan tercihleri güncelleme

Birleştirilebilir işlevler, bir animasyon oluşturulurken olduğu gibi her karede bir yeniden yürütülebilir. Birleştirilebilir işlevler, animasyonlar sırasında takılma olmaması için hızlı olmalıdır. Paylaşılan tercihlerden okuma gibi pahalı işlemler yapmanız gerekiyorsa bunu arka planda bir coroutine'te yapın ve değer sonucunu, birleştirilebilir işleve parametre olarak iletin.

Örneğin, bu kod SharedPreferences içindeki bir değeri güncellemek için bir composable oluşturur. Kompozit, paylaşılan tercihlerden kendisi okumamalı veya yazmamalıdır. Bunun yerine bu kod, okuma ve yazma işlemlerini arka planda çalışan bir ViewModel içine taşır. Uygulama mantığı, güncellemeyi tetiklemek için mevcut değeri bir geri çağırma işleviyle iletir.

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

Bu dokümanda, Oluştur'u kullanırken dikkat etmeniz gereken bazı noktalar ele alınmaktadır:

  • Yeniden derleme, mümkün olduğunca fazla birleştirilebilir işlevi ve lambda'yı atlar.
  • Yeniden derleme işlemi iyimserdir ve iptal edilebilir.
  • Bir birleştirilebilir işlev, animasyondaki her kare kadar sıklıkla oldukça sık çalışabilir.
  • Kompozit işlevler paralel olarak yürütülebilir.
  • Birleştirilebilir işlevler herhangi bir sırada yürütülebilir.

Aşağıdaki bölümlerde, yeniden derlemeyi desteklemek için birleştirilebilir işlevlerin nasıl oluşturulacağı ele alınmaktadır. Her durumda, en iyi uygulama, birleştirilebilir işlevlerinizi hızlı, tekil ve yan etkisiz tutmaktır.

Yeniden derleme mümkün olduğunca atlar

Kullanıcı arayüzünüzün bölümleri geçersiz olduğunda Oluştur, yalnızca güncellenmesi gereken bölümleri yeniden oluşturmak için elinden geleni yapar. Bu, kullanıcı arayüzü ağacında bir düğmenin bileşenini yeniden çalıştırırken, düğmenin üstündeki veya altındaki bileşenlerden hiçbirini çalıştırmadan atlayabileceği anlamına gelir.

Her birleştirilebilir işlev ve lambda kendi kendine yeniden derlenebilir. Aşağıda, yeniden oluşturmanın bir liste oluştururken bazı öğeleri nasıl atlayabileceğini gösteren bir örnek verilmiştir:

/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.bodyLarge)
        HorizontalDivider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/**
 * Display a single name the user can click.
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

Bu kapsamların her biri, yeniden derleme sırasında yürütülecek tek şey olabilir. header değiştiğinde Compose, üst öğelerinden hiçbirini yürütmeden Column lambda işlevine atlayabilir. Ayrıca, Column yürütüldüğünde, names değişmediyse Oluştur, LazyColumn öğelerini atlamayı seçebilir.

Yine de tüm birleştirilebilir işlevlerin veya lambdaların yürütülmesi yan etkiden arındırılmış olmalıdır. Bir yan etki gerçekleştirmeniz gerektiğinde bunu geri çağırma işlevinden tetikleyin.

Yeniden oluşturma iyimser

Oluşturma, bir derlenebilir öğenin parametrelerinin değişmiş olabileceğini düşündüğünde yeniden oluşturma işlemini başlatır. Yeniden oluşturma işlemi iyimserdir. Bu, Oluştur'un, parametreler tekrar değişmeden önce yeniden oluşturma işlemini tamamlamayı beklediği anlamına gelir. Bir parametre, yeniden derleme tamamlanmadan önce değişirse Derle, yeniden derlemeyi iptal edip yeni parametreyle yeniden başlatabilir.

Oluşturma işlemi iptal edildiğinde Oluştur, kullanıcı arayüzü ağacını yeniden oluşturma işleminden kaldırır. Kullanıcı arayüzünün görüntülenmesine bağlı yan etkileriniz varsa kompozisyon iptal edilse bile yan etki uygulanır. Bu durum, uygulama durumunun tutarsız olmasına neden olabilir.

Tüm birleştirilebilir işlevlerin ve lambdaların iyimser yeniden derlemeyi işlemek için tekil ve yan etki içermediğinden emin olun.

Kompozit işlevler oldukça sık çalışabilir

Bazı durumlarda, bir birleştirilebilir işlev kullanıcı arayüzü animasyonunun her karesi için çalışabilir. İşlev, cihaz depolama alanından okuma gibi pahalı işlemler gerçekleştiriyorsa kullanıcı arayüzünde duraklamaya neden olabilir.

Örneğin, widget'ınız cihaz ayarlarını okumaya çalıştıysa bu ayarları saniyede yüzlerce kez okuyabilir. Bu da uygulamanızın performansı üzerinde feci etkiler yaratabilir.

Kompozit işleviniz veri gerektiriyorsa veriler için parametreler tanımlamalıdır. Ardından, pahalı çalışmaları, oluşturma dışında başka bir ileti dizisine taşıyabilir ve mutableStateOf veya LiveData kullanarak verileri Oluştur'a iletebilirsiniz.

Kompozit işlevler paralel olarak çalıştırılabilir

Compose, birleştirilebilir işlevleri paralel olarak çalıştırarak yeniden oluşturmayı optimize edebilir. Bu sayede Compose, birden fazla çekirdekten yararlanabilir ve ekranda olmayan birleştirilebilir işlevleri daha düşük öncelikli olarak çalıştırabilir.

Bu optimizasyon, birleştirilebilir bir işlevin arka plan iş parçacıkları havuzunda çalışabileceği anlamına gelir. Bir birleştirilebilir işlev, ViewModel üzerinde bir işlevi çağırırsa Compose bu işlevi aynı anda birkaç mesaj dizisinden çağırabilir.

Uygulamanızın doğru şekilde davranması için tüm derlenebilir işlevlerin yan etkisi olmamalıdır. Bunun yerine, her zaman kullanıcı arayüzü iş parçacığında yürütülen onClick gibi geri çağırmalardan yan etkiler tetikleyin.

Bir birleştirilebilir işlev çağrıldığında, çağrı, arayandan farklı bir iş parçacığında gerçekleşebilir. Bu, hem bu tür kodların iş parçacığı açısından güvenli olmaması hem de birleştirilebilir lambda'nın izin verilmeyen bir yan etkisi olması nedeniyle, birleştirilebilir lambda'daki değişkenleri değiştiren koddan kaçınılması gerektiği anlamına gelir.

Bir listeyi ve sayısını gösteren bir derlenebilir öğeyi gösteren örneği aşağıda bulabilirsiniz:

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

Bu kod yan etkisizdir ve giriş listesini kullanıcı arayüzüne dönüştürür. Bu kod, küçük bir liste görüntülemek için mükemmeldir. Ancak işlev yerel bir değişkene yazarsa bu kod iş parçacığı açısından güvenli veya doğru olmaz:

@Composable
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Card {
                    Text("Item: $item")
                    items++ // Avoid! Side-effect of the column recomposing.
                }
            }
        }
        Text("Count: $items")
    }
}

Bu örnekte, items her yeniden derlemeyle 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österir. Bu nedenle, bu tür yazma işlemleri Compose'da desteklenmez. Bu yazma işlemlerini yasaklayarak çerçevenin, birleştirilebilir lambdaları yürütmek için iş parçacıklarını değiştirmesine izin veririz.

Kompozit işlevler herhangi bir sırada yürütülebilir

Bir birleştirilebilir işlevin koduna bakarsanız kodun göründüğü sırada çalıştırıldığını varsayabilirsiniz. Ancak bu durumun doğruluğu garanti edilmez. Bir birleştirilebilir işlev, diğer birleştirilebilir işlevlere yönelik çağrılar içeriyorsa bu işlevler herhangi bir sırada çalışabilir. Oluşturma işlemi, bazı kullanıcı arayüzü öğelerinin diğerlerinden daha yüksek önceliğe sahip olduğunu algılayıp bunları önce çizebilir.

Örneğin, 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'ye yapılan aramalar herhangi bir sırada gerçekleşebilir. Bu, örneğin StartScreen()'ün bazı genel değişkenleri (yan etki) ayarlamasını ve MiddleScreen()'ün bu değişiklikten yararlanmasını sağlayamayacağınız anlamına gelir. Bunun yerine, bu işlevlerin her birinin kendi kendine yeterli olması gerekir.

Daha fazla bilgi

Compose ve birleştirilebilir işlevler hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.

Videolar