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ştirmenize gerek kalmadan uygulama kullanıcı arayüzünüzü oluşturmanıza olanak tanıyan bir bildirimsel API sağlayarak uygulama kullanıcı arayüzünüzü yazmayı ve bakımını yapmayı kolaylaştırır. Bu terminolojinin biraz açıklanması gerekiyor ancak sonuçları uygulama tasarımınız açısından önemlidir.

Bildirimsel programlama paradigması

Geçmişte, Android görünüm hiyerarşisi bir kullanıcı arayüzü widget'ları ağacı olarak gösterilebiliyordu. Uygulamanın durumu, kullanıcı etkileşimleri gibi nedenlerle değiştiğinde mevcut verileri göstermek için kullanıcı arayüzü hiyerarşisinin 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üntülemeleri manuel olarak değiştirmek hata olasılığını artırır. Bir veri parçası birden fazla yerde oluşturuluyorsa bunu gösteren görünümlerden birini güncellemeyi unutmak kolaydır. Ayrıca, iki 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ı karmaşıklığı, güncellenmesi gereken görünümlerin sayısıyla birlikte artar.

Son birkaç yıldır sektörün tamamı, kullanıcı arayüzü oluşturma ve güncelleme ile ilgili mühendisliği büyük ölçüde basitleştiren bildirim temelli bir kullanıcı arayüzü modeline geçmeye başladı. Bu teknik, tüm ekranı baştan kavramsal olarak yeniden oluşturup yalnızca gerekli değişiklikleri uygulayarak çalışır. Bu yaklaşım, durum bilgisi olan bir görünüm hiyerarşisinin manuel olarak güncellenmesinin karmaşıklığını önler. Compose, bildirime dayalı bir kullanıcı arayüzü çerçevesidir.

Ekranın tamamını yeniden oluşturmanın zorluklarından biri, zaman, bilgi işlem gücü ve pil kullanımı açısından maliyetli olabilmesidir. Compose, bu maliyeti azaltmak için kullanıcı arayüzünün hangi bölümlerinin herhangi bir zamanda yeniden çizilmesi gerektiğini akıllıca seçer. Bu durum, Yeniden Oluşturma bölümünde ele alındığı gibi, kullanıcı arayüzü bileşenlerinizi tasarlama şekliniz üzerinde bazı etkiler yaratır.

Basit bir composable işlev

Compose'u kullanarak, veri alan ve kullanıcı arayüzü öğeleri yayan bir dizi birleştirilebilir işlev tanımlayarak kullanıcı arayüzünüzü oluşturabilirsiniz. Basit bir örnek, String alan ve bir selamlama mesajı gösteren Text widget'ı yayan bir Greeting widget'ıdır.

"Hello World" metnini gösteren bir telefonun ekran görüntüsü ve bu kullanıcı arayüzünü oluşturan basit Composable işlevinin kodu

1.şekil Verilerin iletildiği ve ekranda bir metin widget'ı oluşturmak için kullanılan basit bir composable işlev.

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

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

  • İşlev, verileri alır. Composable işlevler, uygulamanın 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ı adıyla karşılayabilmek için String kabul eder.

  • İşlev, kullanıcı arayüzünde metin gösterir. Bu işlem, metin kullanıcı arayüzü öğesini oluşturan Text() composable işlevi çağrılarak yapılır. Composable işlevler, diğer composable işlevleri çağırarak kullanıcı arayüzü hiyerarşisi oluşturur.

  • İşlev herhangi bir değer döndürmüyor. Kullanıcı arayüzü yayan işlevlerin herhangi bir şey döndürmesi gerekmez. Bunun nedeni, kullanıcı arayüzü widget'ları oluşturmak yerine istenen ekran durumunu açıklamalarıdır.

  • Bu işlev hızlıdır, idempotent'tir ve yan etkileri 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.
    • Bu işlev, özellikleri veya genel değişkenleri değiştirme gibi yan etkiler olmadan kullanıcı arayüzünü tanımlar.

    Genel olarak, Yeniden oluşturma bölümünde belirtilen nedenlerden dolayı tüm composable işlevler bu özelliklerle yazılmalıdır.

Bildirimsel paradigma değişikliği

Birçok zorunlu nesne yönelimli kullanıcı arayüzü araç setinde, kullanıcı arayüzünü bir widget ağacı oluşturarak başlatırsınız. Bu işlem genellikle bir XML düzen dosyasını genişleterek yapılır. Her widget kendi dahili durumunu korur ve uygulama mantığının widget ile etkileşim kurmasına olanak tanıyan getter ve setter yöntemlerini kullanıma sunar.

Compose'un bildirim temelli yaklaşımında widget'lar nispeten durumsuz olup ayarlayıcı veya alıcı işlevlerini kullanıma sunmaz. 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, ViewModel gibi mimari kalıplara durum sağlamayı kolaylaştırır. Bu konu, Uygulama mimarisi kılavuzu'nda açıklanmıştır. Daha sonra, gözlemlenebilir veriler her güncellendiğinde mevcut uygulama durumunu kullanıcı arayüzüne dönüştürmekle composable'larınız sorumludur.

