Bestelerin yaşam döngüsü

Bu sayfada, composable'ların yaşam döngüsü ve Compose'un bir composable'ın yeniden oluşturulması gerekip gerekmediğine nasıl karar verdiği hakkında bilgi edineceksiniz.

Yaşam döngüsüne genel bakış

Durumu yönetme dokümanlarında belirtildiği gibi, bir kompozisyon, uygulamanızın kullanıcı arayüzünü tanımlar ve composable'lar çalıştırılarak oluşturulur. Composition, kullanıcı arayüzünüzü açıklayan composable'ların ağaç yapısıdır.

Jetpack Compose, ilk kompozisyon sırasında composable'larınızı ilk kez çalıştırdığında, bir kompozisyonda kullanıcı arayüzünüzü tanımlamak için çağırdığınız composable'ları takip eder. Ardından, uygulamanızın durumu değiştiğinde Jetpack Compose bir yeniden oluşturma planlar. Yeniden oluşturma, Jetpack Compose'un durum değişikliklerine yanıt olarak değişmiş olabilecek composable'ları yeniden yürütmesi ve ardından değişiklikleri yansıtmak için Composition'ı güncellemesidir.

Bir oluşturma yalnızca ilk oluşturmayla oluşturulabilir ve yeniden oluşturmayla güncellenebilir. Bir oluşturmayı değiştirmenin tek yolu yeniden oluşturmadır.

Bir composable'ın yaşam döngüsünü gösteren diyagram
Şekil 1. Composition'daki bir composable'ın yaşam döngüsü. Bileşime girer, 0 veya daha fazla kez yeniden oluşturulur ve Bileşimden çıkar.

Yeniden oluşturma genellikle bir State<T> nesnesinde yapılan bir değişiklikle tetiklenir. Compose, bunları izler ve kompozisyonda bu belirli State<T> değerini okuyan tüm composable'ları ve çağırdıkları, atlanamayan tüm composable'ları çalıştırır.

Bir composable birden fazla kez çağrılırsa Composition'a birden fazla örnek yerleştirilir. Her görüşmenin beste içinde kendi yaşam döngüsü vardır.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

Önceki kod snippet'indeki öğelerin hiyerarşik düzenini gösteren şema
Şekil 2. Kompozisyonda MyComposable öğesinin temsili. Bir composable birden fazla kez çağrılırsa Composition'a birden fazla örnek yerleştirilir. Farklı renkteki bir öğe, ayrı bir örnek olduğunu gösterir.

Composition'da composable'ın anatomisi

Composition'daki bir composable örneği, çağrı sitesi ile tanımlanır. Compose derleyicisi, her çağrı sitesini ayrı olarak değerlendirir. Birden fazla çağrı sitesinden composable işlevleri çağırmak, Composition'da composable işlevinin birden fazla örneğini oluşturur.

Yeniden oluşturma sırasında bir composable, önceki oluşturma sırasında çağırdığından farklı composable'lar çağırırsa Compose, hangi composable'ların çağrıldığını veya çağrılmadığını belirler. Her iki oluşturmada da çağrılan composable'lar için Compose, girişleri değişmediyse bunları yeniden oluşturmaktan kaçınır.

Yan etkilerin bestelenebilir öğeleriyle ilişkilendirilmesi için kimliğin korunması çok önemlidir. Böylece yan etkiler, her yeniden oluşturma işleminde yeniden başlatılmak yerine başarıyla tamamlanabilir.

Aşağıdaki örneği inceleyelim:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

Yukarıdaki kod snippet'inde LoginScreen, LoginError composable'ı koşullu olarak çağırır ve LoginInput composable'ı her zaman çağırır. Her çağrının, derleyicinin benzersiz şekilde tanımlamak için kullanacağı benzersiz bir çağrı sitesi ve kaynak konumu vardır.

showError işareti true olarak değiştirilirse önceki kodun nasıl yeniden oluşturulduğunu gösteren diyagram. LoginError composable'ı ekleniyor ancak diğer composable'lar yeniden oluşturulmuyor.
Şekil 3. Durum değiştiğinde ve yeniden oluşturma gerçekleştiğinde Kompozisyon'da LoginScreen öğesinin gösterimi. Aynı renk, yeniden oluşturulmadığı anlamına gelir.

