Bestelerin yaşam döngüsü

Bu sayfada, bir composable'ı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ış

Durum yönetimi belgelerinde belirtildiği gibi, oluşturma işlemi uygulamanızın kullanıcı arayüzünü tanımlar ve composable'lar çalıştırılarak oluşturulur. Oluşturma, kullanıcı arayüzünüzü tanımlayan composable'ların ağaç yapısıdır.

Jetpack Compose, ilk oluşturma sırasında composable'larınızı ilk kez çalıştırdığında, kullanıcı arayüzünüzü bir oluşturmada 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 işlemi 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 oluşturmayı güncellemesidir.

Oluşturma işlemi yalnızca ilk oluşturma ile üretilebilir ve yeniden oluşturma ile güncellenebilir. 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. Oluşturma'da bir composable'ın yaşam döngüsü. Oluşturmaya girer, 0 veya daha fazla kez yeniden oluşturulur ve oluşturmadan çıkar.

Yeniden oluşturma işlemi genellikle bir State<T> nesnesinde yapılan değişiklikle tetiklenir. Compose bunları takip eder ve oluşturmada söz konusu State<T> değerini okuyan tüm composable'ları ve bunların çağırdığı, atlanamayan tüm composable'ları çalıştırır.

Bir composable birden çok kez çağrılırsa oluşturmaya birden çok örnek yerleştirilir. Her çağrının, oluşturma işleminde 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 diyagram
Şekil 2. MyComposable öğesinin oluşturmadaki temsili. Bir composable birden çok kez çağrılırsa oluşturmaya birden çok örnek yerleştirilir. Farklı renkteki öğeler, ayrı bir örnek olduğunu gösterir.

Oluşturma işleminde bir composable'ın anatomisi

Oluşturma işlemindeki bir composable örneği, çağrı sitesi ile tanımlanır. Compose derleyicisi, her çağrı sitesini ayrı olarak değerlendirir. Oluşturulabilir öğeleri birden fazla çağrı sitesinden çağırmak, oluşturulabilir öğenin 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şturmaz.

Kimliğin korunması, yan etkilerin kendi composable'larıyla ilişkilendirilmesi açısından çok önemlidir. Bu sayede, her yeniden oluşturma işleminde yeniden başlatılmak yerine başarıyla tamamlanabilirler.

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, koşullu olarak LoginError composable'ını ve her zaman LoginInput composable'ını çağırır. Her çağrının benzersiz bir çağrı sitesi ve kaynak konumu vardır. Derleyici, çağrıyı benzersiz şekilde tanımlamak için bunları kullanır.

showError işareti true olarak değiştirilirse önceki kodun nasıl yeniden oluşturulacağını gösteren diyagram. LoginError composable'ı eklenir ancak diğer composable'lar yeniden oluşturulmaz.
Şekil 3. Durum değiştiğinde ve yeniden oluşturma gerçekleştiğinde, oluşturmadaki LoginScreen öğesinin temsili. Aynı renk, yeniden oluşturulmadığı anlamına gelir.

LoginInput ilk çağrılan öğe olmaktan çıkıp ikinci çağrılan öğe olsa da LoginInput örneği, yeniden oluşturma işlemleri boyunca korunur. Ayrıca LoginInput, yeniden oluşturma sırasında değişen parametrelere sahip olmadığından LoginInput çağrısı, Oluşturma tarafından atlanır.

Akıllı yeniden oluşturma işlemlerine yardımcı olacak ek bilgiler ekleme

Bir composable'ı birden çok kez çağırdığınızda, bu composable oluşturmaya da birden çok kez eklenir. Aynı çağrı sitesinden bir composable'ı birden çok kez çağırırken Compose, bu composable'a yapılan her çağrıyı benzersiz şekilde tanımlayacak herhangi bir bilgiye sahip değildir. Bu nedenle, örnekleri ayrı tutmak için çağrı sitesine ek olarak yürütme sırası kullanılır. Bazen bu davranış 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, örneği oluşturmada ayrı tutmak için çağrı sitesine ek olarak yürütme sırasını kullanır. Listenin altına yeni bir movie eklenirse Compose, listedeki konumları değişmediği için oluşturmadaki mevcut örnekleri yeniden kullanabilir. Bu nedenle, movie girişi bu örnekler için aynıdır.