Compose kullanıcı arayüzündeki veri akışının, üst düzey nesnelerden alt öğelerine doğru olan akışını gösteren resim.

Şekil 2. Uygulama mantığı, en ü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, uygun verileri bu composable'lara ve hiyerarşide aşağıya doğru iletir.

Kullanıcı, kullanıcı arayüzüyle etkileşim kurduğunda kullanıcı arayüzü onClick gibi etkinlikler oluşturur. Bu etkinlikler, uygulama mantığını bilgilendirmelidir. Bu mantık daha sonra uygulamanın durumunu değiştirebilir. Durum değiştiğinde, composable işlevler yeni verilerle tekrar çağrılır. Bu durum, kullanıcı arayüzü öğelerinin yeniden çizilmesine neden olur. Bu sürece yeniden oluşturma denir.

Uygulama mantığı tarafından işlenen etkinlikleri tetikleyerek kullanıcı arayüzü öğelerinin etkileşime nasıl yanıt verdiğini gösteren resim.

3.Şekil Kullanıcı, bir kullanıcı arayüzü öğesiyle etkileşime girdiğinde bir etkinlik tetiklenir. Uygulama mantığı etkinliğe yanıt verir. Ardından, gerekirse composable işlevler yeni parametrelerle otomatik olarak tekrar çağrılır.

Dinamik içerik

Composable işlevler XML yerine Kotlin ile yazıldığından 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 selamlama oluşturur. Composable 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ğinden yararlanabilirsiniz. Bu güç ve esneklik, Jetpack Compose'un temel avantajlarından biridir.

Yeniden oluşturma

Zorunlu bir kullanıcı arayüzü modelinde, bir widget'ı değiştirmek için widget'ta bir ayarlayıcı çağırarak widget'ın iç durumunu değiştirirsiniz. Compose'da, composable işlevi 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ş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")
    }
}

Arayan, düğmeyi her tıkladığında clicks değerini günceller. Compose, yeni değeri göstermek için lambda'yı Text işleviyle tekrar çağırır. Bu işleme yeniden oluşturma denir. Değere bağlı olmayan diğer işlevler yeniden oluşturulmaz.

Daha önce de belirttiğimiz 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ü ve pil ömrünü etkiler. Compose, bu sorunu akıllı yeniden oluşturma özelliğiyle çözüyor.

Yeniden oluşturma, girişler değiştiğinde composable işlevlerinizi tekrar çağırma işlemidir. Bu durum, işlevin girişleri değiştiğinde gerçekleşir. Compose, yeni girişlere göre yeniden oluşturma yaptığında yalnızca değişmiş olabilecek işlevleri veya lambda'ları çağırır ve geri kalanları atlar. Compose, parametreleri değişmemiş tüm işlevleri veya lambda'ları atlayarak verimli bir şekilde yeniden oluşturabilir.

Bir işlevin yeniden oluşturulması atlanabileceğinden, birleştirilebilir işlevlerin yürütülmesinden kaynaklanan yan etkilerden asla yararlanmayın. Bu durumda kullanıcılar uygulamanızda garip ve tahmin edilemeyen davranışlarla karşılaşabilir. Yan etki, uygulamanızın geri kalanında 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 konumunda bir gözlemlenebilir öğeyi güncelleme
  • Paylaşılan tercihleri güncelleme

Bir animasyon oluşturulurken olduğu gibi, birleştirilebilir işlevler her karede yeniden yürütülebilir. Animasyonlar sırasında takılmayı önlemek için birleştirilebilir işlevler hızlı olmalıdır. Paylaşılan tercihlerden okuma gibi maliyetli işlemler yapmanız gerekiyorsa bunu arka plan coroutine'inde yapın ve değer sonucunu parametre olarak composable işlevine iletin.

Örneğin, bu kod, SharedPreferences içindeki bir değeri güncellemek için composable oluşturur. Composable, paylaşılan tercihlerden okuma veya yazma işlemi yapmamalıdır. Bunun yerine, bu kod okuma ve yazma işlemlerini arka planda çalışan bir ViewModel rutinine taşır. Uygulama mantığı, güncellemeyi tetiklemek için geri çağırma ile geçerli değeri iletir.

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

Bu belgede, Oluştur özelliğini kullanırken dikkat etmeniz gereken bazı noktalar ele alınmaktadır:

  • Yeniden oluşturma, mümkün olduğunca çok sayıda composable işlevi ve lambda'yı atlar.
  • Yeniden oluşturma işlemi iyimserdir ve iptal edilebilir.
  • Bir composable işlevi, animasyonun her karesi kadar sık çalıştırılabilir.
  • Composable 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 oluşturmayı desteklemek için birleştirilebilir işlevlerin nasıl oluşturulacağı ele alınacaktır. Her durumda, en iyi uygulama, birleştirilebilir işlevlerinizi hızlı, idempotent ve yan etkisiz tutmaktır.

