Yazılı Düşünme

Jetpack Compose, Android için modern bildirim temelli bir Kullanıcı Arayüzü Araç Setidir. Compose, ön uç görünümlerini zorunlu olarak değiştirmeden uygulamanızın kullanıcı arayüzünü oluşturmanızı sağlayan bildirim temelli bir API sağlayarak uygulamanızın kullanıcı arayüzünü yazmanızı ve sürdürmenizi kolaylaştırır. Bu terminolojiye ilişkin bazı açıklamalar yapılmalıdır ancak çıkarımlar, uygulama tasarımınız açısından önemlidir.

Bildirim temelli programlama paradigması

Önceden Android görünüm hiyerarşisi, kullanıcı arayüzü widget'larından oluşan bir ağaç olarak temsil edilebiliyordu. Kullanıcı etkileşimleri gibi unsurlar nedeniyle uygulamanın durumu değiştiğinden, kullanıcı arayüzü hiyerarşisinin mevcut verileri gösterecek şekilde güncellenmesi gerekir. Kullanıcı arayüzünü güncellemenin en yaygın yolu, findViewById() gibi işlevleri kullanarak ağacı yürütmek 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ünümleri manuel olarak değiştirmek, hata olasılığını artırır. Bir veri parçası birden fazla yerde oluşturulursa bunu gösteren görünümlerden birini güncellemeyi kolayca 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, güncelleme gerektiren görünümlerin sayısı arttıkça yazılım bakımının karmaşıklığı da artar.

Son birkaç yılda sektörün tamamında bildirim temelli bir kullanıcı arayüzü modeline geçiş yapmaya başladı. Bu model, kullanıcı arayüzlerinin oluşturulması ve güncellenmesiyle ilgili mühendisliği büyük ölçüde basitleştiriyor. Bu teknik, ekranın tamamını kavramsal olarak sıfırdan yeniden üretip 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. Oluşturma, bildirim temelli 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

Veri alan ve kullanıcı arayüzü öğeleri yayınlayan bir composable işlev grubu tanımlayarak kullanıcı arayüzünüzü oluşturmak için Compose'u kullanabilirsiniz. String içeren ve karşılama mesajı görüntüleyen bir Text widget'ı yayan Greeting widget'ı basit bir örnektir.

Metni gösteren bir telefonun ekran görüntüsü

Şekil 1. Verileri ileten ve ekranda bir metin widget'ı oluşturmak için kullanan basit bir composable işlevi.

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

  • İşlev, @Composable ek açıklamasıyla belirtilir. Tüm Oluşturulabilir işlevler bu ek açıklamaya sahip olmalıdır. Bu ek açıklama, Compose derleyiciye bu işlevin verileri kullanıcı arayüzüne dönüştürmeyi hedeflediğini 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ı adıyla karşılaması için String ifadesini 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ü oluşturan oluşturma işlevlerinin, kullanıcı arayüzü widget'ları oluşturmak yerine istenen ekran durumunu tanımladığı için hiçbir şey 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, özellikleri veya genel değişkenleri değiştirmek gibi herhangi bir yan etkisi olmadan kullanıcı arayüzünü 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.

Bildirim temelli paradigma değişimi

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 belirleyici yöntemleri sunar.

Compose'un bildirim temelli yaklaşımında widget'lar nispeten durum bilgisizdir ve belirleyici ya da alıcı işlevlerini göstermez. Aslında widget'lar nesne olarak gösterilmez. Aynı composable işlevi farklı bağımsız değişkenlerle çağırarak kullanıcı arayüzünü güncellersiniz. Bu, Uygulama mimarisi kılavuzu'nda açıklandığı gibi ViewModel gibi mimari kalıplara durum sağlamayı 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.

Üst düzey nesnelerden alt öğelere kadar Compose kullanıcı arayüzünde veri akışını gösteren görsel.

Şekil 2. Uygulama mantığı, üst düzey composable işleve veri sağlar. Bu işlev, verileri kullanarak diğer composable'ları çağırarak kullanıcı arayüzünü açıklar ve uygun verileri bu composable'lara iletir.

Kullanıcı, kullanıcı arayüzüyle etkileşimde bulunduğunda kullanıcı arayüzü, onClick gibi etkinlikleri öne çıkarır. Bu etkinlikler, uygulama mantığına bildirim göndermelidir. Uygulama mantığı, 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 adı verilir.

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ı bir kullanıcı arayüzü öğesiyle etkileşimde bulunarak bir etkinliğin tetiklenmesine neden olmuştur. Uygulama mantığı etkinliğe yanıt verir, ardından composable işlevler gerekirse yeni parametrelerle otomatik olarak tekrar çağrılır.