Listenin en altına yeni bir öğe eklenirse önceki kodun nasıl yeniden oluşturulacağını gösteren diyagram. Listedeki diğer öğelerin konumu değişmez ve yeniden oluşturulmaz.
Şekil 4. Listenin en altına yeni bir öğe eklendiğinde MoviesScreen öğesinin oluşturmadaki temsili. Oluşturma işlemindeki MovieOverview bileşenleri yeniden kullanılabilir. MovieOverview renginin aynı olması, composable'ın yeniden oluşturulmadığı anlamına gelir.

Ancak movies listesi, listenin üstüne 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şturma işlemi gerçekleşir. Örneğin, MovieOverview bir yan etki kullanarak film resmi getiriyorsa bu son derece önemlidir. Efekt uygulanırken yeniden oluşturma işlemi gerçekleşirse efekt iptal edilir ve tekrar 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)

        /* ... */
    }
}

Listenin en üstüne yeni bir öğe eklendiğinde önceki kodun nasıl yeniden oluşturulduğunu gösteren diyagram. 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 oluşturmadaki temsili. MovieOverview bileşenleri yeniden kullanılamaz ve tüm yan etkiler yeniden başlatılır. 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ğine bağ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 oluşturma 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 belirtmenize olanak tanır: key bileşeni.

Bir kod bloğunu, bir veya daha fazla değer iletilen anahtar composable ile sarmalayarak bu değerler, oluşturmadaki bu örneği tanımlamak için kullanılmak üzere birleştirilir. key değeri genel olarak benzersiz olmak zorunda değildir. Yalnızca çağrı sitesindeki composable'ların çağrıları arasında benzersiz olması gerekir. Bu örnekte her movie, movies içinde benzersiz bir key değerine sahip olmalıdır. Bu key değerini uygulamanın başka bir yerindeki başka bir composable ile paylaşması sorun olmaz.

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

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

Listenin en üstüne yeni bir öğe eklendiğinde önceki kodun nasıl yeniden oluşturulduğunu gösteren diyagram. Liste öğeleri anahtarlarla tanımlandığı için Compose, konumları değişmiş olsa bile bunları yeniden oluşturmaması gerektiğini bilir.
Şekil 6. Listeye yeni bir öğe eklendiğinde MoviesScreen öğesinin oluşturmadaki temsili. MovieOverview bileşenlerinin 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 desteğe sahiptir. Örneğin, LazyColumn, items DSL'de ö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 atlanıyor

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

Aşağıdaki durumlar haricinde, composable işlevler atlanabilir:

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

Son koşulu esneten Güçlü Atlama adlı bir derleyici modu 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 sonucu, aynı iki örnek için her zaman aynı olacaktır.
  • Türün herkese açık bir özelliği değişirse oluşturma bilgilendirilir.
  • Tüm herkese açık mülk türleri de kararlıdır.

Bu sözleşmeye giren ve Compose derleyicisinin, @Stable ek açıklaması kullanılarak açıkça kararlı olarak işaretlenmemiş olsalar bile kararlı olarak değerlendireceği bazı önemli yaygın 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'lar)

Bu türlerin tümü, değişmez oldukları için kararlı sözleşmesine uyabilir. Değişmez türler hiçbir zaman değişmediğinden, değişikliği oluşturma işlemine bildirmeleri gerekmez. Bu nedenle, bu sözleşmeye uymak çok daha kolaydır.

Kararlı 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ğinde yapılan değişikliklerden haberdar olacağı için durum nesnesi genel olarak kararlı 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 işlemi atlanır.

Compose, bir türü yalnızca kanıtlayabiliyorsa kararlı olarak kabul eder. Örneğin, bir arayüz genellikle kararlı olarak kabul edilmez ve uygulaması sabit olabilecek, değiştirilebilir herkese açık özelliklere sahip türler de kararlı değildir.

Compose bir türün kararlı olduğunu anlayamıyorsa ancak Compose'u bu türü kararlı olarak değerlendirmeye 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 kabul etmeyebilir. @Stable ek açıklamasını ekleyerek Compose'a bu türün kararlı olduğunu bildirirsiniz. Böylece Compose, akıllı yeniden oluşturma işlemlerini 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.