LoginInput önce çağrılmaktan ikinci çağrılmaya geçse bile LoginInput örneği yeniden oluşturma işlemleri sırasında korunur. Ayrıca, LoginInput, yeniden oluşturma sırasında değişen herhangi bir parametreye sahip olmadığından LoginInput çağrısı Compose tarafından atlanır.

Akıllı yeniden düzenlemeye yardımcı olmak için ek bilgiler ekleme

Bir composable'ı birden çok kez çağırmak, onu Composition'a da birden çok kez ekler. Aynı çağrı sitesinden bir composable birden çok kez çağrıldığında Compose, bu composable'a yapılan her çağrıyı benzersiz şekilde tanımlayacak bilgilere sahip olmaz. Bu nedenle, örnekleri ayrı tutmak için çağrı sitesine ek olarak yürütme sırası kullanılır. Bu davranış bazen yeterli olsa da bazı durumlarda istenmeyen davranışlara neden olabilir.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

Yukarıdaki örnekte, Compose, Composition'da örneğin farklı kalmasını sağlamak için çağrı sitesine ek olarak yürütme sırasını kullanır. Listenin alt kısmına yeni bir movie eklenirse Oluşturma, listedeki konumları değişmediği için Kompozisyon'da zaten bulunan örnekleri yeniden kullanabilir. Bu nedenle, bu örnekler için movie girişi aynıdır.

Listenin en altına yeni bir öğe eklendiğinde önceki kodun nasıl yeniden oluşturulduğunu gösteren şema. Listedeki diğer öğelerin konumu değişmez ve bu öğeler yeniden oluşturulmaz.
Şekil 4. Listeye yeni bir öğe eklendiğinde Kompozisyon'daki MoviesScreen öğesinin gösterimi. Composition'daki MovieOverview composable'ları yeniden kullanabilirsiniz. MovieOverview içindeki aynı renk, composable'ın yeniden oluşturulmadığı anlamına gelir.

Ancak movies listesi, listenin üst veya ortasına öğe eklenerek, öğeler kaldırılarak ya da yeniden sıralanarak değişirse giriş parametresinin listedeki konumu değişen tüm MovieOverview çağrılarında yeniden oluşturmaya neden olur. Örneğin, MovieOverview, yan etki kullanarak bir film resmi getiriyorsa bu durum son derece önemlidir. Efekt devam ederken yeniden oluşturma işlemi gerçekleşirse bu işlem iptal edilir ve yeniden başlatılır.

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

Listeye yeni bir öğe eklendiğinde önceki kodun nasıl yeniden oluşturulduğunu gösteren şema. Listedeki diğer tüm öğelerin konumu değişir ve yeniden oluşturulması gerekir.
Şekil 5. Listeye yeni bir öğe eklendiğinde MoviesScreen öğesinin Kompozisyon'daki gösterimi. MovieOverview composables yeniden kullanılamaz ve tüm yan etkiler yeniden başlar. MovieOverview içinde farklı bir renk, composable'ın yeniden oluşturulduğu anlamına gelir.

İdeal olarak, MovieOverview örneğinin kimliğinin, kendisine iletilen movie kimliğiyle bağlantılı olduğunu düşünmek isteriz. Film listesini yeniden sıralarsak her MovieOverview composable'ı farklı bir film örneğiyle yeniden oluşturmak yerine, ideal olarak Composition ağacındaki örnekleri de benzer şekilde yeniden sıralarız. Compose, çalışma zamanına ağacın belirli bir bölümünü tanımlamak için hangi değerleri kullanmak istediğinizi söylemenizi sağlar: key composable'ı.

Bir kod bloğunu, bir veya daha fazla değerin iletildiği anahtar composable'ı çağırma işlemiyle sarmalayarak bu değerlerin birleştirilmesini sağlayabilirsiniz. Bu değerler, composable'daki söz konusu örneği tanımlamak için kullanılır. Bir key için değerin küresel olarak benzersiz olması gerekmez. Yalnızca çağrı sitesindeki composable'ların çağrıları arasında benzersiz olması gerekir. Dolayısıyla bu örnekte her movie, movies arasında benzersiz olan bir key içermelidir. Bu key'yi uygulamadaki başka bir composable ile paylaşması sorun değildir.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