Dinamik içerik

Oluşturulabilir işlevler XML yerine Kotlin ile yazıldığından, diğer tüm 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. Özelleştirilebilir işlevler oldukça karmaşık olabilir. Belirli bir kullanıcı arayüzü öğesini gösterip göstermeyeceğinize karar vermek için if ifadelerini kullanabilirsiniz. Döngüleri kullanabilirsiniz. Yardımcı işlevleri çağrıyabilirsiniz. Kullanılan dilin tüm esnekliğine sahipsiniz. Bu güç ve esneklik, JetpackCompose'un temel avantajlarından biridir.

Yeniden düzenleme

Zorunlu kullanıcı arayüzü modelinde, bir widget'ı değiştirmek için widget'ta dahili durumunu değiştirmesi için bir belirleyici çağırırsınız. Compose'da composable işlevini yeni verilerle tekrar çağırırsınız. Bunu yapmak, işlevin yeniden oluşturulmasına 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. Compose, yeni değeri göstermek için Text işleviyle lambda'yı tekrar çağırır. Bu sürece yeniden oluşturma adı verilir. Değere bağlı olmayan diğer işlevler yeniden oluşturulmaz.

Konuştuğumuz gibi, kullanıcı arayüzü ağacının tamamını yeniden oluşturmak hesaplama açısından maliyetli olabilir. Bu da işlem gücünü ve pil ömrünü kullanır. Compose, bu akıllı yeniden düzenleme ile bu sorunu çözüyor.

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. Compose yeni girişlere göre yeniden oluşturulduğunda yalnızca değişmiş olabilecek işlevleri veya lambda'ları çağırır ve gerisini atlar. Değiştirilmiş parametreleri olmayan tüm işlevleri veya lambda'ları atlayarak Compose verimli bir şekilde yeniden oluşturabilir.

İşlevin yeniden oluşumu 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 tuhaf ve tahmin edilemeyen davranışlarla karşılaşabilir. Yan etki, uygulamanızın geri kalanı tarafından görülebilen herhangi bir değişikliktir. Örneğin, aşağıdaki işlemlerin tümü tehlikeli yan etkilerdir:

  • Paylaşılan bir nesnenin özelliğine yazma
  • ViewModel içindeki bir gözlemlenebilir öğeyi güncelleme
  • Paylaşılan tercihler güncelleniyor

Oluşturulabilir işlevler, bir animasyonun oluşturulması gibi her karede olduğu kadar sık şekilde yeniden yürütülebilir. Oluşturulabilir işlevler, animasyonlar sırasında tıkanıklıklardan kaçınmak için hızlı olmalıdır. Paylaşılan tercihlerden okuma gibi pahalı işlemler yapmanız gerekiyorsa bunu arka plan eş yordamında yapın ve değer sonucunu composable işlevine parametre olarak iletin.

Örneğin, bu kod SharedPreferences içindeki bir değeri güncellemek için bir composable oluşturur. composable, paylaşılan tercihlerin kendisinden okumamalı veya yazmamalıdır. Bunun yerine bu kod, okuma ve yazma işlemini arka plan koordinatında bir ViewModel öğesine taşır. Uygulama mantığı, güncelleme 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 göz önünde bulundurmanız gereken bazı noktalar anlatılmaktadır:

  • Oluşturulabilir işlevler herhangi bir sırada yürütülebilir.
  • Özelleştirilebilir işlevler paralel olarak yürütülebilir.
  • Yeniden düzenleme, mümkün olduğunca çok composable işlevi ve lambdayı atlar.
  • Yeniden düzenleme iyimser olduğu için iptal edilebilir.
  • Bir composable işlev, oldukça sık, bir animasyonun her karesi kadar sık çalıştırılabilir.

Aşağıdaki bölümlerde, yeniden bestelemeyi desteklemek için composable işlevlerin nasıl oluşturulacağı açıklanmaktadır. Her durumda en iyi uygulama, composable işlevlerinizi hızlı, özdeş ve yan etkiden uzak tutmaktır.

Oluşturulabilir 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, her zaman doğru olmayabilir. 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, bazı kullanıcı arayüzü öğelerinin diğerlerinden daha yüksek önceliğe sahip olduğunu anlama ve önce bunları çizme seçeneği sunar.

