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ılan 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 bilgili görünüm hiyerarşisini manuel olarak güncellemenin karmaşıklığını önler. Compose, bildirime dayalı bir kullanıcı arayüzü çerçevesidir.

Tüm ekranı yeniden oluşturma konusundaki zorluklardan biri, zaman, bilgi işlem gücü ve pil kullanımı açısından potansiyel olarak pahalı olmasıdır. Compose, bu maliyeti azaltmak amacıyla herhangi bir zamanda kullanıcı arayüzünün hangi bölümlerinin yeniden çizilmesi gerektiğini akıllı bir şekilde seçer. Bunun, Yeniden düzenleme bölümünde açıklandığı gibi, kullanıcı arayüzü bileşenlerinizi tasarlama biçiminiz üzerinde bazı etkileri vardır.

Basit bir composable işlev

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.

"Hello World" metnini ve 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 notuyla 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. Özelleştirilebilir işlevler parametreleri kabul edebilir. Bu parametreler, uygulama mantığının kullanıcı arayüzünü tanımlamasına olanak tanır. 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, aslında metin kullanıcı arayüzü öğesini oluşturan Text() composable işlevini çağırarak yapar. Oluşturulabilir işlevler, diğer composable işlevleri çağırarak kullanıcı arayüzü hiyerarşisi yayar.

  • İş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ıdır, ihtiyatlıdır ve yan etki içermez.

    • İş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 composable işlevler Yeniden oluşturma bölümünde açıklanan nedenlerle bu özelliklerle yazılmalıdır.

Beyanla ilgili paradigma değişikliği

Nesne odaklı 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 composable'larınız mevcut uygulama durumunu bir kullanıcı arayüzüne dönüştürmekten 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 composable 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, composable 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 işlenen etkinlikleri tetikleyerek etkileşime nasıl tepki verdiğini gösteren resim.

Şekil 3. Kullanıcının bir kullanıcı arayüzü öğesiyle etkileşime geçmesi bir etkinliğin tetiklenmesine neden olmuştur. Uygulama mantığı etkinliğe yanıt verir. Ardından, composable işlevleri 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, ad listesini 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ı bir şekilde yeniden oluşturabilir.

Örneğin, bir düğme görüntüleyen şu composable işlevi ele alalım:

@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 düzenleme, girişler değiştiğinde composable işlevlerinizi tekrar çağırma işlemidir. Bu durum, işlevin girişleri değiştiğinde ortaya çıkar. 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.

İşlevin yeniden oluşturulması atlanabileceği için hiçbir zaman composable işlevleri yürütmenin yan etkileri olmasın. 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 tercihler güncelleniyor

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 ile 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 composable işlev, oldukça sık, bir animasyonun her karesi kadar sık çalıştırılabilir.
  • 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, composable işlevlerinizi hızlı, bağlantılı ve yan etkiden uzak 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. Oluşturma, header değiştiğinde üst öğelerinden birini yürütmeden Column lambdasına atlayabilir. Ayrıca, Column yürütüldüğünde, names değişmediyse Oluştur, LazyColumn öğelerini atlamayı seçebilir.

Yine, composable işlevlerin veya lambda'ların çalıştırılmasında yan etki bulunmamalıdır. Bir yan etki gerçekleştirmeniz gerektiğinde, bu işlemi bir geri aramadan 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 düzenleme iyimser bir durumdur. Diğer bir deyişle,Compose parametreler tekrar değişmeden önce yeniden düzenleme işleminin tamamlanmasını bekler. Bir parametre, yeniden derleme tamamlanmadan önce değişirse Derle, yeniden derlemeyi iptal edip yeni parametreyle yeniden başlatabilir.

Yeniden oluşturma iptal edildiğinde Compose, kullanıcı arayüzü ağacını yeniden oluşturmadan siler. 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 takılmalara 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ını ciddi şekilde sekteye uğratabilir.

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, composable işlevinin arka plan ileti dizileri havuzunda yürütülebileceğ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 composable işlev çağrıldığında, çağrı, çağrı yapandan farklı bir iş parçacığında gerçekleşebilir. Bu, composable lambda'daki değişkenleri değiştiren koddan kaçınılması gerektiği anlamına gelir. Hem iş parçacığı açısından güvenli olmadığı hem de composable lambda'nın izin verilmeyen bir yan etkisi olduğu için bu koddan kaçınılmalıdır.

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 bir yerel 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 oluşturmada değiştirilmiştir. 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 composable işlevin koduna bakarsanız kodun göründüğü sırayla çalıştırıldığını varsayabilirsiniz. Ancak bu durumun doğruluğu garanti edilmez. Bir composable işlev, diğer composable 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 şuna benzer 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 ayarlamasına (yan etki) ve MiddleScreen()'ün bu değişiklikten yararlanmasına izin veremezsiniz. Bunun yerine, bu işlevlerin her birinin kendi kendine yeterli olması gerekir.

Daha fazla bilgi

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

Videolar