Yukarıdaki bilgiler ışığında, listedeki öğeler değişse bile Compose, MovieOverview için yapılan ayrı ayrı çağrıları tanır ve bunları yeniden kullanabilir.

Listeye yeni bir öğe eklendiğinde önceki kodun nasıl yeniden oluşturulduğunu gösteren şema. Liste öğeleri anahtarlarla tanımlandığından, konumları değişmiş olsa bile Compose bunları yeniden oluşturmaz.
Şekil 6. Listeye yeni bir öğe eklendiğinde MoviesScreen öğesinin Kompozisyon'daki gösterimi. MovieOverview composable'ların benzersiz anahtarları olduğundan Compose, hangi MovieOverview örneklerinin değişmediğini tanır ve bunları yeniden kullanabilir. Bu örneklerin yan etkileri yürütülmeye devam eder.

Bazı composable'lar, key composable'ı için yerleşik destek sunar. Örneğin, LazyColumn, items DSL'sinde özel bir key belirtilmesini kabul eder.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

Girişler değişmediyse atlama

Yeniden oluşturma sırasında, girişleri önceki oluşturmadan değişmemişse uygun bazı composable işlevlerin yürütülmesi tamamen atlanabilir.

Bir composable işlev, aşağıdaki durumlar hariç atlanmaya uygundur:

  • İşlevin Unit olmayan bir dönüş türü var.
  • İşlev, @NonRestartableComposable veya @NonSkippableComposable ile açıklama eklenmiş.
  • Gerekli bir parametre kararlı olmayan bir türde

Son koşulu gevşeten bir derleyici modu olan Strong Skipping vardır.

Bir türün kararlı olarak kabul edilebilmesi için aşağıdaki sözleşmeye uyması gerekir:

  • İki örnek için equals işlevinin sonucu, aynı iki örnek için her zaman aynı olacaktır.
  • Türün herkese açık bir özelliği değişirse Composition bilgilendirilir.
  • Tüm genel mülk türleri de kararlıdır.

Bu sözleşmeye dahil olan ve @Stable ek açıklaması kullanılarak açıkça kararlı olarak işaretlenmemiş olsa da Compose derleyicisinin kararlı olarak değerlendireceği bazı önemli ortak türler vardır:

  • Tüm temel değer türleri: Boolean, Int, Long, Float, Char vb.
  • Yaylı çalgılar
  • Tüm işlev türleri (lambda)

Bu türlerin tümü değişmez oldukları için kararlılık sözleşmesine uyabilir. Değişmez türler asla değişmediğinden değişikliğin bileşimini bildirmeleri gerekmez. Bu nedenle, bu sözleşmeyi takip etmek çok daha kolaydır.

Sabit ancak değiştirilebilir olan önemli bir tür, Compose'un MutableState türüdür. Bir değer MutableState içinde tutuluyorsa Compose, State öğesinin .value özelliğindeki değişikliklerden haberdar edileceğinden genel olarak durum nesnesinin kararlı olduğu kabul edilir.

Bir composable'a parametre olarak iletilen tüm türler kararlı olduğunda, parametre değerleri, kullanıcı arayüzü ağacındaki composable konumuna göre eşitlik açısından karşılaştırılır. Önceki çağrıdan bu yana tüm değerler değişmediyse yeniden oluşturma atlanır.

Compose, bir türün kararlı olduğunu yalnızca kanıtlayabildiği takdirde kabul eder. Örneğin, bir arayüz genellikle kararlı olarak kabul edilmez ve uygulanması değişmez olabilecek, değişebilir genel özelliklere sahip türler de kararlı değildir.

Compose, bir türün kararlı olduğunu çıkaramıyorsa ancak Compose'un bunu kararlı olarak ele almasını zorlamak istiyorsanız türü @Stable ek açıklamasıyla işaretleyin.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

Yukarıdaki kod snippet'inde UiState bir arayüz olduğundan Compose, normalde bu türü kararlı olarak değerlendirmeyebilir. @Stable ekleyerek Compose'a bu türün kararlı olduğunu bildirirsiniz. Böylece Compose, akıllı yeniden oluşturmaları tercih edebilir. Bu, arayüz parametre türü olarak kullanılıyorsa Compose'un tüm uygulamalarını kararlı olarak değerlendireceği anlamına da gelir.