Ö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 çağrıları herhangi bir sırayla gerçekleşebilir. Örneğin, StartScreen() ürününün bir genel değişken (yan etki) ayarlamasını ve MiddleScreen() ürününün bu değişiklikten yararlanmasını sağlayamayacağınız anlamına gelir. Bunun yerine, bu işlevlerin her birinin bağımsız olması gerekir.

Özelleştirilebilir işlevler paralel olarak çalışabilir

Compose, composable işlevleri paralel olarak çalıştırarak yeniden besteyi optimize edebilir. Bu sayede Compose birden fazla çekirdekten yararlanabilir ve composable işlevlerini ekranda daha düşük öncelikte çalıştırabilir.

Bu optimizasyon, composable işlevinin arka plan ileti dizileri havuzu içinde yürütülebileceği anlamına gelir. Bir composable işlev ViewModel üzerinde bir işlevi çağırırsa,Compose bu işlevi aynı anda birkaç iş parçacığından çağırabilir.

Uygulamanızın doğru şekilde çalışmasını sağlamak için composable işlevlerin hiçbir yan etkisi olmamalıdır. Bunun yerine, her zaman UI iş parçacığında yürütülen onClick gibi geri çağırmalardan gelen yan etkileri 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 kodlardan kaçınılmalıdır.

Listeyi ve sayısını görüntüleyen bir composable'ın örneğini 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 etki içermez ve giriş listesini kullanıcı arayüzüne dönüştürür. Bu, küçük bir liste görüntülemek için harika bir koddur. Bununla birlikte, işlev yerel bir değişkene yazarsa bu kod iş parçacığı için güvenli veya doğru olmaz:

@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, items her yeniden oluşturmada değiştirilmiştir. Bu, bir animasyonun her karesi veya liste güncellendiyse olabilir. Her iki durumda da kullanıcı arayüzü yanlış sayıyı görüntüler. Bu nedenle, bu tür yazmalar Compose'da desteklenmez. Bu yazma işlemlerini yasaklayarak çerçevenin composable lambda'lar yürütmesi için iş parçacıklarını değiştirmesine izin veririz.

Yeniden düzenleme mümkün olduğunca fazla atlıyor

Kullanıcı arayüzünüzün bölümleri geçersiz olduğunda Compose, yalnızca güncellenmesi gereken bölümleri yeniden derlemek için elinden geleni yapar. Bu, kullanıcı arayüzü ağacında üzerindeki veya altındaki composable'ların hiçbirini yürütmeden tek bir Button'ın composable'ını tekrar çalıştırmaya atlayabileceği anlamına gelir.

Her composable işlev ve lambda kendi başına yeniden oluşturulabilir. Aşağıda, yeniden bestenin 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)
        Divider()

        // 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 oluşturma 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ülürken names değişmediyse Compose LazyColumn öğelerini atlamayı tercih edebilir.

Tekrar etmek gerekirse, composable'ın tüm işlevlerinin 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 düzenleme iyimser

Compose bir composable'ın parametrelerinin değişebileceğini düşündüğünde yeniden düzenleme başlar. Yeniden düzenleme iyimser bir durumdur. Bu,Compose'un parametreler tekrar değişmeden önce yeniden oluşturma işlemini tamamlamayı beklediği anlamına gelir. Yeniden oluşturma işlemi tamamlanmadan önce bir parametre değişirse Compose yeniden besteyi 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. Gösterilen kullanıcı arayüzüne bağlı olarak herhangi bir yan etkiniz varsa beste iptal edilse bile yan etki uygulanır. Bu durum uygulama durumunun tutarsız olmasına yol açabilir.

Tüm composable işlevlerin ve lambda'ların iyimser yeniden düzenleme yapmak için tarafsız ve yan etki içermeyen olduğundan emin olun.

Özelleştirilebilir işlevler oldukça sık çalışabilir

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

Örneğin, widget'ınız cihaz ayarlarını okumaya çalıştıysa bu ayarları saniyede yüzlerce kez okuyabilir ve bu da uygulamanızın performansını olumsuz etkileyebilir.

composable fonksiyonunuz veri gerektiriyorsa verilere ilişkin parametreleri tanımlamalıdır. Ardından pahalı işleri, bestenin dışına başka bir iş parçacığına taşıyabilir ve mutableStateOf veya LiveData kullanarak verileri Compose'a aktarabilirsiniz.

Daha fazla bilgi

Oluşturma ve composable işlevlerde nasıl düşünüleceği hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.

Videolar