Yazılı Düşünme

Jetpack Compose, Android için modern ve bildirime dayalı bir kullanıcı arayüzü araç setidir. Compose, uygulama kullanıcı arayüzünüzü zorunlu olarak ön uç görünümlerini değiştirmeden oluşturmanıza olanak tanıyan bildirim temelli bir 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 sonuçları uygulama tasarımınız için önemlidir.

Bildirimli 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 edilebiliyordu. Kullanıcı etkileşimleri gibi nedenlerle uygulamanın durumu değiştikçe kullanıcı arayüzü hiyerarşisinin güncel 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 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üleme sayısını manuel olarak değiştirmek hata olasılığını artırır. Bir veri parçası birden fazla yerde oluşturuluyorsa, veriyi gösteren görünümlerden birini güncellemeyi unutabilirsiniz. Bu durum, iki güncelleme beklenmedik bir şekilde çakıştığında yasa dışı durumlara da yol açabilir. Ö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üm sayısıyla birlikte artar.

Son birkaç yıldır tüm sektör, kullanıcı arayüzleri oluşturmayı ve güncellemeyi basitleştiren açıklayıcı bir kullanıcı arayüzü modeline geçmeye başladı.

Bu teknik, kavramsal olarak tüm ekranı sıfırdan yeniden oluşturup yalnızca gerekli değişiklikleri uygulayarak çalışır. Bu yaklaşım, durum bilgisi olan bir görünüm hiyerarşisini manuel olarak güncellemenin karmaşıklığını ortadan kaldırır. Compose, açıklayıcı bir kullanıcı arayüzü çerçevesidir.

Tüm ekranı 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 yeniden çizilmesi gerektiğini akıllıca seçer. Yeniden oluşturma bölümünde açıklandığı gibi, bu durum kullanıcı arayüzü bileşenlerinizi tasarlama şeklinizi etkiler.

Örnek bir composable işlev

Compose'u kullanarak, verileri alan ve kullanıcı arayüzü öğeleri oluşturan bir dizi composable işlev tanımlayarak kullanıcı arayüzünüzü oluşturabilirsiniz. Örneğin, Greeting widget'ı bir String alır ve karşılama mesajı gösteren bir Text widget'ı oluşturur.

"Hello World" metnini ve bu kullanıcı arayüzünü oluşturan composable işlevin kodunu gösteren bir telefon.
Şekil 1. Verilerin iletildiği ve ekranda bir metin widget'ı oluşturmak için bu verileri kullanan bir composable işlev.

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

  • Ek açıklama: İşlev, @Composable ek açıklamasıyla açıklanır. Tüm composable işlevlerde 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 için tasarlandığını bildirir.
  • Veri girişi: İşlev, verileri alır. Composable 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ı adıyla karşılayabilmek için bir String kabul eder.
  • Kullanıcı arayüzü ekranı: İş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. Oluşturulabilir işlevler, diğer oluşturulabilir işlevleri çağırarak kullanıcı arayüzü hiyerarşisi oluşturur.
  • Döndürülen değer yok: İşlev herhangi bir değer döndürmez. Kullanıcı arayüzü oluşturan Compose işlevlerinin herhangi bir şey döndürmesi gerekmez. Çünkü bu işlevler, kullanıcı arayüzü widget'ları oluşturmak yerine hedef ekran durumunu tanımlar.
  • Özellikler: 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.
    • İşlev, özellikleri veya genel değişkenleri değiştirme gibi yan etkiler olmadan kullanıcı arayüzünü tanımlar.

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

Bildirim temelli paradigma değişikliği

Birçok zorunlu nesne tabanlı kullanıcı arayüzü araç setinde, bir widget ağacını örneklendirerek 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şime girmesine olanak tanıyan alıcı ve ayarlayıcı yöntemlerini gösterir.

Compose'un bildirimsel yaklaşımında widget'lar nispeten durum bilgisizdir ve belirleyici veya alıcı işlevleri 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, Uygulama mimarisi kılavuzunda açıklandığı gibi ViewModel gibi mimari kalıplara durum sağlamayı kolaylaştırır. Ardından, gözlemlenebilir veriler her güncellendiğinde mevcut uygulama durumunu bir kullanıcı arayüzüne dönüştürmekten composable'larınız sorumlu olur.

