Yazılı Düşünme

Jetpack Compose, Android için modern bir bildirim temelli Kullanıcı Arayüzü Araç Seti'dir. Compose, ön uç görünümlerini mutlaka değiştirmeden uygulama kullanıcı arayüzünüzü oluşturmanıza olanak tanıyan bir bildirim API'si sağlayarak uygulamanızın kullanıcı arayüzünü yazmayı ve bakımını kolaylaştırır. Bu terminoloji hakkında bazı açıklamalara ihtiyaç vardır, ancak sonuçları uygulamanızın tasarımı açısından önemlidir.

Bildirim temelli programlama paradigması

Geçmişte, Android görünüm hiyerarşisi kullanıcı arayüzü widget'larından oluşan bir ağaç olarak temsil ediliyordu. Uygulamanın durumu, kullanıcı etkileşimleri gibi şeyler nedeniyle 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ğaçta ilerlemek 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 bunları 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ıldır sektör genelinde, kullanıcı arayüzlerinin oluşturulması ve güncellenmesiyle ilişkili mühendisliği büyük ölçüde basitleştiren bildirim temelli kullanıcı arayüzü modeline geçiş yapılmaya başlandı. Teknik, tüm ekranı kavramsal olarak sıfırdan yeniden oluşturup 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ın yeniden üretilmesiyle ilgili 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 için 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 Oluşturma bölümünde açıklandığı gibi kullanıcı arayüzü bileşenlerinizi tasarlama şekliniz üzerinde bazı etkileri olabilir.

Basit bir composable işlev

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

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

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

Bu işlevle ilgili dikkat edilmesi gereken birkaç nokta:

  • İşleve @Composable ek açıklaması eklenir. Tüm Composable işlevlerinde bu ek açıklama bulunmalıdır. Bu ek açıklama, Compose derleyicisine bu işlevin verileri kullanıcı arayüzüne dönüştürmek olduğunu bildirir.

  • İşlev, verileri alır. Özelleştirilebilir işlevler, uygulama mantığının kullanıcı arayüzünü tanımlamasına olanak tanıyan parametreleri kabul edebilir. Bu örnekte widget'ımız, kullanıcıyı adıyla karşılayabilmek için String değerini kabul eder.

  • İşlev, metni kullanıcı arayüzünde görüntüler. Bunu, aslında metin kullanıcı arayüzü öğesini oluşturan Text()composable işlevini çağırarak yapar. Özelleştirilebilir 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ü yayınlayan işlevleri, kullanıcı arayüzü widget'ları oluşturmak yerine istenen ekran durumunu açıkladıkları için hiçbir şey döndürmesine gerek kalmadan oluşturun.

  • Bu işlev hızlıdır, eşdeğerdir ve yan etkisi yoktur.

    • İş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ü, değiştirilen özellikler veya genel değişkenler gibi herhangi bir yan etki olmadan tanımlar.

    Genel olarak, tüm composable işlevleri Recomposition bölümünde açıklanan nedenlerle bu özelliklerle yazılmalıdır.

Bildirim temelli paradigma değişimi

Birçok zorunlu nesne tabanlı kullanıcı arayüzü araç setinde, bir widget ağacını hazırlayarak kullanıcı arayüzünü ilk kullanıma hazırlarsı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 göreceli olarak durum bilgisizdir ve ayarlayıcı veya alıcı işlevlerini açığa çıkarmaz. Aslında, widget'lar nesne olarak ortaya çıkmaz. Kullanıcı arayüzünü, aynı composable işlevi farklı bağımsız değişkenlerle çağırarak güncellersiniz. Uygulama mimarisi rehberinde açıklandığı gibi, ViewModel gibi mimari kalıpları kolayca belirtebilirsiniz. Ardından composable'larınız, her gözlemlenebilir veri güncellemesinde mevcut uygulama durumunu bir kullanıcı arayüzüne dönüştürmekten sorumludur.

Compose kullanıcı arayüzünde üst düzey nesnelerden alt öğelere kadar veri akışının çizimi.

2. Şekil. Uygulama mantığı, üst düzey composable işlevine veri sağlar. Bu işlev, diğer composable'ları çağırarak kullanıcı arayüzünü tanımlamak için verileri kullanır ve uygun verileri bu composable'lara ve hiyerarşide aşağı doğru 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önderir. 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 yanıt verdiğini gösteren resim.