Yeniden oluşturma işlemi mümkün olduğunca fazla atlama yapar.

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 oluşturmak için elinden geleni yapar. Bu nedenle, kullanıcı arayüzü ağacında üstündeki veya altındaki composable'ları çalıştırmadan tek bir Button composable'ını yeniden çalıştırmak için atlayabilir.

Her composable işlev ve lambda kendi başına yeniden oluşturulabilir. Aşağıda, bir liste oluşturulurken yeniden oluşturma işleminin 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 oluşturma sırasında yürütülecek tek şey olabilir. header değiştiğinde Compose, üst öğelerinden herhangi birini yürütmeden Column lambda'sına atlayabilir. Ayrıca Column yürütülürken names değişmediyse LazyColumn öğeleri atlanabilir.

Yine, tüm birleştirilebilir işlevlerin veya lambda'ların yürütülmesi yan etkisiz olmalıdır. Bir yan etki gerçekleştirmeniz gerektiğinde bunu bir geri çağırma işlevinden tetikleyin.

Yeniden oluşturma iyimserdir

Yeniden oluşturma, Compose'un bir composable'ın parametrelerinin değişmiş olabileceğini düşündüğü her zaman başlar. Yeniden oluşturma iyimserdir. Bu,Compose'un parametreler tekrar değişmeden önce yeniden oluşturmayı tamamlamayı beklediği anlamına gelir. Bir parametre, yeniden oluşturma işlemi tamamlanmadan önce değişirse Compose, yeniden oluşturma işlemini iptal edip yeni parametreyle yeniden başlatabilir.

Yeniden oluşturma işlemi iptal edildiğinde Compose, yeniden oluşturma işlemindeki kullanıcı arayüzü ağacını atar. 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, uygulamanın tutarsız bir durumda olmasına neden olabilir.

İyimser yeniden oluşturmayı işlemek için tüm composable işlevlerin ve lambda'ların idempotent ve yan etkisiz olduğundan emin olun.

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

Bazı durumlarda, composable işlevler bir kullanıcı arayüzü animasyonunun her karesi için çalışabilir. İşlev, cihaz depolama alanından okuma gibi maliyetli işlemler gerçekleştiriyorsa kullanıcı arayüzünde takılmaya neden olabilir.

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

Composable işlevinizin veriye ihtiyacı varsa veriler için parametreler tanımlaması gerekir. Ardından, pahalı işlemleri oluşturma dışında başka bir iş parçacığına taşıyabilir ve verileri mutableStateOf veya LiveData kullanarak Compose'a iletebilirsiniz.

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

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

Bu optimizasyon, bir composable işlevin arka plan iş parçacıkları havuzunda yürütülebileceği anlamına gelir. Bir composable işlevi, 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 tüm composable 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 etkileri tetikleyin.

Bir composable işlev çağrıldığında, çağıran işlevden farklı bir iş parçacığında çağrılabilir. Bu nedenle, 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 composable lambda'daki değişkenleri değiştiren kodlardan kaçınılmalıdır.

Bir listeyi ve sayısını gösteren bir composable'ı gösteren örneği 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 etkisi olmayan ve giriş listesini kullanıcı arayüzüne dönüştüren bir koddur. Bu, küçük bir listeyi görüntülemek için harika bir kod. Ancak işlev yerel bir değişkene yazıyorsa 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, her yeniden oluşturma işleminde items değiştirilir. Bu, animasyonun her karesi veya listenin güncellendiği zaman olabilir. Her iki durumda da kullanıcı arayüzünde yanlış sayı gösterilir. Bu nedenle, bu tür yazma işlemleri Oluşturma'da desteklenmez. Bu yazma işlemlerini yasaklayarak, çerçevenin işlenebilir lambda'ları yürütmek için iş parçacıklarını değiştirmesine izin veririz.

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

Bir composable işlevinin koduna baktığınızda, kodun göründüğü sırayla çalıştırıldığını düşünebilirsiniz. Ancak bu durumun geçerli olacağı garanti edilmez. Bir composable işlev, diğer composable işlevlere yapılan çağrıları içeriyorsa bu işlevler herhangi bir sırada çalışabilir. Oluşturma özelliği, bazı kullanıcı arayüzü öğelerinin diğerlerinden daha yüksek öncelikli olduğunu tanıma ve 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 numaralarına yapılan aramalar herhangi bir sırada gerçekleşebilir. Bu nedenle, örneğin StartScreen()'nın bazı genel değişkenleri (yan etki) ayarlaması ve MiddleScreen()'nin bu değişiklikten yararlanması mümkün değildir. Bunun yerine, bu işlevlerin her birinin bağımsız olması gerekir.

Daha fazla bilgi

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

Videolar