Compose kullanıcı arayüzünde üst düzey nesnelerden alt nesnelere doğru veri akışını gösteren görsel.
Şekil 2. Uygulama mantığı, üst düzey composable işleve 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şinin alt kısımlarına iletir.

Kullanıcı, kullanıcı arayüzüyle etkileşimde bulunduğunda kullanıcı arayüzü, onClick gibi etkinlikleri tetikler. 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 işleme yeniden oluşturma denir.

Kullanıcı arayüzü öğelerinin, uygulama mantığı tarafından işlenen 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 girerek 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

composable işlevler XML yerine Kotlin'de 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. Birleş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ğinden yararlanabilirsiniz. Bu güç ve esneklik, Jetpack Compose'un en önemli avantajlarından biridir.

Yeniden oluşturma

Zorunlu kullanıcı arayüzü modelinde bir widget'ı değiştirmek için widget'ta bir ayarlayıcı çağırarak dahili 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 oluşturulan 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, düğme gösteren ş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 lambda'yı Text işleviyle tekrar çağırır. Bu işleme yeniden oluşturma adı verilir. Değere bağlı olmayan diğer işlevler yeniden oluşturulmaz.

Daha önce de belirttiğimiz gibi, tüm kullanıcı arayüzü ağacını yeniden oluşturmak, işlem gücü ve pil ömrü açısından maliyetli olabilir. Compose, bu sorunu akıllı yeniden oluşturma ile çözer.

Yeniden oluşturma, girişler değiştiğinde composable işlevlerinizi tekrar çağırma işlemidir. Compose, yeni girişlere göre yeniden oluşturma işlemi yaptığında yalnızca değişmiş olabilecek işlevleri veya lambda'ları çağırır ve geri kalanları atlar. Compose, parametreleri değişmeyen işlevleri veya lambda'ları atlayarak verimli bir şekilde yeniden oluşturur.

Bir işlevin yeniden oluşturulması atlanabileceğinden, composable işlevlerin yürütülmesinden kaynaklanan yan etkilere asla güvenmeyin. Aksi takdirde kullanıcılar uygulamanızda garip ve öngörülemeyen davranışlarla karşılaşabilir. Yan etki, uygulamanızın geri kalanında 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 içinde bir gözlemlenebilir öğeyi güncelleme
  • Paylaşılan tercihler güncelleniyor

Composable işlevler, animasyon oluşturulurken olduğu gibi her karede yeniden yürütülebilir. Animasyonlar sırasında takılma olmaması için composable işlevler hızlı olmalıdır. Paylaşılan tercihlerden okuma gibi maliyetli işlemler yapmanız gerekiyorsa bunu arka plandaki bir eş yordamda yapın ve değer sonucunu parametre olarak composable işleve iletin.

Örneğin, bu kod SharedPreferences içindeki bir değeri güncellemek için bir composable oluşturur. Oluşturulabilir öğe, paylaşılan tercihlerden okuma veya yazma yapmamalıdır. Bunun yerine bu kod, okuma ve yazma işlemlerini arka plandaki bir eş yordamda ViewModel'e taşır. Uygulama mantığı, bir güncellemeyi tetiklemek için geçerli 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 belgede, Compose'u kullanırken dikkat etmeniz gereken bazı noktalar ele alınmaktadır:

  • Yeniden oluşturma, mümkün olduğunca çok composable işlevi ve lambda'yı atlar.
  • Yeniden oluşturma iyimserdir ve iptal edilebilir.
  • Bir composable işlev, animasyonun her karesinde olduğu gibi oldukça sık çalıştırılabilir.
  • Composable işlevler paralel olarak yürütülebilir.
  • Oluşturulabilir işlevler herhangi bir sırayla yürütülebilir.

Aşağıdaki bölümlerde, yeniden oluşturmayı destekleyen composable işlevlerin nasıl oluşturulacağı açıklanmaktadır. Her durumda en iyi uygulama, composable işlevlerinizi hızlı, idempotent ve yan etkisiz tutmaktır.

Yeniden oluşturma, mümkün olduğunca çok atlanır

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, kullanıcı arayüzü ağacında daha yüksek veya daha düşük birleştirilebilir öğelerden hiçbirini yürütmeden tek bir Button birleştirilebilir öğesini yeniden çalıştırmayı atlayabileceği anlamına gelir.