3. Şekil. Kullanıcı, bir kullanıcı arayüzü öğesiyle etkileşimde bulunarak bir etkinliğin tetiklenmesine neden oldu. 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 dilinde yazıldığı için diğer herhangi bir Kotlin kodu kadar dinamik olabilirler. Örneğin, 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. Özelleştirilebilir 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 Composer'ın en önemli avantajlarından biridir.

Yeniden oluşturma

Zorunlu kullanıcı arayüzü modelinde, bir widget'ı değiştirmek için, widget'ın dahili durumunu değiştirmesi için widget'ta bir setter çağırırsınız. Compose'da composable işlevini yeni verilerle tekrar çağırırsınız. Bu işlem, işlevin yeniden oluşturulmasına neden olur. İşlev tarafından yayınlanan widget'lar, gerekirse yeni verilerle yeniden çizilir. Compose çerçevesi yalnızca değiştirilen bileşenleri akıllıca yeniden derleyebilir.

Örneğin, 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 kişi clicks değerini günceller. Compose, yeni değeri göstermek için lambda'yı Text işleviyle yeniden çağırır. Bu sürece yeniden oluşturma adı verilir. Değere bağlı olmayan diğer işlevler yeniden derlenmez.

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 bilgi işlem gücü ve pil ömrü kullanır. Compose bu akıllı yeniden düzenleme ile bu sorunu çözer.

Yeniden derleme, girişler değiştiğinde composable işlevlerinizi tekrar çağırma işlemidir. Bu işlev, işlev girişleri değiştiğinde gerçekleşir. Compose yeni girişlere göre yeniden derlendiğinde yalnızca değişmiş olabilecek işlevleri veya lambdaları çağırır ve geri kalanları atlar. Compose, parametreleri değiştirilmemiş tüm işlevleri veya lambdaları atlayarak verimli bir şekilde yeniden oluşturabilir.

İşlevin yeniden oluşturulması atlanabilir. Bunu yaparsanız kullanıcılar uygulamanızda tuhaf ve tahmin edilemez davranışlarla karşılaşabilir. Yan etki, uygulamanızın geri kalanı tarafından görülebilen her türlü değişikliktir. Örneğin, aşağıdaki işlemlerin tümü tehlikeli yan etkilerdir:

  • Paylaşılan bir nesnenin özelliğine yazma
  • ViewModel ürününde bir gözlemlenebilir öğe güncelleniyor
  • Paylaşılan tercihler güncelleniyor

Oluşturulabilir işlevler her kare kadar sıklıkta (ör. bir animasyon oluşturulurken) yeniden yürütülebilir. Oluşturulabilir işlevler, animasyonlar sırasında olumsuzluklardan kaçınmak için hızlı olmalıdır. Paylaşılan tercihlerden okuma gibi pahalı işlemler yapmanız gerekirse bunu bir arka plan eş yordasında yapın ve değer sonucunu composable işleve parametre olarak aktarın.

Örneğin bu kod, SharedPreferences içindeki 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 arka plan eş yordamındaki bir ViewModel öğesine taşır. Uygulama mantığı, bir güncellemeyi tetiklemek için geçerli değeri bir geri çağırma ile aktarır.

@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ı konular ele alınmaktadır:

  • Oluşturulabilir işlevler herhangi bir sırada yürütülebilir.
  • Oluşturulabilir işlevler paralel olarak yürütülebilir.
  • Yeniden oluşturma işlemi mümkün olduğunca çok composable işlev ve lambda atlar.
  • Yeniden oluşturma işlemi iyimser ve iptal edilebilir.
  • composable bir işlev, bir animasyonun her karesi kadar sık çalıştırılabilir.

Aşağıdaki bölümlerde, yeniden oluşturmayı desteklemek için composable işlevlerin nasıl oluşturulacağı ele alınmaktadır. Her durumda en iyi uygulama, composable işlevlerinizi hızlı, benzersiz ve yan efektlerden uzak tutmaktır.

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

composable işlevi için koda bakarsanız kodun göründüğü sırayla çalıştığını varsayabilirsiniz. Ancak bu her zaman doğru olmayabilir. composable işlev, diğer composable işlevlere çağrı içeriyorsa bu işlevler herhangi bir sırada çalışabilir. Oluşturma, bazı kullanıcı arayüzü öğelerinin diğerlerinden daha yüksek öncelikli olduğunu fark edip bunları önce ç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. Diğer bir deyişle, örneğin, StartScreen()'nin bir global değişken (bir yan etki) ayarlamasına ve MiddleScreen() adlı kullanıcının bu değişiklikten yararlanmasına izin veremezsiniz. Bunun yerine, bu işlevlerin her birinin bağımsız olması gerekir.