Her composable işlev ve lambda kendi kendine yeniden oluşturulabilir. Aşağıdaki örnekte, yeniden oluşturma işleminin bir listeyi oluştururken bazı öğeleri nasıl atlayabileceği gösterilmektedir:

/**
 * 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ülen tek şey olabilir. Oluşturma, header değiştiğinde üst öğelerinden hiçbirini yürütmeden Column lambda'ya atlayabilir. Column yürütülürken names değişmediyse Compose, LazyColumn öğelerini atlamayı seçebilir.

Tüm composable işlevler veya lambda'lar yan etkisiz olmalıdır. Yan etki gerçekleştirmeniz gerektiğinde bunu bir geri çağırmadan tetikleyin.

Yeniden oluşturma iyimserdir

Yeniden oluşturma, Compose bir composable'ın parametrelerinin değişmiş olabileceğini düşündüğünde başlar. Yeniden oluşturma iyimserdir. Yani Compose,parametreler tekrar değişmeden önce yeniden oluşturma işleminin tamamlanmasını bekler. Yeniden oluşturma tamamlanmadan önce bir parametre değişirse Compose, yeniden oluşturmayı iptal edip yeni parametreyle yeniden başlatabilir.

Yeniden oluşturma iptal edildiğinde Compose, yeniden oluşturma işlemindeki kullanıcı arayüzü ağacını siler. Kullanıcı arayüzünün gösterilmesine bağlı yan etkileriniz varsa oluşturma iptal edilse bile yan etki uygulanır. Bu durum, uygulama durumunun tutarsız olmasına neden olabilir.

İyimser yeniden oluşturma işlemini gerçekleştirmek için tüm composable işlevlerin ve lambda'ların eş kuvvetli ve yan etkisiz olduğunu doğrulayın.

Birleştirilebilir işlevler oldukça sık çalıştırılabilir

Bazı durumlarda, composable işlev bir kullanıcı arayüzü animasyonunun her karesi için çalıştırılabilir. İşlev, cihaz depolama alanından okuma gibi pahalı 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 da uygulamanızın performansını olumsuz etkileyebilir.

Bir composable işlevin veriye ihtiyacı varsa bu veri için parametreler tanımlayın. Ardından, yüksek maliyetli işlemleri oluşturma dışında başka bir iş parçacığına taşıyabilir ve elde edilen değeri, mutableStateOf veya LiveData kullanarak parametre olarak composable işleve iletebilirsiniz.

Composable işlevler paralel olarak çalışabilir

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şlevleri daha düşük öncelikli olarak çalıştırabilir.

Bu optimizasyon, composable işlevin arka plan iş parçacığı havuzunda yürütülebileceği anlamına gelir. Bir composable 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 davrandığını doğrulamak 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ğrı, çağırandan farklı bir iş parçacığında gerçekleşebilir. Bu nedenle, bir composable lambda'daki değişkenleri değiştiren kodlardan kaçınılmalıdır. Bu tür kodlar hem iş parçacığı açısından güvenli değildir hem de composable lambda'nın izin verilmeyen bir yan etkisidir.

Aşağıda, bir listeyi ve sayısını gösteren bir composable örneği verilmiştir:

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

Bu kodun yan etkisi yoktur 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. 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, items her yeniden oluşturma işleminde değiştirilir. Bu, bir animasyonun her karesi veya listenin güncellendiği an olabilir. Her iki durumda da kullanıcı arayüzünde yanlış sayı gösterilir. Bu nedenle, bu tür yazma işlemleri Compose'da desteklenmez. Bu yazma işlemlerini yasaklayarak, çerçevenin composable lambda'ları yürütmek için iş parçacıklarını değiştirmesine izin veririz.

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

Bir composable işlevin koduna bakarsanız kodun göründüğü sırayla çalıştırıldığını düşünebilirsiniz. Ancak bu her zaman doğru değildir. Bir composable işlev, diğer composable işlevlere çağrılar içeriyorsa bu işlevler herhangi bir sırada çalıştırılabilir. Compose, 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 çağrıları herhangi bir sırayla gerçekleşebilir. Yani, örneğin StartScreen()'in bazı genel değişkenleri (yan etki) ayarlamasını ve MiddleScreen()'in bu değişiklikten yararlanmasını sağlayamazsınız. 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