Oluşturulabilir işlevler paralel olarak çalışabilir

Oluşturma, composable işlevlerini paralel olarak çalıştırarak yeniden oluşturma işlemini optimize edebilir. Böylece, Compose birden fazla çekirdekten yararlanabilir ve ekranda olmayan composable işlevleri daha düşük öncelikli olarak çalıştırabilir.

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

Uygulamanızın doğru şekilde çalıştığından emin olmak için tüm composable işlevlerinde yan etki olmamalıdır. Bunun yerine, her zaman kullanıcı arayüzü iş parçacığında yürütülen onClick gibi geri çağırmaların yan etkilerini tetikleyin.

composable bir işlev çağrıldığında, çağrı arayandan farklı bir iş parçacığında gerçekleşebilir. Bu, composable bir lambda içindeki değişkenleri değiştiren koddan kaçınılması gerektiği anlamına gelir. Hem bu kod iş parçacığı güvenli olmadığı hem de composable lambda'nın izin verilmeyen yan etkisidir.

Liste ve sayısını gösteren bir composable örneğini burada 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 etkiler içermez ve giriş listesini kullanıcı arayüzüne dönüştürür. Bu, küçük bir listeyi görüntülemek için çok iyi bir koddur. Ancak, işlev bir yerel değişkene yazarsa bu kod iş parçacığı 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üncellendiğinde olabilir. Her iki durumda da kullanıcı arayüzü yanlış sayıyı görüntüler. Bu nedenle, bu tür yazma işlemleri Composer'da desteklenmez. Bu yazma işlemleri yasaklanarak, çerçevenin composable lambda'ları yürütmek için iş parçacıklarını değiştirmesine izin verilir.

Yeniden oluşturma işlemini mümkün olduğunca fazla atlama

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

Her composable işlev ve lambda kendiliğinden yeniden oluşturulabilir. Aşağıda, yeniden oluşturma işleminin 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 düzenleme 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'ya atlayabilir. Ayrıca, names değişmediyse Compose Column öğesini yürütürken LazyColumn öğelerini atlamayı seçebilir.

Aynı şekilde, tüm composable fonksiyonları veya lambda'ları yürütürken yan etki olmamalıdır. Bir yan etki gerçekleştirmeniz gerektiğinde, geri çağırmayı tetikleyin.

Yeniden düzenleme iyimser

Compose bir composable'ın parametrelerinin değişebileceğini düşündüğünde yeniden oluşturma işlemi başlar. Yeniden oluşturma iyimserdir. Diğer bir deyişle Compose parametreler tekrar değişmeden önce yeniden oluşturma işlemini bitirmeyi bekler. Bir parametre, yeniden oluşturma işlemi tamamlanmadan önce değişirse Oluşturma işlemi, yeniden oluşturmayı iptal edip yeni parametreyle yeniden başlatabilir.

Yeniden oluşturma işlemi iptal edildiğinde Compose kullanıcı arayüzü ağacını yeniden oluşturma işleminden siler. Görüntülenen kullanıcı arayüzüne bağlı herhangi bir yan etkiniz varsa beste iptal edilse bile yan etki uygulanır. Bu durum tutarsız uygulama durumuna neden olabilir.

İyimser yeniden bileşimden yararlanabilmek için tüm composable işlevlerin ve lambda'ların eşgüdümlü ve yan etkisi olmadığından emin olun.

Oluşturulabilir işlevler oldukça sık çalışabilir

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

Örneğin, widget'ınız cihaz ayarlarını okumaya çalışırsa bu ayarları saniyede yüzlerce kez okuyabilir. Bu da uygulamanızın performansını ciddi şekilde etkileyebilir.

composable fonksiyonunuz veri gerektiriyorsa veri parametrelerini tanımlamalıdır. Daha sonra, pahalı işleri kompozisyon dışındaki başka bir iş parçacığına taşıyabilir ve mutableStateOf veya LiveData kullanarak verileri Compose'a aktarabilirsiniz.

Daha fazla bilgi

Compose'da ve composable işlevlerinde düşünme hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.

Videolar