---
title: 'Jetpack Compose ile Arayüz'
source: 'https://academia.sh/tr/kurslar/compose'
language: tr
updated: '2026-10-21T05:02:37+00:00'
license: 'CC BY-SA 4.0'
lessons: 17
outcomes:
    - 'Bileşen işlevleriyle bildirimsel arayüz kurar'
    - 'Yeniden oluşturmanın ne zaman tetiklendiğini açıklar'
    - 'Yan etkileri doğru API ile yönetir'
    - 'Bir verinin arayüz ağacında aşağı mı, yukarı mı, düzey atlayarak derine mi gittiğini yoksa ağacın dışına mı çıktığını ayırt eder'
---

# Jetpack Compose ile Arayüz

Bildirimsel bir arayüzde veri sürekli hareket eder ve her hareket aynı yöne gitmez: ekran durumu bir alt düzeydeki listeye iner, satırdaki dokunma durumun sahibine çıkar, tema rengi aradaki düzeyleri atlayarak en alttaki bileşen işlevine ulaşır, bir ödeme isteği ağacı terk edip bir servise gider. Kurs her düzeneği tek bir soruyla ölçer — bu veri arayüz ağacında hangi yöne gidiyor? Bileşen işlevleri, durum ve hatırlama, durum kaldırma, yeniden oluşturma, yan etkiler, kararlılık, temel düzenler, değiştiriciler, kısıt tabanlı düzen, tembel listeler, hazır arayüz bileşenleri, iskele, gezinme, tema, animasyon, erişilebilirlik ve klasik görünüm sistemi aynı çerçeveden geçirilir.

## Bileşen İşlevi Modeli

İşlev olarak arayüz, durum ve hatırlama, durum kaldırma, yeniden oluşturmanın tetikleyicileri ve atlama koşulları, yaşam döngüsüne bağlı yan etkiler, değişmezlik ve kararlılık.

### Bileşen İşlevleri

Source: https://academia.sh/tr/kurslar/compose/bilesen-islevleri

Bir bileşen işlevi veriyi parametre olarak alır ve arayüz ağacına düğüm ekler. Veri bu ağaçta aşağı, yukarı, derine ya da ağacın dışına gider; ders sekiz hareketi tek bir kuralla sınıflar.

K02'de uygulamanın platformdan ne istediğine baktık ve her isteği tek bir soruyla ölçtük: bu isteği platform nasıl karşılıyor? Bu kurs aynı uygulamanın arayüzüne geçer. Bildirimsel bir arayüz bir işlevin dönüşüdür: veri girer, arayüz ağacı çıkar. O ağaçta veri sürekli hareket eder ve hareketler aynı yöne gitmez. Ekran durumu bir alt düzeydeki listeye iner, satırdaki dokunma durumun sahibine çıkar, tema rengi aradaki düzeyleri atlayarak en alttaki bileşen işlevine ulaşır, bir ödeme isteği ağacı terk edip bir servise gider.

Bu yüzden kursun ölçüsü K02'ninkinden farklıdır. Bir hareketin değeri alıp almadığına bakmayız; **nereden nereye** gittiğine bakarız. Kursun sorusu şudur: **bu veri arayüz ağacında hangi yöne gidiyor?** Dört yanıt vardır ve kurs boyunca değişmez: **aşağı**, **yukarı**, **derin** ve **dış**. Çıktılarda ASCII yazılır: `asagi`, `yukari`, `derin`, `dis`.

Ölçüyü yapan sınıflandırıcı aşağıdaki yardımcı dosyadadır. Her derste aynı dosya ilk blok olarak yer alır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

`Tree` arayüz ağacını çocuktan ebeveyne giden bir eşlem olarak taşır. `Move` bir hareketin etiketini, kaynağını ve hedefini taşır; yönü etiket değil `direction` özelliği verir ve soruyu sırayla sorar. Uçlardan biri ağaçta yoksa hareket dıştır. Hedef kaynağın atasıysa yukarıdır. Hedefin ebeveyni kaynaksa aşağıdır. Kaynak hedefin atasıysa, ama doğrudan ebeveyni değilse, derindir. İki uç akraba değilse `error` verir. Bu sonuncusu ölçüm değil, hatadır: kardeşler arasında doğrudan hareket kurulamaz, kaynağı ile hedefi aynı olan hareket de kurulamaz.

#### Veri Girer, Ağaç Çıkar

Bir bileşen işlevi (`@Composable`) parametrelerini alır ve çağrıldığı yerde arayüz ağacına düğüm ekler. Arayüz, işlevin döndürdüğü bir değer olarak değil, işlevin içinde çağrılan öteki bileşen işlevlerinin oluşturduğu ağaç olarak ortaya çıkar. Bir bileşen işlevi yalnız başka bir bileşen işlevinden çağrılabilir. Her çağrı ebeveyn ile çocuk arasında bir bağ kurar, ağaç da bu bağlardan oluşur.

```kotlin
// Platform kodu: CartScreen
import androidx.compose.runtime.Composable

@Composable
fun CartScreen(items: List<CartItem>, total: String) {
    Header(title = "Sepet")
    CartList(items = items)
    CheckoutButton(total = total)
}
```

`CartScreen` üç bileşen işlevi çağırır; `Header`, `CartList` ve `CheckoutButton` onun çocuklarıdır. `items` ve `total` parametreleri `CartScreen`'den çağrılan işlevlere iner. Bu parça platform kodudur ve burada çıktı, düzen ya da süre iddia etmiyoruz. Dersin ölçtüğü şey, bu çağrı yapısından çıkan ebeveyn ve çocuk ilişkisidir; ölçümü düz Kotlin değerleriyle kurulan bir model yapar. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

#### Sekiz Hareket, Dört Yön

Model bir alışveriş sepeti ekranıdır. `App` kökü `Screen`'i taşır; `Screen` altında `Header`, `CartList` ve `CheckoutButton` durur, `CartList`'in çocuğu `CartRow`'dur. Hareketlerin uçları elle seçilmez, iki model işlevinden gelir. `hoistTo`, durumu okuyan bileşen işlevlerinin en alt ortak atasını bulur; durumun sahibi odur. `nearestProvider`, bir düğümün atalarında verilen anahtarı sağlayan ilk düğümü arar.

```kotlin
// Main.kt
fun hoistTo(readers: List<String>, tree: Tree): String {
    val paths = readers.map { listOf(it) + tree.ancestors(it) }
    return paths.first().first { node -> paths.all { node in it } }
}

fun nearestProvider(node: String, key: String, provides: Map<String, Set<String>>, tree: Tree): String? =
    tree.ancestors(node).firstOrNull { key in provides.getOrDefault(it, emptySet()) }

fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    val owner = hoistTo(listOf("CartList", "CheckoutButton", "Header"), tree)
    val themeSource = nearestProvider("CartRow", "tema", mapOf("App" to setOf("tema")), tree)!!
    println("durum sahibi: $owner")
    println("tema saglayicisi: $themeSource")

    val moves = listOf(
        Move("sepet-listesi", owner, "CartList", tree),
        Move("toplam-tutar", owner, "CheckoutButton", tree),
        Move("baslik", owner, "Header", tree),
        Move("satir-sil", "CartRow", owner, tree),
        Move("odeme-tikla", "CheckoutButton", owner, tree),
        Move("tema-rengi", themeSource, "CartRow", tree),
        Move("odeme-servisi", owner, "PaymentApi", tree),
        Move("kayit-gunlugu", owner, "Analytics", tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
durum sahibi: Screen
tema saglayicisi: App
sepet-listesi: kimden=Screen | kime=CartList -> asagi
toplam-tutar: kimden=Screen | kime=CheckoutButton -> asagi
baslik: kimden=Screen | kime=Header -> asagi
satir-sil: kimden=CartRow | kime=Screen -> yukari
odeme-tikla: kimden=CheckoutButton | kime=Screen -> yukari
tema-rengi: kimden=App | kime=CartRow -> derin
odeme-servisi: kimden=Screen | kime=PaymentApi -> dis
kayit-gunlugu: kimden=Screen | kime=Analytics -> dis
asagi: 3
yukari: 2
derin: 1
dis: 2
```

Sekiz hareketin üçü aşağı, ikisi yukarı, biri derin, ikisi dıştır. Durumun sahibi `Screen` çıktı. Sepet listesi, toplam tutar ve başlık sahibinden doğrudan çocuklarına iner: aşağı. Satırdaki silme ve ödeme düğmesindeki dokunma, atası olan sahibe çıkar: yukarı. Tema rengi `App`'ten `CartRow`'a gider; arada `Screen` ve `CartList` vardır ve renk onlara uğramaz: derin. `PaymentApi` ve `Analytics` ağaçta yoktur; ödeme isteği ile kayıt satırı ağacı terk eder: dış. Bu dağılım kursun bağlayıcı sayısıdır; sonraki dersler yalnız kendi kesitini ölçer ve bu sayıyı değiştirmez.

**BI1.** Model ağacı yalnız ebeveyn ilişkisi olarak taşır. Gerçek arayüz ağacının nasıl kurulduğu ve bileşen işlevlerinin hangi sırayla çağrıldığı ölçülmedi.

#### Parametre Zinciri

Bir bileşen işlevi parametresini doğrudan çocuğuna verirse hareket tektir. `Banner` işlevinin `Greeting(name)` çağırması, `Banner`'dan `Greeting`'e giden bir harekettir. Hareketin uçları dersin model işlevinden gelir: `parameterChain`, verinin sahibinden okuyucusuna kadar olan düğümleri ağacın atalarından sıralar.

```kotlin
// Chain.kt — verinin sahibinden okuyucusuna parametre zinciri
fun parameterChain(owner: String, reader: String, tree: Tree): List<String> =
    ((listOf(reader) + tree.ancestors(reader)).takeWhile { it != owner } + owner).reversed()
```

```kotlin
// Main.kt — Greeting(name) cagrisi: ebeveynden cocuga tek hareket
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "Banner" to "Header",
        "Greeting" to "Banner",
    ))
    val path = parameterChain("Banner", "Greeting", tree)
    val moves = path.zipWithNext().map { (from, to) -> Move("selam-adi", from, to, tree) }
    println("zincir: ${path.joinToString(" > ")}")
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
zincir: Banner > Greeting
selam-adi: kimden=Banner | kime=Greeting -> asagi
asagi: 1
yukari: 0
derin: 0
dis: 0
```

Ad doğrudan ebeveynden çocuğa gitti: tek aşağı hareket. Şimdi aynı ad ekranın en üstünde, `Screen`'de bilinsin ve `Greeting`'e ulaşması gereksin. `Header` ve `Banner` adı kendileri kullanmaz, yalnız bir alt düzeye iletir.

```kotlin
// Main.kt — ayni ad uc duzey iner: uc ayri asagi hareket
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "Banner" to "Header",
        "Greeting" to "Banner",
    ))
    val path = parameterChain("Screen", "Greeting", tree)
    val moves = path.zipWithNext().map { (from, to) -> Move("ad-${to.lowercase()}", from, to, tree) }
    println("zincir: ${path.joinToString(" > ")}")
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
zincir: Screen > Header > Banner > Greeting
ad-header: kimden=Screen | kime=Header -> asagi
ad-banner: kimden=Header | kime=Banner -> asagi
ad-greeting: kimden=Banner | kime=Greeting -> asagi
asagi: 3
yukari: 0
derin: 0
dis: 0
```

Veri her düzeyden parametreyle geçirildiğinde üç ayrı aşağı hareket olur; hiçbiri derin değildir. Parametre zinciri, atlanan düzey bırakmaz. Ortadaki `Header` ve `Banner` adı kullanmasa da imzalarında taşımak zorundadır.

#### Kardeşler Arasında Hareket Yok

Sepet ekranında `Header` ile `CartList` kardeştir. Başlıktaki bir değerin liste tarafından okunması gerektiğini düşünelim. Bu iki uç akraba değildir: hiçbiri ötekinin atası ya da çocuğu olmadığı için `Move("x", "Header", "CartList", tree)` satırı `direction` içindeki son dala düşer ve `error` verir. Bu bir sınıflandırma değil, kurulamayan bir hareket demektir. Bileşen işlevleri arasında da gerçek bir yol yoktur: iki kardeş ortak ebeveyni üzerinden konuşur. Değer önce ortak ataya çıkar, yani yukarı gider; oradan öteki kardeşe iner, yani aşağı gider. Kardeşler arası tek bir hareket iki harekete bölünür ve bu bölünme, bir sonraki derslerde durumun neden ortak atada durduğunu açıklayacak.

İki ucu da elle yazılan bir satır, yani `Move("x", "Screen", "Header", tree)` gibi, kuralı göstermek için kullanılabilir ama ölçüm sayılmaz. Bu derste ve kursun geri kalanında ölçüm sayılan her hareketin en az bir ucu dersin bir model işlevinden gelir; kuralı göstermek için yazılan satırlar düzyazıda böyle anılır. Yön ise hiçbir zaman elle yazılmaz.

#### Sınırlayıcı Ölçüm

Sınıflandırıcı geometriyi görür, kanalı görmez. Verinin parametreyle mi, geri çağrıyla mı, örtük bir sağlayıcıyla mı taşındığını ayırmaz; yalnız iki ucun ağaçtaki konumuna bakar. Bunu görmek için tek bir sağlayıcıyı, tek bir kanalı ve iki okuyucuyu alalım. `App` temayı sağlar. Okuyuculardan biri doğrudan çocuğu `Screen`, öteki iki düzey aşağıdaki `CartRow`'dur.

```kotlin
// Main.kt — ayni saglayici, ayni kanal, iki yon
fun nearestProvider(node: String, key: String, provides: Map<String, Set<String>>, tree: Tree): String? =
    tree.ancestors(node).firstOrNull { key in provides.getOrDefault(it, emptySet()) }

fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val provides = mapOf("App" to setOf("tema"))
    val moves = listOf("Screen", "CartRow").map { reader ->
        Move("tema-${reader.lowercase()}", nearestProvider(reader, "tema", provides, tree)!!, reader, tree)
    }
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
tema-screen: kimden=App | kime=Screen -> asagi
tema-cartrow: kimden=App | kime=CartRow -> derin
asagi: 1
yukari: 0
derin: 1
dis: 0
```

Sağlayıcı aynı, kanal aynı, değer aynı; sonuç iki yön. `Screen` sağlayıcının doğrudan çocuğudur ve değer ona aşağı gider. `CartRow` iki düzey aşağıdadır ve değer aradakilere uğramadan ona derin gider. Bir sağlayıcı değerinin her zaman derin olduğunu söylemek yanlıştır: yönü sağlayıcının hedefe olan uzaklığı belirler. Aynı değeri `Screen`'den `CartList`'e, oradan `CartRow`'a parametreyle geçirseydik iki aşağı hareket olurdu. Kanalı ayırmak kuralın işi değildir; ders hangi kanalı kurduğunu her seferinde söyler.

**BI2.** Model sağlayıcıyı bir anahtar kümesi olarak taşır ve yalnız en yakın sağlayıcıyı arar. Aradaki bir düğümün aynı anahtarı yeniden sağlaması, bu derste ölçülen sekiz hareketin hiçbirinde yoktur.

#### Özet

- Bu dersin yönü: aşağı. Bir bileşen işlevi veriyi parametreyle alır ve ebeveynden doğrudan çocuğa giden her hareket aşağıdır.
- Kursun çekirdek ölçümünde sekiz hareketin dağılımı aşağı 3, yukarı 2, derin 1, dış 2'dir; sonraki dersler bu sayıyı değiştirmez.
- Aynı ad üç düzey boyunca parametreyle geçirildiğinde üç ayrı aşağı hareket olur: aşağı 3, derin 0.
- Aynı sağlayıcı değeri doğrudan çocuğa aşağı, iki düzey aşağıdaki torununa derin gider: aşağı 1, derin 1.
- Kural geometriyi görür, kanalı görmez; ders hangi kanalı kurduğunu söyler.

#### Sonraki Adım

Bu derste veri sahibinden okuyucusuna parametreyle indi. Sıradaki ders, `Durum ve Hatırlama`, durumun bir bileşen işlevi çağrıları arasında nasıl hatırlandığını ve durumun sahibinden okuyucusuna giden değerin hangi yöne gittiğini ölçer.

### Durum ve Hatırlama

Source: https://academia.sh/tr/kurslar/compose/durum-ve-hatirlama

Bir bileşen işlevi çağrıdan çağrıya hiçbir şey saklamaz; saklanacak değeri remember bir düğüme bağlar. Ders durumun düğüme bağlı oluşunu ve sahibinden okuyucusuna giden değerin yönünü ölçer.

Önceki derste veri sahibinden okuyucusuna parametreyle indi. Parametre bir değerin yanından geçtiği yoldur; değerin kendisi nerede yaşar? Bir bileşen işlevi her çağrıldığında baştan çalışır ve yerel değişkenleri çağrı bitince kaybolur. Bir sayacın değeri, bir metin alanının içeriği ya da bir listenin açık olup olmadığı çağrıdan çağrıya taşınmak zorundadır. Bunu yapan düzenek hatırlamadır: `remember`, bir değeri arayüz ağacındaki bir düğüme bağlar.

Ders iki şey ölçer. Birincisi, durumun düğüme bağlı oluşudur. İkincisi, durumun sahibinden okuyucusuna giden değerin yönüdür: **bu veri arayüz ağacında hangi yöne gidiyor?** Sınıflandırıcı önceki derslerdeki gibi ilk blokta durur.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Durum Bir Düğüme Bağlanır

Platformda durum, değeri gözlemlenebilir kılan `mutableStateOf` ile kurulur. `remember` bu durum nesnesini oluşturmanın sonucunu arayüz ağacına yazar ve bileşen işlevi yeniden oluşturulduğunda aynı nesneyi geri verir. Çağıran bileşen işlevi ağaçtan kalkarsa hatırlanan nesne de unutulur. Platform belgesi bunu şöyle söyler: `remember`, değeri oluşturma sırasında saklar ve yeniden oluşturmada saklanan değeri döndürür; `remember`'ı çağıran bileşen işlevi ağaçtan kaldırıldığında nesneyi unutur.

```kotlin
// Platform kodu: Counter
import androidx.compose.runtime.*

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Sayı: $count")
    }
}
```

`Counter`, `count` değerini kendi düğümüne bağlar. Düğmeye her dokunuş değeri yazar, yazma da değeri okuyan bileşen işlevlerinin yeniden oluşturulmasını ister. Bu parça platform kodudur ve burada çıktı, düzen ya da süre iddia etmiyoruz. Ölçümü düz Kotlin değerleriyle kurulan bir model yapar. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Model hatırlamayı düğüm adıyla anahtarlanan bir eşlem olarak taşır. Platform durumu ağaçtaki konuma bağlar; modelde konumun yerini düğümün adı tutar.

```kotlin
// Slots.kt — durumu dugume baglayan hatirlama modeli
class Slots {
    private val values = mutableMapOf<String, Int>()
    fun remember(node: String, initial: Int): Int = values.getOrPut(node) { initial }
    fun write(node: String, value: Int) { values[node] = value }
    fun leave(node: String) { values.remove(node) }
    fun holds(node: String): Boolean = node in values
}
```

`remember` ilk çağrıda başlangıç değerini yazar, sonraki çağrılarda yazılı değeri geri verir. `write` durumu değiştirir. `leave` düğümün ağaçtan çıkışını kurar: düğümün satırı silinir.

```kotlin
// Main.kt — durum dugume baglidir, dugum cikinca yitirilir
fun main() {
    val slots = Slots()
    println("ilk olusturma: ${slots.remember("Screen", 0)}")
    slots.write("Screen", 3)
    println("yeniden olusturma: ${slots.remember("Screen", 0)}")
    slots.leave("Screen")
    println("agactan cikis: sakli=${slots.holds("Screen")}")
    println("yeniden giris: ${slots.remember("Screen", 0)}")
}
```

```text
ilk olusturma: 0
yeniden olusturma: 3
agactan cikis: sakli=false
yeniden giris: 0
```

Yeniden oluşturmada başlangıç değeri 0 yeniden verildi, ama geri gelen 3 oldu: hatırlanan değer başlangıç değerini yendi. Düğüm ağaçtan çıkıp yeniden girince durum sıfırdan başladı. Bu yüzden bir bileşen işlevinin ekrandan kalkıp geri gelmesi, yazılan değerin kaybolması demektir.

**BI3.** Model hatırlamayı yalnız tam sayı değerlerle ve düğüm adıyla kurar. Platformda anahtarın çağrının ağaçtaki konumu olması, `remember`'a verilen anahtarların önbelleği boşaltması ve gözlemlenebilir durumun yazmada okuyucuları işaretlemesi modelde yoktur.

#### Her Çağrının Kendi Durumu Vardır

Aynı bileşen işlevi ağaçta iki yerde çağrılırsa iki düğüm oluşur ve her düğüm kendi durumunu tutar. Sepet satırları bunun örneğidir: aynı `CartRow` işlevi iki kez çağrılır, ama birinin adedini artırmak ötekini değiştirmez.

```kotlin
// Main.kt — ayni islev, iki cagri, iki ayri durum
fun main() {
    val slots = Slots()
    val rows = listOf("CartRow1", "CartRow2")
    rows.forEach { slots.remember(it, 1) }
    slots.write("CartRow1", 5)
    rows.forEach { println("$it: ${slots.remember(it, 1)}") }
}
```

```text
CartRow1: 5
CartRow2: 1
```

Durum işlevin değil, işlevin ağaçtaki çağrısının malıdır. Bu ayrım sonraki derste önem kazanır: bir değeri iki kardeş paylaşacaksa o değer kardeşlerden birinin düğümünde duramaz.

#### Hatırlanan Değer Okuyucusuna İner

Durumu hatırlayan düğüm onun sahibidir. Çocuklar değeri sahibinden parametreyle alır. Model, bir düğümün okuduğu durumun sahibini düğümün kendisinden başlayıp atalarına doğru yürüyerek bulur: değeri hatırlayan ilk düğüm sahiptir.

```kotlin
// Holder.kt — bir dugumun okudugu durumu hangi dugumun hatirladigini bulur
fun holderOf(node: String, slots: Slots, tree: Tree): String? =
    (listOf(node) + tree.ancestors(node)).firstOrNull { slots.holds(it) }
```

Sepet ekranında adedi `Screen` hatırlasın; `Header` ve `CartList` onu okusun. Hareketlerin bir ucu `holderOf` dönüşünden gelir, yön elle yazılmaz.

```kotlin
// Main.kt — Screen hatirlar, cocuklar okur: iki asagi hareket
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val slots = Slots()
    slots.remember("Screen", 2)
    val readers = listOf("Header", "CartList")
    val moves = readers.map { reader ->
        Move("adet-${reader.lowercase()}", holderOf(reader, slots, tree)!!, reader, tree)
    }
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
adet-header: kimden=Screen | kime=Header -> asagi
adet-cartlist: kimden=Screen | kime=CartList -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 0
```

Sahibi `Screen` çıktı ve değer onun iki doğrudan çocuğuna iner: iki aşağı hareket. Bu dersin yönü budur. `CartRow` da adedi okusaydı, değeri `CartList` üzerinden parametreyle alırdı; bu, geçen dersin ölçtüğü gibi iki ayrı aşağı hareket olurdu.

Bu ayrım M30/K03'ten farklıdır. O kursun "bu yeniden hesaplamayı ne tetikledi" sorusu, bir görünümün neden yeniden hesaplandığını okuyanın gözünden sınıflar: yerel, ebeveyn, gözlem, ortam. Buradaki durum yazması da okuyucuların yeniden oluşturulmasını ister, ama ders bunu ölçü yapmaz. Ölçtüğümüz tetik değil, değerin sahibinden okuyucusuna giden yoldur.

#### Kaydedilebilir Durum

Platform belgesine göre `remember` ile tutulan durum yeniden oluşturmalar boyunca saklanır, ama yapılandırma değişikliklerinde korunmaz; bunun için `rememberSaveable` kullanılır. `rememberSaveable`, durumu kayıtlı örnek durumu düzeneğiyle activity ya da süreç yeniden yaratıldığında da korur, örneğin ekran döndürüldüğünde. Kullanıcı activity'yi tümüyle kapatırsa korumaz.

```kotlin
// Platform kodu: SavedCounter
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable

@Composable
fun SavedCounter() {
    var count by rememberSaveable { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Sayı: $count")
    }
}
```

Yazım biçimi `remember` ile aynıdır; fark değerin nerede saklandığıdır. `Bundle`'a yazılabilen değerler kendiliğinden kaydedilir, öteki tipler için bir kaydedici verilir. Modelimiz sistemin yeniden yarattığı ortamı kurmadığı için bu davranış burada ölçülmez; parça KAYNAK kalır.

#### Sınırlayıcı Ölçüm

Kural bir hareketi sınıflar, hareketsizliği sınıflamaz. Durum yalnız kendi düğümünde okunuyorsa kaynağı ile hedefi aynı olur ve böyle bir hareket kurulmaz; `Move` onu sınıflamaya çalışmaz, çünkü ortada hareket yoktur. Aynı `Counter` düğümünü iki biçimde kuralım: durumu kendisi hatırladığında ve durum bir üst düğümde durduğunda.

```kotlin
// Main.kt — yerel durum hareket degildir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Counter" to "Screen",
        "Header" to "Screen",
    ))
    for ((holder, label) in listOf("Counter" to "yerel", "Screen" to "ortak")) {
        val slots = Slots()
        slots.remember(holder, 0)
        val moves = listOf("Counter")
            .filter { holderOf(it, slots, tree) != it }
            .map { Move("adet-okuma", holderOf(it, slots, tree)!!, it, tree) }
        println("durum $label:")
        moves.forEach { it.describe() }
        countDirections(moves)
    }
}
```

```text
durum yerel:
asagi: 0
yukari: 0
derin: 0
dis: 0
durum ortak:
adet-okuma: kimden=Screen | kime=Counter -> asagi
asagi: 1
yukari: 0
derin: 0
dis: 0
```

Yerel durumda hareket listesi boş kalır ve dört yönün hepsi sıfır basılır. Durum bir üst düğüme çıkınca aynı okuma bir aşağı harekete döner. Kural durumun yerelken "yok" olduğunu söylemez; yalnız listeye girmeyen bir şeyi saymaz. Bu fark sonraki derste önem kazanır: durumu yukarı kaldırmak, yerel bir değeri hareket üretir hâle getirmektir.

**BI4.** Model okuma hareketini yalnız durumun sahibinden okuyucusuna kurar. Okuyucunun durumu yazması, yani ters yöndeki olay bu derste ölçülmedi; bir sonraki dersin konusudur.

#### Özet

- Bu dersin yönü: aşağı. Hatırlanan durumun sahibinden okuyucusuna giden değer, doğrudan çocuğa aşağı iner.
- `remember` değeri bir düğüme bağlar: yeniden oluşturmada saklanan 3 geri geldi, düğüm ağaçtan çıkıp yeniden girince değer 0'a döndü.
- Aynı işlevin iki çağrısı ayrı durum tutar: `CartRow1` için 5, `CartRow2` için 1.
- `Screen`'in hatırladığı adedi iki doğrudan çocuk okuyunca ölçüm aşağı 2 verdi.
- Yalnız kendi düğümünde okunan yerel durum hareket üretmez: dört yönün hepsi 0; durum bir üst düğümde durunca aşağı 1.

#### Sonraki Adım

Bu derste değer sahibinden okuyucusuna indi. Okuyucu değeri değiştirmek istediğinde ne olur? Sıradaki ders, `Durum Kaldırma`, durumu okuyucuların ortak atasına taşır ve değiştirme isteğinin sahibine hangi yönde döndüğünü ölçer.

### Durum Kaldırma

Source: https://academia.sh/tr/kurslar/compose/durum-kaldirma

Durum kaldırma, durumu onu okuyan ve yazan bileşen işlevlerinin ortak atasına taşır; değer aşağı iner, olay yukarı çıkar. Ders sahibe giden olayın yönünü ve sahibin yüksekliğinin hareket sayısını nasıl değiştirdiğini ölçer.

Önceki derste `Screen` bir adedi hatırladı ve iki çocuğu değeri okudu. Değer aşağı indi, ama okuyucu o değeri değiştirmek istediğinde yapabileceği bir şey yoktu: durum onun değil, atasına aitti. Bu derste okuyucunun isteği sahibine döner. Durumu okuyan ve yazan bileşen işlevlerinin ortak atasına taşımaya durum kaldırma denir. Dersin sorusu önceki derslerle aynıdır: **bu veri arayüz ağacında hangi yöne gidiyor?**

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Değer Aşağı, Olay Yukarı

Platform belgesi durumu, onu okuyan ve yazan bileşen işlevlerinin en alt ortak atasına kaldırmayı önerir. Durumun sahibi tüketicilere değiştirilemez durumu ve onu değiştirecek olayları sunar. Bileşen işlevi bunu iki parametreyle alır: gösterilecek `value` ve değişiklik istendiğinde çağrılacak `onValueChange`. Durumu kendi tutmayan işleve durumsuz denir; durumu tutup bir durumsuz işleve veren işleve durumlu.

```kotlin
// Platform kodu: QuantityScreen
import androidx.compose.runtime.*

@Composable
fun QuantityField(value: Int, onValueChange: (Int) -> Unit) {
    Button(onClick = { onValueChange(value + 1) }) {
        Text("Adet: $value")
    }
}

@Composable
fun QuantityScreen() {
    var quantity by remember { mutableStateOf(1) }
    QuantityField(value = quantity, onValueChange = { quantity = it })
}
```

`QuantityField` durumu tutmaz. Değeri parametreyle alır, dokunuşu geri çağrıyla bildirir; yeni değeri yazmak `QuantityScreen`'in işidir. Değer ebeveynden çocuğa, olay çocuktan ebeveynine gider. Buna tek yönlü veri akışı denir: her hareket tek yönlüdür, ama iki hareket zıt yönlere gider. Bu parça platform kodudur ve burada çıktı, düzen ya da süre iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Bu ayrım M12/K03'ten farklıdır. O kursun "bu sonucu kim değiştirebiliyor" sorusu sonucun sahibini sınıflar. Bu derste sahibi bir model işlevi bulur, ama ölçü sahip değildir: ölçü, sahibe giden ve sahibinden çıkan hareketin yönüdür.

#### Ortak Ata Sahip Olur

Model, okuyucuların en alt ortak atasını bulan bir işlevle başlar. Ad bu derste yenidir; açılış dersindeki `hoistTo` ile aynı işi yapar.

```kotlin
// Owner.kt — okuyucularin en alt ortak atasi durumun sahibidir
fun commonOwner(readers: List<String>, tree: Tree): String {
    val paths = readers.map { listOf(it) + tree.ancestors(it) }
    return paths.first().first { node -> paths.all { node in it } }
}
```

Sepet ekranında adedi `CartList` değiştirir ve `CheckoutButton` toplamı göstermek için okur. Bunlar kardeştir. Önceki dersten biliyoruz ki kardeşler arasında doğrudan hareket kurulamaz; ikisinin paylaştığı değer ortak atadan geçer. Bir yardımcı bu bölünmeyi kurar: kaynaktan ortak sahibe bir yukarı hareket, sahibinden hedefe bir aşağı hareket.

```kotlin
// Siblings.kt — kardesler arasi istegi ortak ata uzerinden iki harekete boler
fun viaOwner(label: String, from: String, to: String, tree: Tree): List<Move> {
    val owner = commonOwner(listOf(from, to), tree)
    return listOf(
        Move("$label-yukari", from, owner, tree),
        Move("$label-asagi", owner, to, tree),
    )
}
```

```kotlin
// Main.kt — kardesten kardese hareket iki harekettir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    println("durum sahibi: ${commonOwner(listOf("CartList", "CheckoutButton"), tree)}")
    val moves = viaOwner("adet", "CartList", "CheckoutButton", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
durum sahibi: Screen
adet-yukari: kimden=CartList | kime=Screen -> yukari
adet-asagi: kimden=Screen | kime=CheckoutButton -> asagi
asagi: 1
yukari: 1
derin: 0
dis: 0
```

Sahip `Screen` çıktı. `CartList`'ten `CheckoutButton`'a giden istek önce sahibe çıktı: yukarı. Sahipten düğmeye indi: aşağı. Bu dersin yönü olan yukarı, kardeşler arası her paylaşımın ilk yarısıdır.

**BI5.** Model sahibi yalnız okuyucuların ve yazanın düğüm adlarından bulur. Platformda sahibin yazma ve okuma mantığının arayüz mantığı mı iş mantığı mı olduğuna göre ağacın dışında, örneğin bir `ViewModel`'de de durabilmesi modelde yoktur.

#### Tam Akış

Şimdi tüm akışı kuralım. Sahip `commonOwner` ile seçilir. Her okuyucuya sahibinden değer iner, yazan düğümden sahibe olay çıkar. Değer ve olay aradaki her düzeyden parametre ve geri çağrıyla geçer; bir düzey atlanmaz. Bunu yapan yardımcı bir aşağı yolu çıkarır.

```kotlin
// Flow.kt — sahipten okuyuculara inen degerler, yazandan sahibe cikan olay
fun downPath(owner: String, node: String, tree: Tree): List<String> =
    ((listOf(node) + tree.ancestors(node)).takeWhile { it != owner } + owner).reversed()

fun flowMoves(owner: String, readers: List<String>, writer: String, tree: Tree): List<Move> {
    val values = readers
        .flatMap { reader -> downPath(owner, reader, tree).zipWithNext() }
        .distinct()
        .map { (from, to) -> Move("deger-${to.lowercase()}", from, to, tree) }
    val events = downPath(owner, writer, tree).reversed().zipWithNext()
        .map { (from, to) -> Move("olay-${from.lowercase()}", from, to, tree) }
    return values + events
}

fun liftTo(node: String, levels: Int, tree: Tree): String = tree.ancestors(node)[levels - 1]
```

`flowMoves` aynı hareketi iki okuyucu için iki kez saymaz; `distinct` ortak yol parçasını tek hareket yapar. `liftTo` bir düğümün birkaç düzey üstünü verir ve bir sonraki bölümde kullanılacak.

```kotlin
// Main.kt — deger asagi, olay yukari
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    val readers = listOf("CartList", "CheckoutButton")
    val owner = commonOwner(readers, tree)
    println("durum sahibi: $owner")
    val moves = flowMoves(owner, readers, "CartList", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
durum sahibi: Screen
deger-cartlist: kimden=Screen | kime=CartList -> asagi
deger-checkoutbutton: kimden=Screen | kime=CheckoutButton -> asagi
olay-cartlist: kimden=CartList | kime=Screen -> yukari
asagi: 2
yukari: 1
derin: 0
dis: 0
```

İki değer sahibinden çocuklarına iner: iki aşağı. Adedi değiştiren dokunuş `CartList`'ten sahibe çıkar: bir yukarı. Sayı olarak aşağı önde, ama durum kaldırmanın yeni getirdiği hareket olaydır: durum yerelken olay yoktu, sahibe taşınınca yukarı gidecek bir istek doğdu. Dersin yönü bu yüzden yukarıdır.

Durum yalnız bir bileşen işlevine aitse kaldırılacak bir şey yoktur. Okuyucu da yazan da `CartList` ise ortak ata `CartList`'in kendisidir ve hareket kurulmaz.

```kotlin
// Main.kt — tek okuyucu: sahip okuyucunun kendisi
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
        "CheckoutButton" to "Screen",
    ))
    val owner = commonOwner(listOf("CartList"), tree)
    println("durum sahibi: $owner")
    countDirections(flowMoves(owner, listOf("CartList"), "CartList", tree))
}
```

```text
durum sahibi: CartList
asagi: 0
yukari: 0
derin: 0
dis: 0
```

Kaldırma, yalnız paylaşım olduğunda gerekir. Yerel durum önceki dersteki gibi hareket üretmez.

#### Sınırlayıcı Ölçüm

Durumu gerekenden yükseğe, `App`'e kaldırırsak ne değişir? Olay hâlâ sahibe gider; yönü yukarıdır. Ama sahip uzaklaştığı için değer ve olay aradaki her düzeyden geçmek zorunda kalır.

```kotlin
// Main.kt — ayni durum bir ust duzeye kaldirilinca hareket sayisi artar
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    val readers = listOf("CartList", "CheckoutButton")
    val needed = commonOwner(readers, tree)
    for (owner in listOf(needed, liftTo(needed, 1, tree))) {
        println("durum sahibi: $owner")
        val moves = flowMoves(owner, readers, "CartList", tree)
        countDirections(moves)
        println("toplam: ${moves.size}")
    }
}
```

```text
durum sahibi: Screen
asagi: 2
yukari: 1
derin: 0
dis: 0
toplam: 3
durum sahibi: App
asagi: 3
yukari: 2
derin: 0
dis: 0
toplam: 5
```

Sahip `App` olunca değer `Screen`'e, oradan iki okuyucuya iner: üç aşağı hareket. Olay `CartList`'ten `Screen`'e, oradan `App`'e çıkar: iki yukarı hareket. Toplam üçten beşe çıktı. Dört kategori adı değişmedi; `derin` ve `dis` yine sıfır. `Screen` yalnız iletici oldu: değeri kullanmıyor, ama imzasında taşıyor. Platform belgesinin öğüdü de budur: durumu en alt ortak atasına kaldır ve gerekmeyen bileşen işlevlerine geçirme.

Kardeşler arası hareketin ortak ata üzerinden iki harekete bölünmesi ve sahibin yüksekliğinin hareket sayısını değiştirmesi aynı kurala bağlıdır: kural yalnız iki ucun ağaçtaki konumuna bakar. Hangi yolu seçtiğimizi ders söyler, kural değil.

**BI6.** Model, iletici düğümlerin değeri ve olayı olduğu gibi aktardığını varsayar. Platformda aradaki bileşen işlevinin değeri dönüştürmesi ya da olayı kendisinin işlemesi modelde yoktur.

#### Özet

- Bu dersin yönü: yukarı. Durum kaldırılınca okuyucudan sahibine yeni bir hareket doğar: değişiklik isteği.
- Sahip, okuyucuların en alt ortak atasıdır: `CartList` ve `CheckoutButton` için `Screen`.
- Kardeşler arası istek iki harekete bölünür: yukarı 1, aşağı 1.
- Tam akışta iki değer aşağı iner, bir olay yukarı çıkar: aşağı 2, yukarı 1.
- Durum `App`'e kaldırılınca aşağı 3, yukarı 2 olur; toplam hareket 3'ten 5'e çıkar, kategoriler aynı kalır.

#### Sonraki Adım

Bu derste olay sahibine döndü ve sahip değeri yeniden indirdi. Değer değişince hangi bileşen işlevlerinin yeniden çalışacağı ayrı bir sorudur. Sıradaki ders, `Yeniden Oluşturma`, değişikliğin sahibinden hangi düğümlere ulaştığını ve değerin okunmasının ertelenmesinin yönü nasıl değiştirdiğini ölçer.

### Yeniden Oluşturma

Source: https://academia.sh/tr/kurslar/compose/yeniden-olusturma

Durum değişince platform, değişikliği okuyan bileşen işlevlerini yeniden çalıştırır ve girdisi değişmeyenleri atlar. Ders değişikliğin sahibinden okuyucusuna hangi yolla gittiğini ve okumanın ertelenmesinin yönü nasıl değiştirdiğini ölçer.

Önceki derste olay sahibine döndü ve sahip yeni değeri yazdı. Yazıdan sonra ekranın güncellenmesi gerekir, ama ağacın tamamının yeniden çalışması gerekmez. Platform durum değişince yalnız değişiklikten etkilenebilecek bileşen işlevlerini yeniden çalıştırır. Buna yeniden oluşturma denir. Dersin sorusu yine aynıdır: **bu veri arayüz ağacında hangi yöne gidiyor?** Burada veri, durumun yeni değeridir ya da değiştiğinin haberidir; ve yönü, okumanın nerede yapıldığına bağlıdır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Yeniden Oluşturma ve Atlama

Platform belgesine göre durum değiştiğinde platform yeniden oluşturma planlar: değişmiş olabilecek bileşen işlevlerini yeniden çalıştırır ve oluşturulan ağacı günceller. Yeniden oluşturma genellikle bir `State<T>` nesnesinin değişmesiyle başlar; platform bu nesneyi okuyan bileşen işlevlerini ve onların çağırdığı, atlanamayan bileşen işlevlerini çalıştırır. Girdisi önceki oluşturmadan değişmemiş uygun bir bileşen işlevi ise tümüyle atlanabilir. Belgeye göre kararlı girdiler `equals` ile, kararsız girdiler güçlü atlama kipinde referans eşitliğiyle karşılaştırılır; girdiler önceki değerlerle eşitse bileşen işlevi atlanır.

Bir yeniden oluşturmanın hangi bileşen işlevlerini kapsadığına yeniden oluşturma kapsamı denir. Değişen durumu okuyan bileşen işlevi kapsamın içindedir. Kapsamın içindeki bir bileşen işlevi çocuğuna değişen bir değer geçirirse çocuk da kapsama girer; değişmeyen girdiyle çağrılan çocuk atlanır.

Sepet ekranında toplamı `CartScreen` hatırlasın ve en alttaki `CartRow` göstersin. Önce değeri sahibinde okuyup parametreyle indiren biçime bakalım.

```kotlin
// Platform kodu: ValueChannel
import androidx.compose.runtime.*

@Composable
fun CartScreen() {
    var total by remember { mutableStateOf(0) }
    Header()
    CartList(total = total)
}

@Composable
fun CartList(total: Int) {
    CartRow(total = total)
}

@Composable
fun CartRow(total: Int) {
    Text("Toplam: $total")
}
```

`CartScreen` değeri kendisi okur ve `CartList`'e geçirir; `CartList` kullanmadan `CartRow`'a iletir. `Header` değişmeyen girdiyle çağrılır. Bu parça platform kodudur ve burada çıktı, düzen, yeniden oluşturma sayısı ya da süre iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Model yeniden oluşturmayı üç işleviyle kurar. `scopeChain`, iki düğüm arasındaki yolu verir. `recomposedScopes`, okumanın yapıldığı düğümden değeri kullanan düğüme kadar olan yolu kapsam sayar. `skippedScopes`, kapsamın çocuğu olup kapsamda olmayan düğümleri atlanmış sayar. Ayrıca iki kanalın hareketlerini `valueMoves` ve `deferredMoves` kurar; ikincisini bu dersin ilerisinde kullanacağız.

```kotlin
// Channels.kt — degisikligin sahibinden okuyucuya gittigi iki yol
fun scopeChain(from: String, to: String, tree: Tree): List<String> =
    ((listOf(to) + tree.ancestors(to)).takeWhile { it != from } + from).reversed()

fun recomposedScopes(readAt: String, consumer: String, tree: Tree): List<String> =
    scopeChain(readAt, consumer, tree)

fun skippedScopes(recomposed: List<String>, nodes: List<String>, tree: Tree): List<String> =
    nodes.filter { it !in recomposed && tree.parent(it) in recomposed }

fun valueMoves(owner: String, consumer: String, tree: Tree): List<Move> =
    scopeChain(owner, consumer, tree).zipWithNext()
        .map { (from, to) -> Move("degisim-${to.lowercase()}", from, to, tree) }

fun deferredMoves(owner: String, consumer: String, tree: Tree): List<Move> =
    listOf(Move("degisim-${consumer.lowercase()}", owner, consumer, tree))
```

```kotlin
// Main.kt — okuma sahipte: kapsam yoldaki uc dugumdur, Header atlanir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val nodes = listOf("App", "Screen", "Header", "CartList", "CartRow")
    val recomposed = recomposedScopes("Screen", "CartRow", tree)
    println("yeniden olusturulan: ${recomposed.sorted()}")
    println("atlanan: ${skippedScopes(recomposed, nodes, tree).sorted()}")
}
```

```text
yeniden olusturulan: [CartList, CartRow, Screen]
atlanan: [Header]
```

Okuma `Screen`'de yapıldığı için kapsam `Screen`'den başlar ve değerin indiği yol boyunca `CartList` ve `CartRow`'u içerir. `Header` kapsamın çocuğudur ama girdisi değişmemiştir: atlanır. `App` hiç etkilenmez. Bu modelin atlama için girdilerin kararlı ve eşit olduğunu varsaydığını not edelim; kararlılık sonraki dersin konusudur.

**BI7.** Model kapsamı, okuma düğümünden değeri kullanan düğüme kadar olan yol olarak kurar. Platformda satır içi işlevlerin kapsam sayılmaması, kapsamın bileşen işlevi sınırlarına göre belirlenmesi ve yeniden oluşturmanın kaç kez çalıştığı ölçülmedi.

#### Parametre Kanalı: İki Aşağı

Aynı değişikliği hareket olarak ölçelim. Okuma `Screen`'de kaldığında yeni değer `CartList`'e parametreyle geçer, `CartList` onu `CartRow`'a geçirir. Her geçiş ebeveynden doğrudan çocuğadır.

```kotlin
// Main.kt — parametre kanali: her duzeyde bir asagi hareket
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val moves = valueMoves("Screen", "CartRow", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
degisim-cartlist: kimden=Screen | kime=CartList -> asagi
degisim-cartrow: kimden=CartList | kime=CartRow -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 0
```

İki aşağı hareket var. Aradaki `CartList` değeri kullanmıyor, ama yeni değer ona geldiği için yeniden oluşturuluyor. Hareket sayısını ve kapsamı okumanın yeri belirler.

#### Okumayı Ertelemek: Tek Derin

Platform belgesi okumanın ertelenmesini bir performans yöntemi olarak anlatır. Durum yukarıda tutuluyorsa ve bir çocukta okunuyorsa, okuma bir geri çağrıya sarılabilir; böylece okuma yalnız gerçekten gerektiğinde ve gerektiği yerde yapılır. Belgeye göre değer yalnız çocuk içinde okunduğunda, değişince en yakın kapsam çocuğun kendisi olur.

```kotlin
// Platform kodu: DeferredChannel
import androidx.compose.runtime.*

@Composable
fun CartScreen() {
    var total by remember { mutableStateOf(0) }
    Header()
    CartList(total = { total })
}

@Composable
fun CartList(total: () -> Int) {
    CartRow(total = total)
}

@Composable
fun CartRow(total: () -> Int) {
    Text("Toplam: ${total()}")
}
```

`CartScreen` artık değeri okumaz; değeri okuyan bir geri çağrıyı aşağı verir. Okuma `CartRow` içinde, `total()` çağrıldığında yapılır. Geri çağrının kendisi ilk oluşturmada her düzeyden parametreyle geçer; ama değişen şey geri çağrı değil, durumun değeridir ve bu değişikliğin haberi doğrudan okuyanın kapsamına ulaşır. Modelde değişiklik hareketi sahibinden okuyucuya tek adımda gider.

```kotlin
// Main.kt — okuma ertelendi: kapsam tek dugum, degisiklik tek derin hareket
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val nodes = listOf("App", "Screen", "Header", "CartList", "CartRow")
    val recomposed = recomposedScopes("CartRow", "CartRow", tree)
    println("yeniden olusturulan: ${recomposed.sorted()}")
    println("atlanan: ${skippedScopes(recomposed, nodes, tree).sorted()}")
    val moves = deferredMoves("Screen", "CartRow", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
yeniden olusturulan: [CartRow]
atlanan: []
degisim-cartrow: kimden=Screen | kime=CartRow -> derin
asagi: 0
yukari: 0
derin: 1
dis: 0
```

Kapsam tek düğümdür. `Screen` ile `CartList` yeniden oluşturulmaz, çünkü ikisi de değeri okumaz. Değişiklik `Screen`'den `CartRow`'a tek hareketle, aradaki `CartList`'e uğramadan ulaşır: derin. Dersin yönü budur.

Bu ders M30/K03'ten farklıdır. O kursun "bu yeniden hesaplamayı ne tetikledi" sorusu bir görünümün neden yeniden hesaplandığını okuyanın gözünden sınıflar. Burada neden değil, değişikliğin sahibinden okuyucusuna giden yol ölçülür.

**BI8.** Ertelenmiş kanalda değişikliğin doğrudan okuyanın kapsamına gitmesi platformun gözlem kuralıdır ve modelde böyle kurulmuştur. Gözlemin nasıl çalıştığı, yani okumanın kapsamla nasıl ilişkilendirildiği modelde yoktur.

#### Sınırlayıcı Ölçüm

Aynı veri, aynı sahip, aynı okuyucu, iki kanal. Kural kanalı görmez; yalnız iki ucun konumuna bakar. Ama yönün farkı kanaldan gelir ve kapsamın boyutu da.

```kotlin
// Main.kt — ayni veri, iki kanal, iki yon
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val byValue = valueMoves("Screen", "CartRow", tree)
    val deferred = deferredMoves("Screen", "CartRow", tree)
    println("parametre: kapsam=${recomposedScopes("Screen", "CartRow", tree).size} yonler=${byValue.map { it.direction }}")
    println("ertelenmis: kapsam=${recomposedScopes("CartRow", "CartRow", tree).size} yonler=${deferred.map { it.direction }}")
}
```

```text
parametre: kapsam=3 yonler=[asagi, asagi]
ertelenmis: kapsam=1 yonler=[derin]
```

Parametre kanalı iki aşağı hareket ve üç düğümlük kapsam kurar. Ertelenmiş kanal tek derin hareket ve tek düğümlük kapsam kurar. Derin, "daha iyi" demek değildir: ertelenmiş okumada `CartRow`'un içeriği geri çağrıyla okunur ve bunun bedeli kodun okunurluğudur. Ders bu bedeli ölçmez. Ölçtüğü şey, aynı değişikliğin iki kanalda iki farklı yönle ve iki farklı kapsamla `CartRow`'a ulaşmasıdır.

#### Özet

- Bu dersin yönü: derin. Okuma en alttaki düğüme ertelenince değişiklik sahibinden okuyucuya tek hareketle ulaşır.
- Okuma sahipte kalınca yeniden oluşturulan düğümler `CartList`, `CartRow` ve `Screen` olur; `Header` atlanır.
- Parametre kanalında değişiklik her düzeyden iner: aşağı 2, derin 0.
- Ertelenmiş kanalda aynı değişiklik tek hareketle gider: aşağı 0, derin 1.
- İki kanal aynı iki ucu bağlar; kapsam 3 düğümden 1 düğüme iner. Kural kanalı görmez, yön farkı kanaldan gelir.

#### Sonraki Adım

Bu derste girdisi değişmeyen bileşen işlevlerinin atlandığını varsaydık. Hangi girdinin değişmemiş sayılacağı ayrı bir sorudur ve 06. dersin konusudur. Ondan önce `Yan Etkiler` dersi, yeniden oluşturmanın dışına, ağaç dışında iz bırakan etkilere bakar: hareketin ucu ağaçta olmadığında yön ne olur?

### Yan Etkiler

Source: https://academia.sh/tr/kurslar/compose/yan-etkiler

Bir bileşen işlevi ağacı kurarken ağacın dışına da dokunabilir: servise istek atar, kayıt tutar, bir dinleyiciye abone olur. Ders bu hareketleri ölçer ve hepsinin aynı yöne, dışa gittiğini gösterir.

Şimdiye kadar veri ağacın içinde dolaştı: aşağı indi, yukarı çıktı, araya girenleri atlayıp derine gitti. Oysa bir bileşen işlevinin yaptığı her şey ağaca düğüm eklemek değildir. Bir bileşen işlevi bir servise istek atabilir, bir kayıt satırı yazabilir, platformun bir dinleyicisine abone olabilir. Bunlar yan etkidir: işlevin ağaç dışında bıraktığı iz. Dersin sorusu yine aynıdır: **bu veri arayüz ağacında hangi yöne gidiyor?** Yan etkinin yanıtı hep aynıdır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Ağacı Terk Eden Hareket

Platform belgesi yan etkiyi bileşen işlevinin kapsamı dışında uygulamanın durumunu değiştiren olay olarak anlatır ve bileşen işlevlerinin yeniden oluşturmayla her an yeniden çalışabildiğini hatırlatır. Bu yüzden etkinin işlevin gövdesine doğrudan yazılmaması, belgenin sunduğu etki işlevlerinden biriyle sarılması gerekir. Belge `LaunchedEffect` için şunu söyler: "When `LaunchedEffect` enters the Composition, it launches a coroutine with the block of code passed as a parameter." Anahtar değişirse mevcut eş yordam iptal edilir ve yenisi başlatılır.

```kotlin
// Platform kodu: LaunchedEffect
import androidx.compose.runtime.*

@Composable
fun CartRow(productId: String) {
    LaunchedEffect(productId) {
        service.loadPrice(productId)
    }
    Text("Fiyat yükleniyor")
}
```

`productId` aynı kaldıkça yeniden oluşturma etkiyi yeniden başlatmaz. Değişirse önceki iş iptal edilir, yeni anahtarla yeni iş başlar. Bu parça platform kodudur ve burada çıktı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Modelde etkiyi sahibi düğüm, hedefi ve anahtarı tanımlar. Hedef ağaçta olmayan bir ad olduğu için etkiden çıkan her hareket dıştır.

```kotlin
// Effects.kt — anahtar degisince yeniden baslayan etki
class Effect(val owner: String, val target: String, private var key: String) {
    fun keyChanged(newKey: String): Boolean {
        val changed = newKey != key
        key = newKey
        return changed
    }
}

fun runIfKeyChanged(effect: Effect, newKey: String, tree: Tree): List<Move> =
    if (effect.keyChanged(newKey)) {
        listOf(Move("istek-$newKey", effect.owner, effect.target, tree))
    } else {
        emptyList()
    }

fun contactMove(label: String, effect: Effect, tree: Tree): Move =
    Move(label, effect.owner, effect.target, tree)

fun replyMove(label: String, effect: Effect, tree: Tree): Move =
    Move(label, effect.target, effect.owner, tree)

fun writeToOwner(label: String, effect: Effect, tree: Tree): Move =
    Move(label, effect.owner, tree.ancestors(effect.owner).dropLast(1).last(), tree)

fun writeToParent(label: String, effect: Effect, tree: Tree): Move =
    Move(label, effect.owner, tree.ancestors(effect.owner).first(), tree)
```

Bu yardımcılar hareketlerin uçlarını `Effect` alanlarından ve ağaçtan türetir. `contactMove` sahipten hedefe, `replyMove` hedeften sahibe gider. `writeToOwner` durumu tutan ekranı ağaçtan seçer, `writeToParent` en yakın ebeveyni seçer. Bu işlevler hareketin uçlarını modelin `Effect` kaydından okur; dış uç, ağaçta olmayan ve modelin kaydında tanımlanan bir addır. Kayıtlardaki sahip ve hedef adları `Effect(...)` satırında yazılır, ama hareket bu adlarla elle kurulmaz.

```kotlin
// Main.kt — dort yeniden olusturma, iki anahtar, iki istek
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val effect = Effect("CartRow", "Service", "")
    val keys = listOf("k1", "k1", "k2", "k2")
    val moves = keys.flatMap { runIfKeyChanged(effect, it, tree) }
    println("yeniden olusturma: ${keys.size} | calisan etki: ${moves.size}")
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
yeniden olusturma: 4 | calisan etki: 2
istek-k1: kimden=CartRow | kime=Service -> dis
istek-k2: kimden=CartRow | kime=Service -> dis
asagi: 0
yukari: 0
derin: 0
dis: 2
```

Dört yeniden oluşturmada anahtar yalnız iki kez değişti: ilk anahtar ve `k2`. Etki iki kez çalıştı, iki hareket çıktı ve ikisi de dış. `Service` ağaçta olmadığı için kural başka bir şeye bakmaz.

**BI9.** Model anahtarın farklılaşmasını metin eşitliğiyle kurar. Platformda anahtarın `equals` ile karşılaştırılması, eş yordamın iptal edilme anı ve ilk çalışmanın zamanlaması ölçülmedi.

#### Abonelik ve Temizlik

Bazı etkiler bir şeye bağlanır ve bağlandıkları şeyden ayrılmaları gerekir. Belge `DisposableEffect` için şunu der: bir `DisposableEffect` bloğunun son ifadesi `onDispose` olmak zorundadır; aksi hâlde derleme sırasında hata verilir. Anahtar değişirse ya da bileşen işlevi ağaçtan çıkarsa platform önce `onDispose`'u çalıştırır.

```kotlin
// Platform kodu: DisposableEffect
import androidx.compose.runtime.*

@Composable
fun CartScreen(logger: Logger) {
    DisposableEffect(logger) {
        logger.subscribe()
        onDispose { logger.unsubscribe() }
    }
}
```

Abone olmak da, abonelikten çıkmak da ağacın dışına gider. Aynı hedefe giden iki hareket de dışa gider; biri gidip biri dönmez.

```kotlin
// Main.kt — abone ol ve temizle: iki hareket, ikisi de dis
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
    ))
    val effect = Effect("Screen", "Log", "")
    val moves = listOf(
        contactMove("abone-ol", effect, tree),
        contactMove("abonelikten-cik", effect, tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
abone-ol: kimden=Screen | kime=Log -> dis
abonelikten-cik: kimden=Screen | kime=Log -> dis
asagi: 0
yukari: 0
derin: 0
dis: 2
```

Temizlik bir geri dönüş gibi görünse de yön geri dönmek değildir. Kayıt ağaçta olmadığı için "kimden `Screen`, kime `Log`" dışa gider; kuralın çözebileceği başka bir yol yoktur.

Üçüncü ve dördüncü etki işlevleri farklı bir şey yapar. `SideEffect`, belgeye göre her başarılı yeniden oluşturmadan sonra çalışır ve platform durumunu platform dışı koda yayınlamak içindir. `rememberUpdatedState` ise etkinin yeniden başlamasını istemediğiniz bir değeri güncel tutar.

```kotlin
// Platform kodu: SideEffect ve rememberUpdatedState
import androidx.compose.runtime.*

@Composable
fun CartRow(onTimeout: () -> Unit, user: User) {
    val currentOnTimeout by rememberUpdatedState(onTimeout)
    LaunchedEffect(true) {
        delay(3000)
        currentOnTimeout()
    }
    SideEffect {
        analytics.setUser(user.id)
    }
}
```

`LaunchedEffect(true)` anahtarı hiç değişmediği için etki yeniden başlamaz; `rememberUpdatedState` sayesinde geri çağrı yine en güncel değeri taşır. `SideEffect` ise ağacın dışındaki `analytics` nesnesine her başarılı yeniden oluşturmada yazar. İkisi de dışa giden hareketlerdir.

#### Yanıt Ağaca Nasıl Döner

Yan etkinin istediği iş bitince bir yanıt gelir ve yanıt ağaca dönmek zorundadır, yoksa ekranda hiçbir şey değişmez. Bu dönüş doğrudan arayüz düğümüne yazılmaz. Etki yanıtı durum yazısıyla durumun sahibine verir. Üç hareket var: istek, yanıt, durum yazısı.

```kotlin
// Main.kt — istek ve yanit dista, durum yazisi agacin icinde
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val effect = Effect("CartRow", "Service", "")
    val moves = listOf(
        contactMove("istek", effect, tree),
        replyMove("yanit", effect, tree),
        writeToOwner("durum-yazisi", effect, tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
istek: kimden=CartRow | kime=Service -> dis
yanit: kimden=Service | kime=CartRow -> dis
durum-yazisi: kimden=CartRow | kime=Screen -> yukari
asagi: 0
yukari: 1
derin: 0
dis: 2
```

İstek ve yanıt aynı yöne, dışa gider. Hangisinin hangisi olduğunu yönden değil `kimden` ve `kime` ayırır: istekte kaynak ağaçtadır, yanıtta ağaçta değildir. Dersin yönü budur: dış. Üçüncü hareket ise ağaç içindedir. Etki yanıtı `CartRow`'dan sahibe yazar ve bu bir yukarıdır; Ders 03'ün olay yolu buraya bağlanır.

Bu ders M12/K02'den farklıdır. O kurs bir istekle ağın hangi uçlara ulaştığını ve yayılımı sınıflar. Burada sorulan, yayılım değil, hareketin ağacın içinde mi dışında mı bittiğidir.

**BI10.** Servis yanıtının durum yazısıyla ağaca döndüğü, modelin kurduğu bir kuraldır. Eş yordamın hangi iş parçacığında çalıştığı, iptalin zamanlaması ve yanıtın birden çok kez gelmesi modelde yoktur.

#### Sınırlayıcı Ölçüm

İki sahip, aynı ağ ve aynı hareket sayısı. Birinde etki, ağaçtaki bir düğümden dışarıdaki bir hedefe iki hareket çıkarır. Diğerinde etki ağaç içinde kalan bir yazı yapar.

```kotlin
// Main.kt — etki disa gider, yazi icerde kalir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
        "CartRow" to "CartList",
    ))
    val service = Effect("CartRow", "Service", "")
    val log = Effect("CartRow", "Log", "")
    val outside = listOf(
        contactMove("istek", service, tree),
        contactMove("kayit", log, tree),
    )
    val inside = listOf(
        writeToOwner("yazi-1", service, tree),
        writeToParent("yazi-2", service, tree),
    )
    println("etki: ${outside.map { it.direction }}")
    println("yazi: ${inside.map { it.direction }}")
}
```

```text
etki: [dis, dis]
yazi: [yukari, yukari]
```

Aynı kaynak, iki hedef ailesi, iki yön. Fark, hedefin ağaçta olup olmamasıdır; etkinin hangi etki işleviyle sarıldığı yönü değiştirmez.

#### Özet

- Bu dersin yönü: dış. Etkiden çıkan her hareket ağacın dışındaki bir hedefe gider.
- Dört yeniden oluşturmada anahtar iki kez değiştiği için etki 2 kez çalıştı ve ikisi de dış çıktı.
- Abone olmak ve temizlemek iki ayrı hareket olarak ölçüldü: dış 2.
- Servis yanıtı ağaca durum yazısıyla döner: dış 2, yukarı 1.
- Ağaçta kalan iki yazı yukarı, ağaçtan çıkan iki etki dış ölçüldü; etki işlevinin türü yönü değiştirmez.

#### Sonraki Adım

Etkiyi yeniden başlatan şey anahtarın değişmesiydi. Bir değerin değişip değişmediğini platformun nasıl anladığı, yeniden oluşturmada atlamanın temelidir. Sıradaki ders, `Değişmezlik ve Kararlılık`, bu soruyu ve onun sahibe yönünü ölçer.

### Değişmezlik ve Kararlılık

Source: https://academia.sh/tr/kurslar/compose/degismezlik-ve-kararlilik

Platform bir değerin değişip değişmediğini bilirse yeniden oluşturmada atlama yapabilir. Ders yerinde değiştirilen listeyle yeni liste üreten olayın aynı yöne, yukarı gittiğini ama yalnız birinin gözlendiğini ölçer.

Önceki ders, girdisi değişmeyen bileşen işlevinin atlandığını varsaymıştı. Bu ders o varsayımı açar: platform bir değerin değişip değişmediğini nasıl bilir? Cevap hareketin yönünü değiştirmez ama hareketin görülüp görülmediğini değiştirir. Dersin sorusu yine aynıdır: **bu veri arayüz ağacında hangi yöne gidiyor?** İki ayrı biçimde yukarı gidebilir ve yalnız birinde platform bunu fark eder.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Platform Neyi Bilir

Platform belgesine göre bir tür, değişmezse ya da değerinin yeniden oluşturmalar arasında değişip değişmediği platform tarafından bilinebiliyorsa kararlıdır. Platformun değeri bilemediği tür kararsızdır. Belgenin örneği: özellikleri `val` olan bir veri sınıfı değişmezdir; özellikleri `var` olan sürümde özellikleri değişse platform bunu fark etmezdi, çünkü platform yalnız kendi durum nesnelerindeki değişiklikleri izler.

Liste arayüzleri için belge şunu der: standart koleksiyon arayüzleri (`List`, `Set`, `Map`) kararsız sayılır, çünkü altındaki gerçekleme değiştirilebilir olabilir. Güçlü atlama kipi, belgeye göre Kotlin 2.0.20 ve üzerinde varsayılan olarak açıktır. Kip açıkken kararsız parametreler referans eşitliğiyle (`===`) karşılaştırılır ve aynı nesne geçilirse bileşen işlevi atlanır.

```kotlin
// Platform kodu: MutableList ve data class
import androidx.compose.runtime.*

class CartState(val items: MutableList<String>)

@Composable
fun CartRow(state: CartState) {
    Button(onClick = { state.items.add("c") }) {
        Text("Ekle: ${state.items.size}")
    }
}

data class CartItems(val items: List<String>)

@Composable
fun CartList(cart: CartItems, onAdd: (CartItems) -> Unit) {
    Button(onClick = { onAdd(cart.copy(items = cart.items + "c")) }) {
        Text("Ekle: ${cart.items.size}")
    }
}
```

İlk bileşen işlevi listeyi olduğu yerde değiştirir: `state` ve onun listesi aynı nesne kalır. İkincisi yeni bir `CartItems` üretir ve değişikliği sahibine olay olarak verir. Bu parça platform kodudur ve burada çıktı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Model "gözlenen"i basit bir kuralla kurar: değişiklikten önceki ve sonraki liste aynı nesneyse platform fark edemez; farklı nesneyse eder. Bir `Edit`, değişikliği yapanı, listenin sahibini ve önceki ile sonraki listeyi taşır.

```kotlin
// Edits.kt — bir degisikligin yonu ve gozlenip gozlenmedigi
class Edit(
    val label: String,
    val by: String,
    val owner: String,
    before: List<String>,
    val after: List<String>,
    tree: Tree,
) {
    val move = Move(label, by, owner, tree)
    val observed: Boolean = before !== after

    fun report() {
        move.describe()
        println("  gozlenen=$observed | icerik=$after")
    }
}
```

**BI11.** Model gözlenmeyi referans farkıyla kurar. Platformun kararlılığı hangi türlerde nasıl çıkardığı, derleyici ayarının etkisi ve eşitlik karşılaştırmasının maliyeti ölçülmedi.

#### Yerinde Değiştirmek

Çocuk, sahibin listesini olduğu yerde değiştirir. Veri çocuktan sahibe gider; sahip `Screen`, değiştiren `CartRow`.

```kotlin
// Main.kt — yerinde degistirme: yon yukari, gozlenen degil
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartRow" to "Screen",
    ))
    val list = mutableListOf("a", "b")
    val before: List<String> = list
    list.add("c")
    val edit = Edit("degistir", "CartRow", "Screen", before, list, tree)
    edit.report()
}
```

```text
degistir: kimden=CartRow | kime=Screen -> yukari
  gozlenen=false | icerik=[a, b, c]
```

Hareketin yönü yukarıdır, çünkü hedef kaynağın atasıdır. Ama liste aynı nesne kaldığı için `gozlenen=false`: içerik değişti, platform değişikliği öğrenmedi. Ekran eski içerikle kalabilir.

#### Olayla Yeni Liste

Çocuk listeyi değiştirmez; sahibine olay verir, sahip yeni liste üretir.

```kotlin
// Main.kt — olayla yeni liste: yon yukari, gozlenen
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartRow" to "Screen",
    ))
    val before = listOf("a", "b")
    val after = before + "c"
    val edit = Edit("olay", "CartRow", "Screen", before, after, tree)
    edit.report()
}
```

```text
olay: kimden=CartRow | kime=Screen -> yukari
  gozlenen=true | icerik=[a, b, c]
```

Yön aynıdır: yukarı. Liste yeni nesne olduğu için `gozlenen=true`. Dersin yönü budur: yukarı.

Olayın hikâyesi burada bitmez. Sahip yeni listeyi bildiğinde onu aşağı, okuyucuya verir.

```kotlin
// Main.kt — tam akis: bir yukari, bir asagi
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartRow" to "Screen",
    ))
    val moves = listOf(
        Move("olay", "CartRow", "Screen", tree),
        Move("yeni-liste", "Screen", "CartRow", tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
olay: kimden=CartRow | kime=Screen -> yukari
yeni-liste: kimden=Screen | kime=CartRow -> asagi
asagi: 1
yukari: 1
derin: 0
dis: 0
```

Yerinde değiştirmede yalnız bir hareket vardır ve geri dönüş yoktur. Olayla yeni liste ise iki hareketten oluşur: olay yukarı, yeni liste aşağı. Ders 03'teki akışın aynısıdır. Yukarıdaki iki satır kuralı göstermek için yazılmıştır, ölçüm değildir: ikisinde de uçlar elle yazılmıştır.

Bu ders M31/K01'den farklıdır. O kurs iki değerin eşit sayılıp sayılmayacağını kurdu. Burada eşitlik ölçülmez; ölçülen, hareketin yönü ve değişikliğin görülüp görülmediğidir.

**BI12.** Sahibin yeni listeyi aşağı vermesi modelde bir hareket olarak kurulur. Platformda yeni listenin hangi kapsamlarda yeniden oluşturmaya yol açtığı ölçülmedi.

#### Sınırlayıcı Ölçüm

Aynı değiştiren, aynı sahip, aynı içerik. İki değişiklik yöntemi yan yana.

```kotlin
// Main.kt — ayni yon, farkli gozlenme
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartRow" to "Screen",
    ))
    val inPlace = mutableListOf("a", "b")
    val beforeInPlace: List<String> = inPlace
    inPlace.add("c")
    val copied = listOf("a", "b")
    val a = Edit("yerinde", "CartRow", "Screen", beforeInPlace, inPlace, tree)
    val b = Edit("kopya", "CartRow", "Screen", copied, copied + "c", tree)
    println("yerinde: yon=${a.move.direction} gozlenen=${a.observed}")
    println("kopya: yon=${b.move.direction} gozlenen=${b.observed}")
}
```

```text
yerinde: yon=yukari gozlenen=false
kopya: yon=yukari gozlenen=true
```

İki değişiklik aynı yöne gider. Fark yönde değil, gözlenmededir. Kural yöne baktığı için ikisine aynı cevabı verir; görülüp görülmediği ise listenin nesne olarak değişmesine bağlıdır.

#### Özet

- Bu dersin yönü: yukarı. Çocuğun sahibin listesini değiştirmesi de, sahibe olay vermesi de yukarı gider.
- Yerinde değiştirmede içerik değişti ama gözlenen değil: yukarı 1, gözlenen false.
- Olayla yeni liste üretmede aynı yön gözlenen true ile ölçüldü.
- Olayla yeni liste iki hareketle gösterilir: olay yukarı, yeni liste aşağı. Bu gösterim bir ölçüm değildir; uçları elle yazılmıştır.
- Aynı yönün iki yöntemi yan yana ölçüldü: ikisi de yukarı, yalnız kopya gözlenen.

#### Sonraki Adım

Bu ders bileşen işlevi modelini kapatır: bileşen işlevi, durum, durumu kaldırma, yeniden oluşturma, yan etkiler ve kararlılık. Sıradaki konu, `Temel Düzenler`, bu modelin üstüne ekrandaki düzeni kurar.

## Düzen ve Arayüz Bileşenleri

Sütun, satır ve kutu düzenleri, sıraya bağlı değiştiriciler, kısıt tabanlı düzen, tembel listeler, hazır arayüz bileşenleri, iskele yapısı, gezinme ve rotalar, tema ve tipografi, animasyonlar, erişilebilirlik ve klasik görünüm sistemiyle birlikte kullanım.

### Temel Düzenler

Source: https://academia.sh/tr/kurslar/compose/temel-duzenler

Sütun, satır ve kutu düzenlerinde kısıtların, ölçülerin ve konumların arayüz ağacında hangi yöne gittiğinin ölçümü.

Önceki derste bir değişikliğin sahibine sessizce çıkabildiği ölçüldü. Bu ders, aynı ağaçta ters yöndeki bir veriye bakar: ebeveynin çocuğa verdiği sınır. Bir sütun düzeni kendisine verilen genişliği çocuklarına iletir; çocuklar bu genişliğin içinde kalarak kendi yüksekliklerini bildirir; sütun da bu yüksekliklerden her çocuğun konumunu çıkarır. Ölçü ekseni kurs boyunca aynıdır: bu veri arayüz ağacında hangi yöne gidiyor? Dört yön vardır. Bir veri ebeveynden doğrudan çocuğuna giderse **aşağı**, çocuktan ebeveyn zincirinin herhangi bir atasına giderse **yukarı**, bir atadan ebeveyn atlayarak torununa giderse **derin** gider; uçlardan biri ağaçta değilse **dış** olur.

Bu dersin sorusu düzen geçişinin üç verisidir: kısıt, ölçü ve konum. Üçü de aynı iki düğüm arasında dolaşır, ama aynı yöne gitmezler. Hangisinin yönü dersin yönünü belirler, bunu son bölümde seçeceğiz. Önce ölçüyü yapan çekirdek:

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Sütun, Satır ve Kutu

Üç temel düzen işlevi de bir bileşen işlevidir (`@Composable`) ve çocuklarını bir lambda içinde alır. `Column` çocuklarını üstten alta, `Row` başlangıçtan bitişe dizer (soldan sağa yazılan dilde soldan sağa); `Box` çocuklarını üst üste koyar. Dizilimin gittiği eksene ana eksen, ona dik eksene çapraz eksen denir. Ana eksendeki boşluk dağılımı `Arrangement` ile, çapraz eksendeki konum `Alignment` ile verilir.

```kotlin
// Platform kodu: Column, Row ve Box
@Composable
fun Summary() {
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Text("Title")
        Row(horizontalArrangement = Arrangement.SpaceBetween) {
            Text("Name")
            Text("Total")
        }
        Box(contentAlignment = Alignment.BottomEnd) {
            Text("Note")
        }
    }
}
```

Bu parça üç ağaç ilişkisi kurar. `Column`, `Text`, `Row` ve `Box` işlevlerinin her biri arayüz ağacında bir düğümdür; `Text("Title")` ile `Row` ve `Box`, `Column`'un çocuklarıdır; `Row`'un iki `Text`'i onun çocuğudur. Sıra, kaynakta yazıldığı sıradır. Hizalama ve boşluk parametreleri düğümün çocuklarına dair kuralı taşır; çocuğun kendisine dair bir şey söylemez.

#### Düzenin Tek Geçişi

Platform bir düzen düğümünü üç adımda işler. Önce ebeveyn, çocuğa kısıtları verir: çocuğun alabileceği en küçük ve en büyük genişlik ile yükseklik. Sonra çocuk bu aralığın içinde kalan bir boyutu seçip bildirir; buna ölçme denir. Son olarak ebeveyn, bildirilen boyutlara bakıp her çocuğun konumunu belirler; buna yerleştirme denir. Bu üç adım tek geçişte olur: ebeveyn bir çocuğa kısıtı verir, çocuk bir kez ölçülür ve bir kez yerleştirilir.

Model, platformun bu kuralını kurar. Model platformun kendisi değildir; yardımcı dosya yalnız sütun kuralını taşır. Girdi, ebeveynin genişliği, çocukların `(ad, genişlik, yükseklik)` üçlüleri ve çapraz eksendeki hizalamadır. Çıktı, her çocuğun `x` ve `y` konumudur. `y`, öncekilerin yüksekliklerinin toplamıdır; `x` hizalamaya bağlıdır. Buradaki sayılar modelin hesabıdır, platformun ölçtüğü piksel değildir.

```kotlin
// Layout.kt — sutun duzeninin tek gecisi (modelin hesabi)
class Placed(val node: String, val x: Int, val y: Int)

fun placeColumn(parentWidth: Int, items: List<Triple<String, Int, Int>>, alignment: String): List<Placed> {
    var y = 0
    return items.map { (node, width, height) ->
        val x = when (alignment) {
            "center" -> (parentWidth - width) / 2
            "end" -> parentWidth - width
            else -> 0
        }
        val placed = Placed(node, x, y)
        y += height
        placed
    }
}
```

İlk ölçüm tek çocuğun üç verisidir. Ağaç `App`'in altında `Screen`, onun altında `Column` ve `Column`'un altında iki çocuk kurar. `Title` ilk çocuktur. Kısıt `Column`'dan `Title`'a verilir. Ölçü `Title`'dan `Column`'a döner. Konum yine `Column`'dan `Title`'a gider; hareketin hedefi, modelin yerleştirdiği düğümün adıdır.

```kotlin
// Main.kt — tek cocugun kisiti, olcusu ve konumu
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
    val placed = placeColumn(360, listOf(Triple("Title", 200, 48), Triple("Body", 300, 120)), "start")
    val first = placed[0]
    val moves = listOf(
        Move("kisit-baslik", "Column", "Title", tree),
        Move("olcu-baslik", first.node, "Column", tree),
        Move("konum-baslik", "Column", first.node, tree),
    )
    for (move in moves) {
        move.describe()
    }
    println("${first.node}: x=${first.x} y=${first.y}")
    countDirections(moves)
}
```

```text
kisit-baslik: kimden=Column | kime=Title -> asagi
olcu-baslik: kimden=Title | kime=Column -> yukari
konum-baslik: kimden=Column | kime=Title -> asagi
Title: x=0 y=0
asagi: 2
yukari: 1
derin: 0
dis: 0
```

`kisit-baslik` satırının iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan `olcu-baslik` ve `konum-baslik` satırlarının bir ucu modelin yerleştirdiği düğümden gelir.

Kısıt ve konum aşağı gitti; ölçü yukarı. `Title`'ın konumu `x=0`, `y=0`: sütunun en üstündeki çocuk, hizalama başta olduğu için sol üst köşeye oturur. Aşağı giden iki hareket farklı veri taşır. Kısıt, çocuğun neyi seçebileceğini söyler; konum, seçilen boyutun nereye konacağını. İkisi de ebeveynin elindedir, çünkü çocuk kendi konumunu bilmez.

**AD1.** Model kısıtı tek bir sayıya, ebeveynin genişliğine indirir. Platformda kısıt, en küçük ve en büyük genişlik ile yükseklik olmak üzere dört sınırdır ve yükseklik kısıtının sınırsız olduğu durum ölçülmedi.

#### Sıra Konumu Değiştirir, Yönü Değil

`Column` çocukları kaynakta yazıldığı sırayla dizer. Aynı iki çocuk ters sırayla verilirse konumlar yer değiştirir. Aşağıdaki ölçüm iki çocuğu iki sırada yerleştirir ve her konum hareketini ağaçta sınıflandırır.

```kotlin
// Main.kt — iki sirada konum hareketleri
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
    val items = listOf(Triple("Title", 200, 48), Triple("Body", 300, 120))
    val forward = placeColumn(360, items, "start")
    val backward = placeColumn(360, items.reversed(), "start")
    val moves = mutableListOf<Move>()
    for ((name, order) in listOf("duz" to forward, "ters" to backward)) {
        for (p in order) {
            moves.add(Move("konum-$name-${p.node.lowercase()}", "Column", p.node, tree))
            println("$name ${p.node}: y=${p.y}")
        }
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
duz Title: y=0
duz Body: y=48
ters Body: y=0
ters Title: y=120
konum-duz-title: kimden=Column | kime=Title -> asagi
konum-duz-body: kimden=Column | kime=Body -> asagi
konum-ters-body: kimden=Column | kime=Body -> asagi
konum-ters-title: kimden=Column | kime=Title -> asagi
asagi: 4
yukari: 0
derin: 0
dis: 0
```

`Title` düz sırada `y=0`, ters sırada `y=120`; `Body` ise `y=48` ile `y=0` arasında yer değiştirdi. Dört konum hareketi de aynı yöne gitti: aşağı. Sıra, hareketin ucundaki değeri değiştirir; hareketin hangi iki düğüm arasında olduğunu değiştirmez. Bu yüzden sırayı değiştirmek bir düzen sonucunu değiştirir, veri akışını değiştirmez.

**AD2.** Model çocuklar arasında boşluk bırakmaz; `Arrangement` değerleri ve `spacedBy` gibi ana eksen boşlukları `y` hesabına girmedi.

#### Hizalama Yalnız Konumdur

`Column`'da `horizontalAlignment`, çapraz eksendeki konumu seçer; `Row`'da `verticalAlignment` aynı işi dikey eksende yapar. Model sütunun çapraz eksenini `x` olarak kurduğu için aynı ölçüm `Row`'un çapraz ekseni için de geçerlidir: eksenler yer değiştirir, ilişki aynı kalır. Hizalama hesabı çocuğun genişliği ve ebeveynin genişliği arasındaki farkı kullanır. Bu fark ebeveynde yerinde hesaplanır; çocuğa gönderilen şey hesabın sonucudur.

```kotlin
// Main.kt — uc hizalamanin konumu
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column"))
    val moves = mutableListOf<Move>()
    for (alignment in listOf("start", "center", "end")) {
        val title = placeColumn(360, listOf(Triple("Title", 200, 48)), alignment)[0]
        println("$alignment: x=${title.x}")
        moves.add(Move("konum-$alignment", "Column", title.node, tree))
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
start: x=0
center: x=80
end: x=160
konum-start: kimden=Column | kime=Title -> asagi
konum-center: kimden=Column | kime=Title -> asagi
konum-end: kimden=Column | kime=Title -> asagi
asagi: 3
yukari: 0
derin: 0
dis: 0
```

Aynı çocuk üç hizalamada `x=0`, `x=80` ve `x=160` aldı. Üç hareket de aynı: ebeveynden çocuğa, aşağı. Hizalama bir veri değil, bir hesap parametresidir; ağaçta yeni bir ilişki kurmaz. `Box` bu ilişkiyi ikinci eksene taşır: çocukları aynı kutuda üst üste durur ve `contentAlignment` hem yatay hem dikey konumu birlikte seçer.

**AD3.** Model hizalamayı üç değere indirir. Platformdaki `Alignment` değerlerinin tamamı, soldan sağa yazılan dilde başlangıç ve bitiş anlamı ve çocuğun ebeveynden büyük olduğu durum ölçülmedi.

#### Satır ve Kutu Aynı İlişkiyi Kurar

`Row`, `Column`'un eksenleri değiştirilmiş hâlidir. Ana eksen yatay olur: çocukların genişlikleri toplanır, her çocuğun `x` konumu öncekilerin genişliklerinin toplamı olur. Çapraz eksen dikeydir ve `y` hizalamaya bağlıdır. Hesabın biçimi aynıdır; değişen yalnız hangi ölçünün toplandığı ve hangisinin ebeveynle karşılaştırıldığıdır. Ağaçta ise hiçbir şey değişmez: `Row` da çocuklarına kısıt verir, onlardan ölçü alır ve onları yerleştirir. Hareketlerin uçları aynıdır, bu yüzden yönleri de aynıdır.

`Box` farklı davranır, ama ağaç ilişkisi bakımından değil. Çocukların hiçbiri diğerinin konumunu etkilemez; her çocuk kutunun kısıtı içinde bağımsız ölçülür ve `contentAlignment`'a göre yerleştirilir. Kutunun kendi boyutu, çocukların en büyüğüne uyar. Bu, ölçünün yukarı gittiği bir durumdur ve ebeveynin boyutu çocuklardan çıkar. Ama bir çocuğun ölçüsü, kardeşinin kısıtını değiştirmez. Kardeşler arasında doğrudan bir veri hareketi yoktur; bu sınır sonraki derslerde sık sık görülecek.

Bu üç düzenin ortak noktası, çocuğun kendi konumunu bilmemesidir. Konum, çocuğun parametresi olarak değil, ebeveynin yerleştirme adımının sonucu olarak oluşur. Bir çocuk "ekranın sağına yaslan" demek isterse bunu ebeveynin hizalama parametresiyle ya da kendi kısıtını genişleten bir değiştiriciyle yapar. Değiştirici konusu ikinci dersin işidir.

**AD4.** `Row` ve `Box` için ayrı bir model kurulmadı; bu bölümdeki hesap tarifi sütun modelinden türetilmiş bir anlatımdır ve ölçülmemiştir.

#### Sınırlayıcı Ölçüm

Dersin yönü, tek bir hareketin değil üç verinin birlikte okunmasıyla seçilir. Aşağıdaki ölçüm dört hareketi birlikte sayar: `Screen`'in `Column`'a verdiği kısıt, `Column`'un `Body`'ye verdiği kısıt, `Body`'nin bildirdiği ölçü ve `Column`'un `Body`'ye verdiği konum.

```kotlin
// Main.kt — dort hareketin yon dokumu
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
    val placed = placeColumn(360, listOf(Triple("Title", 200, 48), Triple("Body", 300, 120)), "start")
    val body = placed[1]
    val moves = listOf(
        Move("kisit-sutun", "Screen", "Column", tree),
        Move("kisit-govde", "Column", "Body", tree),
        Move("olcu-govde", body.node, "Column", tree),
        Move("konum-govde", "Column", body.node, tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
kisit-sutun: kimden=Screen | kime=Column -> asagi
kisit-govde: kimden=Column | kime=Body -> asagi
olcu-govde: kimden=Body | kime=Column -> yukari
konum-govde: kimden=Column | kime=Body -> asagi
asagi: 3
yukari: 1
derin: 0
dis: 0
```

`kisit-sutun` ve `kisit-govde` satırlarının ikişer ucu elle yazılmıştır: bu satırlar kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan hareketler, bir ucu modelin yerleştirdiği `Body` düğümünden gelen `olcu-govde` (yukarı) ve `konum-govde` (aşağı) satırlarıdır; aşağı üç sayısının ikisi gösterim satırlarına aittir.

Aşağı üç, yukarı bir. Yukarı giden tek veri ölçüdür; kısıt ve konum aşağıdır. Ölçü kısıtsız olmaz, çünkü çocuk ancak kendisine verilen aralığın içinde bir boyut seçebilir; yani yukarı giden her ölçünün öncesinde aşağı giden bir kısıt vardır. Bu yüzden dersin yönü kısıtın yönüdür: **aşağı**.

**AD5.** Model dört hareketi bir `Column` ve onun altındaki tek bir `Body` için kurar. Çocuk sayısı arttıkça ölçü ve konum hareketlerinin sayısı çocuk sayısıyla artar; bu ölçüm tek çocukla sınırlıdır.

#### Özet

- Bu dersin yönü: aşağı. Kısıt ve konum ebeveynden çocuğa iner; ölçülen konum hareketi de aşağıdır.
- Düzenin tek geçişi üç veri taşır: kısıt, ölçü ve konum.
- Kısıt ebeveynden çocuğa, konum ebeveynden çocuğa gider; ikisi de aşağıdır.
- Çocuğun bildirdiği ölçü ebeveyne döner; bu hareket yukarıdır.
- Sıra ve hizalama konumun değerini değiştirir, hareketin yönünü değiştirmez.

#### Sonraki Adım

Kısıtı çocuğa ileten tek düğüm sütun değildir. Bir bileşen işlevinin üstüne zincirleme eklenen her değiştirici de kendi kısıtını ardındakine geçirir. Sıra değiştiğinde aynı kısıt başka bir düğümden geçerek ilerler. Sonraki ders, sırası yönü değiştiren bu zinciri ölçer.

### Değiştiriciler

Source: https://academia.sh/tr/kurslar/compose/degistiriciler

Değiştirici zincirinde sıranın anlamı ve dıştaki değiştiricinin kısıtının içeriğe hangi yönde ulaştığının ölçümü.

Önceki derste bir sütun, kendisine verilen genişliği çocuklarına iletti ve kısıtın ebeveynden çocuğa, yani aşağı gittiği görüldü. Bu ders aynı kısıtın bir düğümden değil, bir zincirden geçişine bakar. Bir bileşen işlevinin `modifier` parametresine verilen değiştirici zinciri (`Modifier`) boyut, boşluk, arka plan ve tıklama davranışı ekler. Zincir soldan sağa yazılır ve sıra anlam taşır: aynı iki değiştirici ters sırada başka bir sonuç verir.

Bu sonucun nedeni, zincirdeki her değiştiricinin kendisinden sonra gelenleri sarmasıdır. Dıştaki değiştirici kısıtı alır, değiştirir ve içtekine verir; içtekinden dönen ölçüyü kendi payıyla büyütür ya da sınırlar. Ölçülecek şey, bu sarmalamanın arayüz ağacında nasıl göründüğüdür.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Zincir ve Sıra

Aşağıdaki parça iki zincir kurur. İlkinde boyut boşluktan önce gelir, ikincisinde sonra. Üçüncü örnek, tıklama alanının boşluğu kapsayıp kapsamadığını sıraya göre değiştiren zincirdir.

```kotlin
// Platform kodu: sirasi anlam tasiyan zincirler
@Composable
fun Chains() {
    Box(Modifier.size(200.dp).padding(16.dp).background(Color.LightGray))
    Box(Modifier.padding(16.dp).size(200.dp).background(Color.LightGray))
    Text(
        text = "Open",
        modifier = Modifier.fillMaxWidth().clickable { open() }.padding(16.dp),
    )
}
```

İlk iki `Box` aynı üç değiştiriciyi taşır, ama sırayı farklıdır. `size(200.dp)` önce gelirse düğümün toplam genişliği 200'dür ve boşluk bu 200'ün içinden ayrılır. `padding(16.dp)` önce gelirse içerik 200 genişliğinde olur ve boşluk onun dışına eklenir. Üçüncü örnekte `clickable`, `padding`'den önce olduğu için tıklanabilir alan boşluğu da kapsar; sıra tersine çevrilirse yalnız içeriği kapsar.

Platformun kuralı şudur: zincirin ilk değiştiricisi kısıtı alır, onu işleyip ikinciye verir. Zincirin sonunda içerik, dar kısıtlarla ölçülür. Model bu kuralı kurar. Model, platformun kendisi değildir. Yardımcı dosya üç değiştirici tanır: `SizeMod` genişliği kısıtlar, `PaddingMod` her iki yandan boşluk ayırır, `BackgroundMod` kapladığı alanı kaydeder. İçerik, kendisine verilen en büyük genişliği alır; bu bir modelleme varsayımıdır. Ayrıca `wrapChain` işlevi, zinciri arayüz ağacına çevirir: ilk değiştirici ebeveynin çocuğudur, sonraki her değiştirici bir öncekinin çocuğudur, içerik en içtedir.

```kotlin
// Chain.kt — degistirici zincirinin olcusu (modelin hesabi)
sealed interface Mod
class SizeMod(val width: Int) : Mod
class PaddingMod(val amount: Int) : Mod
object BackgroundMod : Mod

class Measured(val total: Int, val content: Int, val painted: Int?)

fun measureChain(chain: List<Mod>, min: Int, max: Int): Measured {
    if (chain.isEmpty()) return Measured(max, max, null)
    val rest = chain.drop(1)
    return when (val mod = chain.first()) {
        is SizeMod -> {
            val fixed = mod.width.coerceIn(min, max)
            val inner = measureChain(rest, fixed, fixed)
            Measured(fixed, inner.content, inner.painted)
        }
        is PaddingMod -> {
            val inner = measureChain(rest, maxOf(0, min - 2 * mod.amount), maxOf(0, max - 2 * mod.amount))
            Measured((inner.total + 2 * mod.amount).coerceIn(min, max), inner.content, inner.painted)
        }
        BackgroundMod -> {
            val inner = measureChain(rest, min, max)
            Measured(inner.total, inner.content, inner.total)
        }
    }
}

class Wrapped(val tree: Tree, val nodes: List<String>)

fun wrapChain(chain: List<Mod>, parent: String): Wrapped {
    val nodes = chain.map {
        when (it) {
            is SizeMod -> "Size"
            is PaddingMod -> "Padding"
            BackgroundMod -> "Background"
        }
    } + "Content"
    val parentOf = nodes.indices.associate { nodes[it] to (if (it == 0) parent else nodes[it - 1]) }
    return Wrapped(Tree(parentOf), nodes)
}
```

#### Boyut Boşluğun İçinde

İlk ölçüm `size(200)` ve ardından `padding(16)` zincirini kurar. Ebeveyn, en fazla 360 genişliğinde bir kısıt verir. Ağaç `Row` altında `Size`, onun altında `Padding`, onun altında `Content` olur. Her kısıt bir sarmalayıcıdan, hemen içindekine geçer.

```kotlin
// Main.kt — boyut once, bosluk sonra
fun main() {
    val chain = listOf(SizeMod(200), PaddingMod(16))
    val wrapped = wrapChain(chain, "Row")
    val measured = measureChain(chain, 0, 360)
    println("toplam=${measured.total} icerik=${measured.content}")
    val moves = listOf(
        Move("kisit-boyut", wrapped.nodes[0], wrapped.nodes[1], wrapped.tree),
        Move("kisit-bosluk", wrapped.nodes[1], wrapped.nodes[2], wrapped.tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
toplam=200 icerik=168
kisit-boyut: kimden=Size | kime=Padding -> asagi
kisit-bosluk: kimden=Padding | kime=Content -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 0
```

Düğümün toplam genişliği 200, içerik 168. Boşluk 200'ün içinden ayrıldı: her yandan 16, toplam 32. İki kısıt hareketi de aşağıdır, çünkü her biri sarmalayıcıdan hemen içindeki düğüme gider. Kısıt, dıştaki değiştiricinin kısıtını doğrudan sarmaladığına verdiği hareket olarak sınıflanır: aşağı.

**AD1.** Model `size` değiştiricisini tek bir genişlikle sınırlar ve `coerceIn` ile kısıtın içinde tutar. Platformdaki `requiredSize` gibi kısıtı aşabilen değiştiriciler ölçülmedi.

#### Sıra Değişince Son Genişlik

Şimdi iki değiştiricinin sırası ters çevrilir: önce boşluk, sonra boyut. Ebeveynin kısıtı aynıdır. Fark, kısıtın önce boşluğa ulaşmasıdır; boşluk kısıtı daraltır, boyut daralmış kısıtın içinde 200'ü seçer ve boşluk bu 200'ün dışına eklenir.

```kotlin
// Main.kt — iki sirada son genislik
fun main() {
    val orders = listOf(
        "boyut-bosluk" to listOf(SizeMod(200), PaddingMod(16)),
        "bosluk-boyut" to listOf(PaddingMod(16), SizeMod(200)),
    )
    for ((name, chain) in orders) {
        val measured = measureChain(chain, 0, 360)
        println("$name: toplam=${measured.total} icerik=${measured.content}")
    }
}
```

```text
boyut-bosluk: toplam=200 icerik=168
bosluk-boyut: toplam=232 icerik=200
```

Aynı iki değiştirici, aynı kısıt; toplam genişlik 200'e karşı 232, içerik 168'e karşı 200. Zincirin sırası, değerin hangi sarmalayıcıda eklendiğini belirler. Bunlar modelin hesabıdır. Sıranın ağaçtaki karşılığı, son bölümde ölçülür.

**AD2.** Ebeveyn kısıtı 360 olarak sabitlendi. Kısıtın alt sınırı sıfır alındı; alt sınırı sıfırdan büyük kısıtlar bu derste ölçülmedi.

#### Kısıt İçe, Ölçü Dışa

Zincirin okunuşunda iki yön vardır ve ikisi de aynı zincirde yaşar. Kısıt dıştan içe iner: her halka kısıtı işleyip bir içtekine verir. Ölçü içten dışa döner: içerik boyutunu bildirir, her halka bu boyuta kendi payını ekler ya da çıkarır ve bir dıştakine bildirir. Yukarıdaki ilk ölçümde içerik 168 genişliğinde döndü, `Padding` buna 32 ekledi ve `Size` 200'ü bildirdi. İkinci sırada aynı dönüş 168 yerine 200 ile başladı ve 232 ile bitti.

Bu ayrım, iki yaygın yanlış okumayı önler. Birincisi, `size` değiştiricisinin her zaman son boyutu kesinleştirdiği düşüncesidir. `size`, yalnız kısıtı sabitler; sabitlediği değer hâlâ ondan önce gelen halkaların kısıtının içinde kalır. Model bunu `coerceIn` ile kurdu. Platformda kısıtı aşabilen ayrı bir değiştirici de vardır, `requiredSize`; bu ders onu ölçmedi. İkincisi, sıranın yalnız görünümü etkilediği düşüncesidir. Sıra, hangi halkanın hangi kısıtı gördüğünü belirler; görünüm bunun sonucudur.

Zincirin ağaçtaki yeri de ayrıca not edilmelidir. Platformda her halkanın ayrı bir arayüz düğümü olduğu varsayılmaz. Model ise her halkayı bir düğüm sayar; çünkü ölçülen ilişki, hangi halkanın hangi halkadan önce kısıt aldığıdır. Bu bir basitleştirmedir ve sonuçları, kısıtın geçtiği sırayı doğru yansıttığı ölçüde geçerlidir.

**AD3.** Model her değiştiriciyi bir düğüm sayar. Platformun değiştirici zincirini kendi içinde nasıl temsil ettiği bu derste ölçülmedi.

#### Arka Plan Sırayı İzler

Arka plan, kendi düğümünün kapladığı alanı boyar; yani sarmaladığı zincirin toplamı kadarını. `padding` arka plandan önce gelirse arka plan boşluğun içinde kalır. Arka plan önce gelirse boşluğu da boyar.

```kotlin
// Main.kt — arka planin kapladigi alan
fun main() {
    val orders = listOf(
        "bosluk-arkaplan" to listOf(PaddingMod(16), BackgroundMod),
        "arkaplan-bosluk" to listOf(BackgroundMod, PaddingMod(16)),
    )
    for ((name, chain) in orders) {
        val measured = measureChain(chain, 0, 360)
        println("$name: boyanan=${measured.painted} icerik=${measured.content}")
    }
}
```

```text
bosluk-arkaplan: boyanan=328 icerik=328
arkaplan-bosluk: boyanan=360 icerik=328
```

İçerik iki durumda da 328 genişliğinde. Boyanan alan ise 328'e karşı 360. Bu, `background` değiştiricisinin tek başına bir genişliği olmadığını gösterir: genişliği, zincirde kendisinden sonra gelenlerin toplamıdır. Tıklama alanı da aynı mantıkla çalışır: `clickable`, sarmaladığı zincirin kapladığı alanı dinler.

**AD4.** Model boyanan alanı yalnız genişlik olarak ölçer; yükseklik ve köşe yuvarlama gibi şekil bilgisi modelde yoktur. Tıklama alanı da bu derste ölçülmedi, yalnız arka planla aynı mantığı izlediği söylendi.

#### Sınırlayıcı Ölçüm

Dersin yönü, sıranın yönü değiştirip değiştirmediği sorusuyla belirlenir. `Size`'ın kısıtı iki sırada da içeriğe ulaşır. Ama bir sırada `Padding` onun hemen içindedir, diğer sırada `Size` içeriği doğrudan sarar. Ölçüm, her iki sırada `Size`'tan `Content`'e giden kısıt hareketini sınıflandırır.

```kotlin
// Main.kt — ayni kisitin iki siradaki yonu
fun main() {
    val orders = listOf(
        "boyut-bosluk" to listOf(SizeMod(200), PaddingMod(16)),
        "bosluk-boyut" to listOf(PaddingMod(16), SizeMod(200)),
    )
    val moves = mutableListOf<Move>()
    for ((name, chain) in orders) {
        val wrapped = wrapChain(chain, "Row")
        moves.add(Move("boyut-icerige-$name", "Size", wrapped.nodes.last(), wrapped.tree))
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
boyut-icerige-boyut-bosluk: kimden=Size | kime=Content -> derin
boyut-icerige-bosluk-boyut: kimden=Size | kime=Content -> asagi
asagi: 1
yukari: 0
derin: 1
dis: 0
```

Aynı `Size` kısıtı, `boyut-bosluk` sırasında `Content`'e derin ulaşır, çünkü araya `Padding` girmiştir. `bosluk-boyut` sırasında `Size` içeriği doğrudan sarar ve kısıt aşağı gider. Değiştirici aynı, kısıt aynı; sıra değişince araya giren sarmalayıcı yönü değiştirir. Zincirin her halkası kısıtı hemen içindekine verir; bu hareketler aşağıdır. Derin, araya giren halkaların atlandığı durumdur ve onu yalnız sıra doğurur. Kısıtın zincir boyunca asıl yolu halka halka iner, bu yüzden dersin yönü: **aşağı**.

**AD5.** Model yalnız bir kısıt ve iki sıra için yönü sınıflandırır. Üç ya da daha fazla halkalı zincirde atlanan halka sayısı arttıkça hareket yine derindir, ama bu sayı ölçülmedi.

#### Özet

- Bu dersin yönü: aşağı. Kısıt, zincirde sarmalayan halkadan sarmalanana doğrudan iner.
- Değiştirici zinciri, her halkası kendisinden sonrakini saran iç içe düğümlerdir.
- Dıştaki değiştiricinin kısıtı, doğrudan sarmaladığına aşağı gider.
- Aynı iki değiştiricinin sırası değişince son genişlik değişir; boşluk boyutun içinde ya da dışında kalır.
- Arka planın kapladığı alan, kendisinden sonra gelen zincirin toplamıdır.

#### Sonraki Adım

Değiştiriciler kısıtı bir zincir boyunca iletir; ama zincirdeki her halka yine ebeveyn-çocuk ilişkisinin içindedir. Kardeşler arasında doğrudan bir kısıt yoktur. Bir öğenin konumunu başka bir kardeşe göre veren düzenlerde bu sınır görünür hâle gelir. Sonraki ders, kısıt tabanlı düzeni ölçer.

### Kısıt Tabanlı Düzen

Source: https://academia.sh/tr/kurslar/compose/kisit-tabanli-duzen

Kardeşe bağlanan ögelerin konumunun ebeveyn üzerinden hesaplandığının ve kardeşten kardeşe doğrudan hareketin kurulamadığının ölçümü.

Önceki derste sütunun çocukları kaynakta yazıldığı sırayla dizildi. Her çocuk konumunu yalnız ebeveyninden aldı; kardeşlerinin nerede olduğunu bilmesi gerekmedi. Ama bazı arayüzlerde bir ögenin yeri, başka bir ögenin yerine bağlıdır: ad, simgenin sağına, rozet adın altına gelir. Bu bağlılığı iç içe sütun ve satırlarla kurmak mümkündür, ama her bağlılık bir düzey ekler. Kısıt tabanlı düzen (`ConstraintLayout`) bunu tek düzeyde çözer: ögeler birbirine, ebeveyne ve kılavuz çizgilerine bağlanır.

Bu ders, bir kardeşe bağlanmanın arayüz ağacında ne anlama geldiğini ölçer. Kardeşler aynı ebeveynin çocuklarıdır ve ağaçta aralarında bir kenar yoktur. Bir ögenin konumu başka bir ögeye bağlıysa, bu bilginin hangi yoldan gittiği sorusu ortaya çıkar.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Ögeleri Birbirine Bağlamak

Bir kısıt tabanlı düzende ögeler önce referans olarak oluşturulur, sonra her biri kenarlarını başka bir referansın kenarına bağlar. Yatay eksende bir ögenin başlangıcı, bir başkasının bitişine; dikey eksende üstü, bir başkasının altına bağlanabilir. Aralarına boşluk konabilir. Kılavuz çizgisi ise ekranda görünmeyen bir yardımcıdır: ebeveynin genişliğinin bir oranında durur ve ögeler ona bağlanabilir.

```kotlin
// Platform kodu: kardese baglanan ogeler ve kilavuz cizgisi
import androidx.constraintlayout.compose.ConstraintLayout

@Composable
fun ProfileRow() {
    ConstraintLayout(Modifier.fillMaxWidth()) {
        val (avatar, name, badge) = createRefs()
        val guide = createGuidelineFromStart(0.3f)
        Box(Modifier.size(48.dp).constrainAs(avatar) {
            start.linkTo(parent.start)
            top.linkTo(parent.top)
        })
        Text("Ada", Modifier.constrainAs(name) {
            start.linkTo(avatar.end, margin = 8.dp)
            top.linkTo(parent.top)
        })
        Text("New", Modifier.constrainAs(badge) {
            start.linkTo(guide)
            top.linkTo(name.bottom)
        })
    }
}
```

Bu parçada üç ögenin üçü de `ConstraintLayout`'un çocuğudur; `name`, `avatar`'ın bitişine, `badge` ise `name`'in altına ve kılavuz çizgisine bağlanır. Yazım, bağlılığı ögenin kendi lambdasında tutar: `name`, `avatar`'ı kendi kenarı üzerinden okur. Ağaçta ise `name` ile `avatar` kardeştir; kaynakta görünen bağ, ağaçta bir kenar değildir.

Model, platformun bu kuralını kurar. Model, platformun kendisi değildir. Yardımcı dosya, ögeleri bağımlılık sırasıyla alır: bir ögenin başlangıcı, bağlandığı ögenin başlangıcı, genişliği ve aradaki boşluğun toplamıdır; hiçbir şeye bağlanmayan ögenin başlangıcı yalnız boşluktur. Bağımlılık sırası ögelerin verilişinden gelir; platformun bağımlılık grafiğini çözmesi modelde yoktur.

```kotlin
// Anchors.kt — kardese baglanan ogelerin konumu (modelin hesabi)
class Item(val name: String, val width: Int, val anchor: String?, val gap: Int)
class Placement(val name: String, val x: Int, val anchor: String?)

fun solveAnchors(items: List<Item>): List<Placement> {
    val placed = mutableMapOf<String, Int>()
    val widths = items.associate { it.name to it.width }
    return items.map { item ->
        val anchor = item.anchor
        val x = if (anchor == null) item.gap else placed.getValue(anchor) + widths.getValue(anchor) + item.gap
        placed[item.name] = x
        Placement(item.name, x, anchor)
    }
}
```

#### Kardeşin Konumu Önce Yukarı Çıkar

Bir kardeşe bağlanan ögenin konumu iki adımda oluşur. Önce kardeşin konumu düzen ebeveynine bildirilir: düzen, hangi ögenin nerede olduğunu bilmek zorundadır, çünkü yerleştirme adımı onundur. Sonra ebeveyn, bağlanan ögeye onun yeni konumunu verir. İlk hareket çocuktan ebeveyne, yani yukarıdır; ikincisi ebeveynden çocuğa, yani aşağıdır.

Ağaç `Screen`'in altında `ConstraintLayout`, onun altında üç öge kurar: `Avatar`, `Name`, `Badge`. İlk ölçüm, ilk iki ögeyi yerleştirir. `Avatar` ebeveyne bağlıdır; `Name`, `Avatar`'a bağlanır.

```kotlin
// Main.kt — kardesin konumu ebeveyne cikar, ebeveynden iner
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App", "ConstraintLayout" to "Screen",
        "Avatar" to "ConstraintLayout", "Name" to "ConstraintLayout", "Badge" to "ConstraintLayout",
    ))
    val placements = solveAnchors(listOf(Item("Avatar", 48, null, 0), Item("Name", 120, "Avatar", 8)))
    val first = placements[0]
    val second = placements[1]
    println("${first.name}: x=${first.x}")
    println("${second.name}: x=${second.x}")
    val moves = listOf(
        Move("konum-bildir-avatar", first.name, "ConstraintLayout", tree),
        Move("konum-ver-ad", "ConstraintLayout", second.name, tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
Avatar: x=0
Name: x=56
konum-bildir-avatar: kimden=Avatar | kime=ConstraintLayout -> yukari
konum-ver-ad: kimden=ConstraintLayout | kime=Name -> asagi
asagi: 1
yukari: 1
derin: 0
dis: 0
```

`Name`, `x=56` konumunu aldı: `Avatar`'ın genişliği 48 ve aradaki boşluk 8'dir. Bu bilgi `Avatar`'dan `Name`'e doğrudan gitmedi. Önce `Avatar` kendi konumunu düzene bildirdi, yukarı; düzen bunu kullanıp `Name`'in konumunu hesapladı ve ona verdi, aşağı. Kaynakta tek satırlık bir bağ gibi görünen şey, ağaçta iki hareketlik bir yoldur.

**AD1.** Model ögeleri bağımlılık sırasıyla alır ve yalnız yatay eksende yerleştirir. Dikey bağlar, birden çok ögeye aynı anda bağlanma ve bağlılıkların döngü kurduğu durum ölçülmedi.

#### Zincir Boyunca İki Hareketli Adımlar

Zincir uzadığında her bağ aynı iki hareketi tekrar eder. `Badge`, `Name`'e bağlanır; `Name`'in konumu önce düzene çıkar, sonra `Badge`'e iner. Bu, üç ögeli bir zincirde `Name`'in iki farklı rol oynadığı anlamına gelir: `Avatar`'a göre bağlanan, `Badge` tarafından okunan öge.

```kotlin
// Main.kt — uc ogelik zincirin hareketleri
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App", "ConstraintLayout" to "Screen",
        "Avatar" to "ConstraintLayout", "Name" to "ConstraintLayout", "Badge" to "ConstraintLayout",
    ))
    val placements = solveAnchors(listOf(
        Item("Avatar", 48, null, 0), Item("Name", 120, "Avatar", 8), Item("Badge", 32, "Name", 8),
    ))
    val moves = mutableListOf<Move>()
    for (p in placements) {
        println("${p.name}: x=${p.x}")
        val anchor = p.anchor ?: continue
        moves.add(Move("konum-bildir-${anchor.lowercase()}", anchor, "ConstraintLayout", tree))
        moves.add(Move("konum-ver-${p.name.lowercase()}", "ConstraintLayout", p.name, tree))
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
Avatar: x=0
Name: x=56
Badge: x=184
konum-bildir-avatar: kimden=Avatar | kime=ConstraintLayout -> yukari
konum-ver-name: kimden=ConstraintLayout | kime=Name -> asagi
konum-bildir-name: kimden=Name | kime=ConstraintLayout -> yukari
konum-ver-badge: kimden=ConstraintLayout | kime=Badge -> asagi
asagi: 2
yukari: 2
derin: 0
dis: 0
```

Üç öge, iki bağ ve dört hareket: iki yukarı, iki aşağı. `Badge` `x=184` konumunu aldı: `Name`'in başlangıcı 56, genişliği 120 ve boşluk 8'dir. Yukarı hareketlerin sayısı, bağların sayısına eşittir; her bağ, bağlanılan kardeşin konumunu bir kez ebeveyne çıkarır.

**AD2.** Model bağın bağlandığı ögenin sağ kenarını kullanır. Kılavuz çizgisine bağlanma ayrı bir ilişkidir ve ölçülmedi.

#### Kılavuz Çizgisi Bir Kardeştir

Kılavuz çizgisi, ögelerle aynı düzeyde duran ve görünmeyen bir yardımcıdır. Ögeler ona bağlanırken konum bilgisi yine ebeveyn üzerinden akar: çizginin konumu ebeveynin genişliğinin oranından hesaplanır ve çizgiye bağlanan ögeye düzen verir. Kılavuz çizgisi bu açıdan bir kardeşle aynıdır: ağaçta ögelerin yanındadır ve onlarla arasında doğrudan bir kenar yoktur. Bu derste ölçülen model çizgiyi ayrı bir öge olarak kurmadı; bu cümle platform davranışının anlatımıdır, ölçüm sonucu değil.

#### Kardeşten Kardeşe Kurulmaz

Kaynak kodda `name`'in `avatar`'a doğrudan bağlandığı satırı yazmak kolaydır. Ağaçta ise iki kardeşin birbirine veri göndermesi, ortak ataya çıkmadan ve ondan inmeden mümkün değildir. Çekirdek bu durumu bir yön olarak değil, hata olarak sınıflar: ne aşağı, ne yukarı, ne derin, ne de dış. Hareketi kurmaya çalışan kod `akrabasiz` hatasıyla durur.

```kotlin
// Main.kt — kardesler arasi dogrudan hareket reddedilir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App", "ConstraintLayout" to "Screen",
        "Avatar" to "ConstraintLayout", "Name" to "ConstraintLayout",
    ))
    val direct = runCatching { Move("dogrudan", "Avatar", "Name", tree) }
    println("kuruldu=${direct.isSuccess}")
    println(direct.exceptionOrNull()?.message)
    val viaParent = listOf(
        Move("yukari-yol", "Avatar", "ConstraintLayout", tree),
        Move("asagi-yol", "ConstraintLayout", "Name", tree),
    )
    for (move in viaParent) {
        move.describe()
    }
}
```

```text
kuruldu=false
akrabasiz: Avatar ve Name
yukari-yol: kimden=Avatar | kime=ConstraintLayout -> yukari
asagi-yol: kimden=ConstraintLayout | kime=Name -> asagi
```

`yukari-yol` ve `asagi-yol` satırlarının ikişer ucu elle yazılmıştır: bu satırlar kuralı göstermek için yazılmıştır, ölçüm değildir. Kardeş kısıtının iki hareket olduğu bulgusu, uçları modelin yerleştirdiği düğümlerden gelen sınırlayıcı ölçüme dayanır.

Doğrudan hareket kurulamadı ve çekirdeğin kendi mesajı `akrabasiz: Avatar ve Name` basıldı. Aynı bilgi ebeveyn üzerinden iki hareketle taşındı: yukarı ve aşağı. Ağaçta bir kardeş kısıtı, bu iki hareketin toplamıdır.

#### Sınırlayıcı Ölçüm

Dersin yönü, kardeş kısıtını mümkün kılan hareketten seçilir. Aşağı yöndeki kısıt ve konum hareketleri önceki derste ölçüldü; bu derste yeni olan, bağlanılan kardeşin konumunun ebeveyne çıkmasıdır. Aşağıdaki ölçüm bir bağı hareketlerine ayırır ve doğrudan yolun yerine konan iki hareketi sayar.

```kotlin
// Main.kt — bir kardes kisitinin hareketleri
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App", "ConstraintLayout" to "Screen",
        "Avatar" to "ConstraintLayout", "Name" to "ConstraintLayout",
    ))
    val placements = solveAnchors(listOf(Item("Avatar", 48, null, 0), Item("Name", 120, "Avatar", 8)))
    val moves = listOf(
        Move("konum-bildir", placements[0].name, "ConstraintLayout", tree),
        Move("konum-ver", "ConstraintLayout", placements[1].name, tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
konum-bildir: kimden=Avatar | kime=ConstraintLayout -> yukari
konum-ver: kimden=ConstraintLayout | kime=Name -> asagi
asagi: 1
yukari: 1
derin: 0
dis: 0
```

Bir kardeş kısıtı iki harekettir: biri yukarı, biri aşağı. Aşağı hareket, her ebeveyn çocuk ilişkisinde zaten bulunan konum verişidir. Kısıt tabanlı düzene özgü olan yukarı harekettir, çünkü bu düzenin kardeşe bağlanma yeteneği ancak bu hareketle çalışır. Dersin yönü: **yukarı**.

**AD3.** Model bir bağı iki harekete ayırır. Platformun bu iki adımı gerçekte tek bir çözüm geçişinde birleştirip birleştirmediği ölçülmedi.

#### Özet

- Bu dersin yönü: yukarı. Dersin yeni hareketi, bağlanılan kardeşin konumunun ebeveyne çıkmasıdır.
- Kısıt tabanlı düzende bir ögenin konumu başka bir ögeye bağlanabilir; ağaçta bu iki ögenin kardeş olması demektir.
- Kardeşten kardeşe doğrudan hareket kurulamaz; çekirdek bunu `akrabasiz` hatasıyla reddeder.
- Bir kardeş kısıtı iki harekettir: kardeşin konumu ebeveyne yukarı, ebeveynden bağlanan ögeye aşağı.
- Kılavuz çizgisi ögelerle aynı düzeyde durur ve konum bilgisi onun için de ebeveynden akar.

#### Sonraki Adım

Kısıt tabanlı düzen, ögelerin hepsinin bir arada görünür olduğunu varsayar. Binlerce ögenin yer aldığı bir listede bu varsayım bozulur: ekranda olmayan ögeler için ağaçta düğüm de olmaz. Sonraki ders, yalnız görünen ögelerin oluşturulduğu tembel listeyi ve kaydırma konumunun sahibine çıkışını ölçer.

### Tembel Listeler

Source: https://academia.sh/tr/kurslar/compose/tembel-listeler

Tembel listede yalnız görünen satırların arayüz ağacına girmesinin, kaydırma konumunun sahibine çıkışının ve ağaçta olmayan satıra giden verinin ölçümü.

Önceki derste ögelerin hepsi ağaçtaydı ve bir öge ötekine ancak ebeveyn üzerinden bağlanabiliyordu. Bir satır listesinde bu varsayım pahalıya gelir: bin satırlık bir sepeti bin düğümle kurmak, ekranda beş satır görünürken de bin satırı ölçmek ve yerleştirmek demektir. Tembel liste bunu yapmaz. Yalnız görünen aralıktaki satırlar için düğüm oluşturur; kullanıcı kaydırdıkça aralık kayar, ekrandan çıkan satırın düğümü ağaçtan düşer ve yeni giren satır için yenisi kurulur.

Bu durum ağaç ölçüsünü doğrudan etkiler. Ağaçta olmayan bir düğüme veri gönderilemez. Bu ders, bir satırın ağaçta bulunup bulunmamasının, ona giden verinin yönünü nasıl değiştirdiğini ölçer. Aynı derste ters yöndeki bir veri de ölçülür: listenin kaydırma konumu, listeyi kullanan ekrana nasıl ulaşır?

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Tembel Liste ve Öge

`LazyColumn`, dikey tembel listedir; yatay karşılığı `LazyRow`'dur. Satırlar `items` ile tanımlanır: bir koleksiyon ve her öge için bir bileşen işlevi verilir. Sütun ve satır düzenleri çocuklarını bir kerede ölçerken tembel liste, ölçmeyi görünen aralıkla sınırlar. Kaydırma konumu bir durum nesnesinde tutulur (`LazyListState`); bu nesneyi oluşturan ve hatırlayan bileşen işlevi, konumu okuyabilir.

```kotlin
// Platform kodu: tembel liste, ogeler ve kaydirma durumu
@Composable
fun CartList(products: List<Product>) {
    val listState = rememberLazyListState()
    LazyColumn(state = listState) {
        items(products, key = { it.id }) { product ->
            ProductRow(product)
        }
    }
    val first = listState.firstVisibleItemIndex
}
```

Burada `CartList`, `listState`'in sahibidir: durumu o oluşturur ve hatırlar. `LazyColumn` bu durumu parametre olarak alır, kaydırma değiştikçe onu günceller. Satırlar `items` lambdasında oluşturulur, bu yüzden ağaçta `LazyColumn`'un çocuklarıdır. `CartList`, `LazyColumn`'un ebeveynidir.

Model, platformun bu kuralını kurar. Model, platformun kendisi değildir. Yardımcı dosya iki şey yapar: toplam satır sayısı, ilk görünen satırın sırası ve ekrana sığan satır sayısından görünen aralığı çıkarır; sonra bu aralıktaki satırlardan bir arayüz ağacı kurar. Ağaç `Screen`'in altında `LazyList`'i, onun altında yalnız görünen satırları taşır. Ölçümlerin uçlarını da iki işlev verir: `rowName` bir satırın düğüm adını üretir, `scrollOwner` listenin ebeveynini ağaçtan okur. Bu derste hareketin uçları elle yazılmaz; en az bir ucu bu işlevlerden gelir.

```kotlin
// Window.kt — tembel listenin gorunen satirlari (modelin hesabi)
fun visibleRows(total: Int, firstVisible: Int, viewportRows: Int): List<String> =
    (firstVisible until minOf(total, firstVisible + viewportRows)).map { rowName(it) }

fun rowName(index: Int): String = "Row$index"

fun scrollOwner(tree: Tree): String = tree.parent("LazyList") ?: error("liste kokte")

fun listTree(rows: List<String>): Tree {
    val parentOf = mutableMapOf("Screen" to "App", "LazyList" to "Screen")
    for (row in rows) {
        parentOf[row] = "LazyList"
    }
    return Tree(parentOf)
}
```

#### Görünen Aralık

İlk ölçüm bin satırlık bir koleksiyonu ele alır. Ekrana beş satır sığar ve liste en başta durur. Ağaçta kaç satır olduğu, ve ilk görünen satıra giden verinin yönü basılır.

```kotlin
// Main.kt — bin satirlik listede agactaki satirlar
fun main() {
    val rows = visibleRows(1000, 0, 5)
    val tree = listTree(rows)
    println("agactaki satir: ${rows.size}")
    println("satirlar: ${rows.joinToString(",")}")
    val moves = listOf(Move("veri-ilk-satir", "LazyList", rows.first(), tree))
    for (move in moves) {
        move.describe()
    }
}
```

```text
agactaki satir: 5
satirlar: Row0,Row1,Row2,Row3,Row4
veri-ilk-satir: kimden=LazyList | kime=Row0 -> asagi
```

Bin satırdan beşi ağaçtadır. Listenin ilk satıra verdiği veri, ebeveynden çocuğa, yani aşağı gitti. Bu, önceki derslerdeki sütun çocuğuyla aynı hareketin tembel bir listedeki biçimidir.

**AD1.** Model ekrana sığan satır sayısını sabit alır ve satır yüksekliklerini hesaba katmaz. Platformun görünür aralığın hemen ötesindeki satırları önceden hazırlayabileceği de modelde yoktur.

#### Kaydırma Konumu Sahibine Çıkar

Kullanıcı kaydırınca ilk görünen satır değişir. Bu bilgiyi liste üretir: kaydırma jestini işleyen liste, durum nesnesine yeni konumu yazar. Durumun sahibi `Screen` ya da onun altındaki bir bileşen işleviyse, konum listeden sahibine gitmiş olur. Liste, sahibinin altındadır, yani sahibi listenin atasıdır; hareket çocuktan ataya gider: yukarı.

Aşağıdaki ölçüm listeyi üç satır kaydırır. Aralık `Row3`'ten `Row7`'ye kayar; `Row0` ağaçtan çıkar, `Row5`...`Row7` ise öncekinde olmayan yeni satırlardır. Kaydırma konumu `Screen`'e gider.

```kotlin
// Main.kt — kaydirma sonrasi aralik ve konumun sahibine cikisi
fun main() {
    val before = visibleRows(1000, 0, 5)
    val after = visibleRows(1000, 3, 5)
    val tree = listTree(after)
    println("once: ${before.joinToString(",")}")
    println("sonra: ${after.joinToString(",")}")
    println("ilk gorunen sira: 3")
    val moves = listOf(Move("kaydirma-konumu", "LazyList", scrollOwner(tree), tree))
    for (move in moves) {
        move.describe()
    }
}
```

```text
once: Row0,Row1,Row2,Row3,Row4
sonra: Row3,Row4,Row5,Row6,Row7
ilk gorunen sira: 3
kaydirma-konumu: kimden=LazyList | kime=Screen -> yukari
```

Aralık beş satırlık kaldı, ama içerik değişti: `Row0`, `Row1` ve `Row2` ağaçtan çıktı; `Row5`, `Row6` ve `Row7` girdi. Kaydırma konumu `LazyList`'ten `Screen`'e yukarı gitti. Bu hareket kullanıcının parmağından değil, listenin kendi durumundan gelir; ekran bu sayede "kullanıcı beşinci satıra yaklaştı" gibi bir bilgiyi okuyabilir.

**AD2.** Model kaydırma konumunu tek bir tamsayı, ilk görünen satırın sırası olarak taşır. Platformun satır içindeki kaydırma kaymasını da ayrıca tuttuğu bu derste ölçülmedi.

#### Anahtar Kimliktir

Ağaçtan düşen ve yeniden giren bir satırın durumu, hangi satır olduğuna göre korunmalıdır. `items` çağrısına verilen `key`, satırın kimliğini belirler. Anahtar verilmezse platform kimliği satırın konumundan türetir; listenin başına yeni bir ögenin eklenmesi her satırın konumunu bir kaydırır ve konuma bağlı durumlar yanlış satıra geçer. Anahtar verilirse durum ögeyle birlikte hareket eder. Anahtar, listede benzersiz olmalıdır.

Bu bölüm bir ölçüm içermez. Kimliğin konumdan ayrılması bir yön sorusu değil, bir eşleştirme sorusudur: aynı veri bir satırdan ötekine mi geçti, yoksa aynı satırda mı kaldı? Böyle bir eşleştirme, ağaç ilişkisini değil, düğümün kimliğini ilgilendirir. Bu dersin ekseni olan yön, anahtar değişse de değişmez: satıra veri hâlâ listeden gider.

#### Ağaçta Olmayan Satır

Kaydırmadan sonra `Row0` ağaçta değildir. Listenin elinde `Row0`'a ait veri hâlâ vardır, çünkü koleksiyon listenin dışında tutulur. Ancak o veriyi alacak düğüm yoktur. Çekirdek, uçlardan birinin ağaçta bulunmadığı her hareketi dış olarak sınıflar.

```kotlin
// Main.kt — gorunen ve gorunmeyen satira ayni veri
fun main() {
    val tree = listTree(visibleRows(1000, 3, 5))
    val moves = listOf(
        Move("veri-satir-3", "LazyList", rowName(3), tree),
        Move("veri-satir-7", "LazyList", rowName(7), tree),
        Move("veri-satir-0", "LazyList", rowName(0), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
veri-satir-3: kimden=LazyList | kime=Row3 -> asagi
veri-satir-7: kimden=LazyList | kime=Row7 -> asagi
veri-satir-0: kimden=LazyList | kime=Row0 -> dis
asagi: 2
yukari: 0
derin: 0
dis: 1
```

Aynı kaynaktan çıkan aynı biçimdeki veri, görünen iki satıra aşağı gitti; ağaçta olmayan satıra dış gitti. Yönü değiştiren şey verinin içeriği ya da kaynağı değil, hedefin ağaçta bulunup bulunmamasıdır. Bu yüzden ekranda olmayan bir satırın durumunu okumak ya da ona doğrudan bir değişiklik yazmak mümkün değildir. O satırla ilgili her şey, satır ağaca yeniden girdiğinde, listenin elindeki koleksiyondan yeniden verilir.

**AD3.** Model ağaçtan düşen satırın durumunun ne olduğunu kurmaz. Satırın kendi hatırladığı değerlerin ağaçtan düşünce kaybolup kaybolmadığı ve kaydedilebilir durumun bunu nasıl değiştirdiği bu derste ölçülmedi.

#### Sınırlayıcı Ölçüm

Dersin yönünü, listenin iki ucundaki verilerden hangisinin ders konusuna özgü olduğu belirler. Bir öge verisinin görünen satıra gitmesi, önceki derslerdeki sütun çocuğuyla aynıdır: aşağı. Tembel listeye özgü olan iki şeydir: ağaçta olmayan satıra giden verinin dış kalması ve kaydırma konumunun sahibine çıkması. Aşağıdaki ölçüm bunları bir araya getirir.

```kotlin
// Main.kt — listenin dort hareketinin yon dokumu
fun main() {
    val rows = visibleRows(1000, 3, 5)
    val tree = listTree(rows)
    val moves = listOf(
        Move("veri-ilk-satir", "LazyList", rows.first(), tree),
        Move("veri-son-satir", "LazyList", rows.last(), tree),
        Move("veri-gorunmeyen", "LazyList", rowName(0), tree),
        Move("kaydirma-konumu", "LazyList", scrollOwner(tree), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
veri-ilk-satir: kimden=LazyList | kime=Row3 -> asagi
veri-son-satir: kimden=LazyList | kime=Row7 -> asagi
veri-gorunmeyen: kimden=LazyList | kime=Row0 -> dis
kaydirma-konumu: kimden=LazyList | kime=Screen -> yukari
asagi: 2
yukari: 1
derin: 0
dis: 1
```

İki aşağı, bir yukarı, bir dış. Aşağı giden iki hareket, ebeveynin çocuğuna verdiği olağan veridir. Dış hareket, listenin tembel olmasının bedelidir. Yukarı hareket ise tembel listenin ekran için taşıdığı bilgidir: konum. Satırlara veri vermek için liste zaten çalışır; ekranın listeyi izleyebilmesi içinse konumun yukarı çıkması gerekir. Dersin yönü: **yukarı**.

**AD4.** Model dört hareketi tek bir kaydırma konumunda kurar. Konum değiştikçe hareket sayıları da değişir; bu ölçüm yalnız `firstVisible` 3 iken geçerlidir.

#### Özet

- Bu dersin yönü: yukarı. Tembel listeye özgü hareket, kaydırma konumunun listeden durumun sahibine çıkmasıdır.
- Tembel liste yalnız görünen aralıktaki satırlar için düğüm oluşturur; ağaçtaki satır sayısı toplam sayıdan küçüktür.
- Listenin görünen satıra verdiği veri aşağı gider.
- Kaydırma konumu listeden durumun sahibine, yani ataya gider: yukarı.
- Ağaçta olmayan satıra giden veri dıştır; satır ağaca girene kadar bekler.

#### Sonraki Adım

Tembel liste, satırlarını kendisi oluşturur ama satırın içeriğini bileşen işlevi belirler. Hazır arayüz bileşenleri bu içeriği önceden kurulmuş parçalarla sağlar; ve bu parçalar içeriklerine kendi renklerini verir. Sonraki ders, düğmenin içeriğine verdiği rengin yönünü ölçer.

### Hazır Arayüz Bileşenleri

Source: https://academia.sh/tr/kurslar/compose/hazir-arayuz-bilesenleri

Düğme gibi hazır arayüz bileşenlerinin içeriğine verdiği rengin ağaçta hangi yöne gittiğinin ve içeriği bir düzenle sarmanın bu yönü nasıl değiştirdiğinin ölçümü.

Tasarım sistemi, sık gereken parçaları hazır sunar: düğme, kart, metin alanı, anahtar, onay kutusu. Bu parçaları kullanan geliştirici rengi, kenar yuvarlaklığını ya da dokunma davranışını yeniden yazmaz. Bunun karşılığında bir şey sorması gerekir: düğmenin içine koyduğu simge ve metin, rengini nereden alıyor? Düğmenin arka planı koyu ise içindeki metin açık olmalıdır; bu karar bir yerde verilir ve içeriğe bir yoldan ulaşır.

Bu dersin ölçü eksenindeki soru, o yolun yönüdür. Düğme içeriğine renk sağlar. İçerik düğmenin hemen altındaysa renk bir kat aşağı iner. İçerik bir düzenle sarılmışsa renk araya giren düğümü atlayarak iner. Yön bu iki durumda farklı adlar taşır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Düğme ve İçerik Yuvası

Hazır düğme, bir tıklama işlevi ve bir içerik yuvası alır. Yuva, düğmenin içinde ne duracağını çağıran belirler: yalnız bir metin, yalnız bir simge ya da ikisi birden. Düğme bu yuvayı kendi çocuk ağacına yerleştirir ve yuvadaki ögelerin okuyacağı içerik rengini sağlar.

```kotlin
// Platform kodu: hazir dugme ve icerik yuvasi
@Composable
fun SaveButton(onSave: () -> Unit) {
    Button(onClick = onSave) {
        Row {
            Icon(Icons.Default.Check, contentDescription = null)
            Text("Kaydet")
        }
    }
}
```

Buradaki `Row`, yuvanın içinde yazılmıştır, yani düğmenin çocuğudur; `Icon` ve `Text` ise `Row`'un çocuklarıdır. Düğme içerik rengini oluşturma yereli (`LocalContentColor`) üzerinden sağlar. `Icon` ve `Text`, rengi parametre olarak almak yerine bu yerelden okur. Yerelin ayrıntısı tema dersindedir; bu derste yalnız renk hareketinin yönü ölçülür.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Aşağıdaki yardımcı dosya düğmenin ağacını iki biçimde kurar: içerik düğmenin doğrudan altında ya da bir `Row` içinde. Ayrıca düğmenin arka plan rengine göre içerik rengini seçen küçük bir kural içerir ve hangi ögelerin rengi okuduğunu söyler.

```kotlin
// Button.kt — dugme agaci ve icerik rengi (modelin hesabi)
fun buttonTree(content: List<String>, wrapped: Boolean): Tree {
    val parentOf = mutableMapOf("Screen" to "App", "Button" to "Screen")
    val holder = if (wrapped) "Row" else "Button"
    if (wrapped) {
        parentOf["Row"] = "Button"
    }
    for (child in content) {
        parentOf[child] = holder
    }
    return Tree(parentOf)
}

fun contentColorFor(container: String): String =
    if (container == "koyu") "acik" else "koyu"

fun colorReaders(content: List<String>): List<String> =
    content.filter { it.startsWith("Icon") || it.startsWith("Text") }
```

#### Sarılı İçerik

İlk ölçümde içerik bir `Row` ile sarılıdır. Düğme koyu bir arka plan taşır; içerik rengi açık seçilir ve rengi okuyan her ögeye düğmeden gider.

```kotlin
// Main.kt — Row ile sarili icerik
fun main() {
    val content = listOf("Icon", "Text")
    val tree = buttonTree(content, wrapped = true)
    println("icerik rengi: ${contentColorFor("koyu")}")
    val moves = colorReaders(content).map { Move("renk-${it.lowercase()}", "Button", it, tree) }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
icerik rengi: acik
renk-icon: kimden=Button | kime=Icon -> derin
renk-text: kimden=Button | kime=Text -> derin
asagi: 0
yukari: 0
derin: 2
dis: 0
```

İki hareketin ikisi de derin. Renk düğmeden çıktı; hedefin ebeveyni düğme değil `Row` olduğu için hareket doğrudan çocuğa varmadı, bir düğümü atlayarak indi. Araya giren `Row`, renkle hiçbir şey yapmaz: onu ne okur ne değiştirir. Renk onun yanından geçer.

**AD1.** Model düğmenin arka plan rengini iki değerden biri olarak alır ve içerik rengini bu ikiliden seçer. Platformun gerçek renk hesabı, kontrast oranı ve etkin olmayan düğmenin soluk rengi modelde yoktur.

#### Sarısız İçerik

İkinci ölçümde aynı iki öge düğmenin doğrudan altındadır. Düğme aynı rengi veriyor, aynı ögeler aynı rengi okuyor. Değişen tek şey aradaki `Row`'un yokluğudur.

```kotlin
// Main.kt — dogrudan altindaki icerik
fun main() {
    val content = listOf("Icon", "Text")
    val tree = buttonTree(content, wrapped = false)
    val moves = colorReaders(content).map { Move("renk-${it.lowercase()}", "Button", it, tree) }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
renk-icon: kimden=Button | kime=Icon -> asagi
renk-text: kimden=Button | kime=Text -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 0
```

Bu kez iki hareket de aşağı. Renk aynı, kaynak aynı, hedef aynı; yön, aradaki düğümün varlığına bağlı olarak değişti. Bir hareketin yönü verinin kendi özelliği olmadığından, kodu okurken "bu renk aşağı gider" demek yerine "bu renk, bu ağaçta aşağı gider" demek gerekir.

**AD2.** Model yalnız iki ağaç biçimini karşılaştırır. Düğmenin birden çok kat sarılması, sarmalayanın kendi yerel değerini sağlaması ya da içeriğin yuvadan dışarı çıkarılması modelde ölçülmedi.

#### Sınırlayıcı Ölçüm

Dersin yönünü, her iki ağaçtaki hareketleri yan yana koyunca görmek mümkün olur. Aşağıdaki ölçüm üç hareket içerir: arka plan rengi ekrandan düğmeye, sonra iki farklı sarma biçimi için içerik rengi düğmeden simgeye.

```kotlin
// Main.kt — iki agacin yan yana dokumu
fun main() {
    val content = listOf("Icon", "Text")
    val wrappedTree = buttonTree(content, wrapped = true)
    val plainTree = buttonTree(content, wrapped = false)
    val moves = listOf(
        Move("zemin-rengi", "Screen", "Button", plainTree),
        Move("sarili-simge", "Button", "Icon", wrappedTree),
        Move("sarili-metin", "Button", "Text", wrappedTree),
        Move("sarisiz-simge", "Button", "Icon", plainTree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
zemin-rengi: kimden=Screen | kime=Button -> asagi
sarili-simge: kimden=Button | kime=Icon -> derin
sarili-metin: kimden=Button | kime=Text -> derin
sarisiz-simge: kimden=Button | kime=Icon -> asagi
asagi: 2
yukari: 0
derin: 2
dis: 0
```

Bu bloktaki dört satırın ikişer ucu da elle yazılmıştır: bu satırlar iki ağacı yan yana göstermek için yazılmıştır, ölçüm değildir. Dersin derin yönü, uçları `colorReaders` işlevinden gelen önceki ölçüme dayanır.

İki aşağı, iki derin. Ekrandan düğmeye giden zemin rengi doğrudan çocuğa gider: aşağı. Aynı ögeye giden içerik rengi, arada bir düzen varsa derin, yoksa aşağıdır. Hazır arayüz bileşenlerinin ilginç yanı budur: düğme kendi içeriğine rengi veriyor ama içeriğin hangi derinlikte durduğunu bilmiyor. Çağıran yuvaya ne yazarsa yazsın renk oraya ulaşır. Derin yön, bunu mümkün kılan hareket çeşididir.

Bu yüzden hazır arayüz bileşeninin içeriğini düzenlemek renk için güvenlidir; içeriği bir `Row` ya da `Box` ile sarmak rengi kesmez. Renk, ağaç boyunca bir ata tarafından sağlandığı sürece, derindeki ögeye ulaşır. Dersin yönü: **derin**.

**AD3.** Model rengi yalnız `Icon` ve `Text` adlı ögelerin okuduğunu varsayar. Kendi çizimini yapan bir özel ögenin rengi okuması için ne yapması gerektiği bu derste ölçülmedi.

#### Özet

- Bu dersin yönü: derin. Düğme ile içerik arasına bir düzen girince renk, araya giren düğümü atlayarak ulaşır.
- Hazır arayüz bileşeni, ortak davranışı ve görünümü hazır sunar; içeriği bir yuva üzerinden çağıran belirler.
- Düğme içeriğine rengi verir; içerik düğmenin doğrudan altındaysa renk aşağı gider.
- İçerik bir düzenle sarılırsa aynı renk, araya giren düğümü atlayarak derin gider.
- Yön, verinin değil ağacın özelliğidir: aynı renk iki ağaçta iki farklı yön taşır.

#### Sonraki Adım

Düğme, içeriğine renk verdi; iskele ise içeriğine boşluk verir. Sonraki ders, ekranın üst çubuğu, alt çubuğu ve içeriğini bir arada tutan iskelenin içeriğe verdiği boşluğun yönünü ölçer.

### İskele Yapısı

Source: https://academia.sh/tr/kurslar/compose/iskele-yapisi

Ekran iskelesinin içeriğine verdiği iç boşluğun ağaçta aşağı gitmesinin, iskelenin yuvasına yazılan ama ekranın durumunu okuyan içeriğin ise derin bir yoldan beslenmesinin ölçümü.

Bir ekranın üstünde başlık çubuğu, altında gezinme çubuğu, ortasında içerik bulunur. Bunları elle yerleştirmek mümkündür, ama çubukların yüksekliğini içeriğe bildirmek gerekir; aksi halde içeriğin ilk satırı başlık çubuğunun altında kalır. İskele bu işi üstlenir: üst çubuğu, alt çubuğu ve içeriği ayrı yuvalara alır, çubukların kapladığı yeri ölçer ve içeriğe iç boşluk olarak verir.

Bu ders iki veriyi karşılaştırır. Biri iskelenin içeriğe verdiği iç boşluktur ve kaynağı iskelenin kendisidir. Öteki ekranın durumudur, ve içeriğe iskeleden geçmeden ulaşır. İkisi aynı yuvaya varır, ama yönleri farklıdır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Yuvalar ve İç Boşluk

İskele, adlandırılmış yuvalar alır: `topBar`, `bottomBar` ve `content`. İçerik yuvası bir lambdadır ve bu lambda iskelenin hesapladığı iç boşluğu parametre olarak alır. Bu parametre `PaddingValues` türündedir ve dört kenarın boşluğunu taşır.

```kotlin
// Platform kodu: iskele ve icerigine verdigi ic bosluk
@Composable
fun CartScreen(cartCount: Int) {
    Scaffold(
        topBar = { CartTopBar(title = "Sepet ($cartCount)") },
    ) { innerPadding ->
        CartContent(Modifier.padding(innerPadding), cartCount)
    }
}
```

Yuva lambdasındaki `innerPadding`, iskelenin verdiği değerdir; içerik bunu kendi kenar boşluğu olarak kullanır. Kullanılmazsa içerik çubukların altına taşar. Aynı lambda `cartCount`'u da okur. Bu değer lambdanın yazıldığı işlevin, yani `CartScreen`'in parametresidir.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yardımcı dosya, iskelenin yuvalarını ağaca yerleştirir ve çubuk yüksekliklerinden içerik için iç boşluğu hesaplar.

```kotlin
// Scaffold.kt — yuvalar ve ic bosluk (modelin hesabi)
class Insets(val top: Int, val bottom: Int)

fun scaffoldSlots(hasBottomBar: Boolean): List<String> =
    if (hasBottomBar) listOf("TopBar", "Content", "BottomBar") else listOf("TopBar", "Content")

fun scaffoldTree(slots: List<String>): Tree {
    val parentOf = mutableMapOf("Screen" to "App", "Scaffold" to "Screen")
    for (slot in slots) {
        parentOf[slot] = "Scaffold"
    }
    return Tree(parentOf)
}

fun contentInsets(topBar: Int, bottomBar: Int): Insets = Insets(topBar, bottomBar)
```

#### İç Boşluk İskeleden Gelir

İlk ölçümde üst çubuk 56, alt çubuk 80 birim yüksekliktedir. İskele bunlardan içerik için iç boşluğu hesaplar ve içerik yuvasına verir.

```kotlin
// Main.kt — iskelenin icerige verdigi ic bosluk
fun main() {
    val slots = scaffoldSlots(true)
    val tree = scaffoldTree(slots)
    val insets = contentInsets(56, 80)
    println("ic bosluk: ust=${insets.top} alt=${insets.bottom}")
    val move = Move("ic-bosluk", "Scaffold", slots[1], tree)
    move.describe()
}
```

```text
ic bosluk: ust=56 alt=80
ic-bosluk: kimden=Scaffold | kime=Content -> asagi
```

İç boşluk 56 ve 80 olarak içeriğe ulaştı; hareket iskeleden doğrudan çocuğuna gitti: aşağı. İskele içeriği çocuğu olarak kurduğu için bu hareket her zaman bir kat iner. İçerik hangi çubuk düzenini kullanırsa kullansın, boşluk iskelenin hesabından gelir.

**AD1.** Model çubuk yüksekliklerini iki tamsayı olarak alır. Çubukların içeriğe göre uzaması, sistem çubuklarının kapladığı kenar boşlukları ve klavye boşluğu modelde yoktur.

#### Yuva Lambdasının İki Yeri

Yuva lambdası `CartScreen` içinde yazılmıştır, bu yüzden `cartCount`'u yakalayabilir. Ağaçta ise lambdanın ürettiği içerik iskelenin çocuğudur; `CartScreen` ile arasında iskele düğümü vardır. Bir öge kaynak kodda `CartScreen`'in içinde yazıldığı için ağaçta onun doğrudan çocuğu olmaz.

Bu fark yönü belirler. `cartCount`, `CartScreen`'den içeriğe gider. Hareket iskele düğümünün üstünden atlar: iskele bu değeri ne okur ne dönüştürür, yalnız yuvayı yerleştirir. Değer yakalama yoluyla ulaşır.

```kotlin
// Main.kt — ekranin durumu iskelenin ustunden gecer
fun main() {
    val slots = scaffoldSlots(true)
    val tree = scaffoldTree(slots)
    val moves = listOf(
        Move("sepet-sayisi", "Screen", slots[1], tree),
        Move("baslik", "Screen", slots.first(), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
sepet-sayisi: kimden=Screen | kime=Content -> derin
baslik: kimden=Screen | kime=TopBar -> derin
asagi: 0
yukari: 0
derin: 2
dis: 0
```

İkisi de derin. Sepet sayısı içeriğe, başlık üst çubuğa iskeleyi atlayarak gitti. İskeleye hiçbir parametre verilmedi; değerler yuva lambdalarının içinde okundu. Bu durum iskeleyi sağlam kılar: iskele ekranın durumundan habersizdir ve yeni bir durum eklemek iskele çağrısını değiştirmez.

**AD2.** Model yakalama ile ağaç ilişkisini aynı çizgide gösterir. Platformun lambdayı hangi koşulda yeniden çalıştırdığı, yakalanan değerin değişince içeriğin nasıl güncellendiği modelde yok.

#### Sınırlayıcı Ölçüm

Dersin yönü iki hareketten birine bağlı değildir: ikisi de aynı yuvalara varır ve ikisi de gerçektir. Yön, iskeleye ait olan veriyle ekrana ait olan veriyi ayırmaktan çıkar. Aşağıdaki ölçüm dördünü birlikte gösterir: ekranın iskeleye verdiği yuvalar, iskelenin içeriğe verdiği iç boşluk, ve ekranın durumunun içeriğe ve üst çubuğa gidişi.

```kotlin
// Main.kt — iskele cevresindeki dort hareket
fun main() {
    val slots = scaffoldSlots(true)
    val tree = scaffoldTree(slots)
    val moves = listOf(
        Move("yuvalar", "Screen", "Scaffold", tree),
        Move("ic-bosluk", "Scaffold", slots[1], tree),
        Move("sepet-sayisi", "Screen", slots[1], tree),
        Move("baslik", "Screen", slots.first(), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
yuvalar: kimden=Screen | kime=Scaffold -> asagi
ic-bosluk: kimden=Scaffold | kime=Content -> asagi
sepet-sayisi: kimden=Screen | kime=Content -> derin
baslik: kimden=Screen | kime=TopBar -> derin
asagi: 2
yukari: 0
derin: 2
dis: 0
```

`yuvalar` satırının iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan `ic-bosluk`, `sepet-sayisi` ve `baslik` satırlarının bir ucu modelin yuvalarından gelir: aşağı 1, derin 2.

İki aşağı, iki derin. İskeleye giren ve iskeleden içeriğe çıkan her şey bir kat iner: iskelenin kendi sözleşmesi budur. Ekranın durumu ise bu sözleşmenin dışında, yakalama yoluyla iner ve iskeleyi atlar. İç boşluk yalnız iskelede bilinen çubuk yüksekliklerinden türer; durum ise yalnız yazıldığı yerde bilinir. Her veri, bilindiği yerden gelir. İskelenin sözleşmesindeki hareketin yönü: **aşağı**.

**AD3.** Model dört hareketi tek bir çubuk düzeninde kurar. Çubuksuz bir iskelede iç boşluk sıfır olurdu, ama bu dersin ölçümleri onu kapsamaz.

#### Özet

- Bu dersin yönü: aşağı. İskelenin kendi sözleşmesindeki hareket, iç boşluğun iskeleden içeriğe inmesidir.
- İskele üst çubuk, alt çubuk ve içerik yuvalarını yerleştirir ve çubukların kapladığı yeri içeriğe iç boşluk olarak verir.
- İç boşluk iskeleden içeriğe, doğrudan çocuğa gider: aşağı.
- Yuva lambdası ekranın içinde yazılır, ama ağaçta iskelenin çocuğudur.
- Lambdanın yakaladığı ekran durumu iskeleyi atlar: derin.

#### Sonraki Adım

İskele içeriğini ekranın içinde yerleştirir; ama kullanıcı bir ekrandan ötekine geçtiğinde yerleştirilecek içerik değişir. Sonraki ders, ekranlar arası gezinmeyi, rotalarla hedefi seçmeyi ve henüz ağaçta olmayan bir hedefe argüman götürmeyi ölçer.

### Gezinme ve Rotalar

Source: https://academia.sh/tr/kurslar/compose/gezinme-ve-rotalar

Rotayla seçilen hedefin gezinme bitene kadar arayüz ağacında bulunmamasının, bu yüzden hedefe giden argümanın önce dış, sonra aşağı gitmesinin ve geri dönen sonucun ölçümü.

Şimdiye kadar ölçülen her hareketin iki ucu da ağaçtaydı. Gezinmede bu değişir. Kullanıcı bir listeden ürün ayrıntısına geçtiğinde ayrıntı ekranı, geçişten önce ağaçta değildir. Ona götürülecek ürün numarası ise geçişin başlangıcında bellidir. Veri hedefinden önce vardır.

Bu ders, gezinme kabının (`NavHost`) bu boşluğu nasıl kapattığını ölçer. Rota hedefi seçer; hedef ağaca girer; argüman o zaman varır. Aynı argümanın iki anda iki farklı yön taşıması, bu dersin konusudur.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Rota, Hedef ve Geri Yığını

Gezinme düzeni üç parçadan oluşur. Gezinme denetleyicisi (`NavController`), hangi hedefte olunduğunu ve geri yığınını tutar. Gezinme kabı (`NavHost`) ağaçta durur ve o anki hedefin içeriğini çocuğu olarak çizer. Rota, bir hedefin metin adresidir; argüman rotanın içinde taşınır: `detail/42`, `detail` hedefine `42` argümanını götürür.

```kotlin
// Platform kodu: gezinme kabi, rotalar ve argumanli hedef
@Composable
fun AppNavigation(navController: NavHostController) {
    NavHost(navController, startDestination = "list") {
        composable("list") {
            ProductList(onOpen = { id -> navController.navigate("detail/$id") })
        }
        composable("detail/{id}") { entry ->
            ProductDetail(entry.arguments?.getString("id"))
        }
    }
}
```

`navigate("detail/42")` çağrıldığında platform rotayı ayrıştırır, uyan hedefi bulur, geri yığınına yeni bir kayıt ekler ve kabın içeriğini değiştirir. Hedefin içeriği ancak bu adımlardan sonra ağaca girer. `ProductDetail` o ana kadar hiçbir yerde çizilmiş değildir.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yardımcı dosya rotayı ayrıştırıp bir hedef adı verir ve gezinme kabının altında bir hedefin bulunduğu ağacı kurar.

```kotlin
// Routes.kt — rota ayristirma ve hedef secimi (modelin hesabi)
class Route(val destination: String, val argument: String)

fun parseRoute(route: String): Route {
    val parts = route.split("/")
    return Route(parts[0], parts.getOrElse(1) { "" })
}

fun routeTarget(route: String, graph: List<String>): String? {
    val destination = parseRoute(route).destination
    return graph.firstOrNull { it.lowercase() == destination }
}

fun navTree(current: String): Tree =
    Tree(mapOf("Screen" to "App", "NavHost" to "Screen", current to "NavHost"))
```

#### Gezinmeden Önce

İlk ölçümde ağaçta liste hedefi çizili. Kullanıcı `detail/42` rotasına gitmek ister. Model rotayı ayrıştırır ve hedefi seçer; ama ağaç henüz gezinmeden önceki ağaçtır.

```kotlin
// Main.kt — gezinme baslamadan hedefe giden arguman
fun main() {
    val graph = listOf("List", "Detail")
    val route = "detail/42"
    val target = routeTarget(route, graph) ?: return
    println("hedef: $target")
    println("arguman: ${parseRoute(route).argument}")
    val before = navTree("List")
    Move("arguman", "NavHost", target, before).describe()
}
```

```text
hedef: Detail
arguman: 42
arguman: kimden=NavHost | kime=Detail -> dis
```

Hedef `Detail` seçildi, argüman `42` ayrıştırıldı; ama `Detail` ağaçta olmadığı için hareket dış çıktı. Veri hazır, alıcı yok. Platform argümanı bu aralıkta geri yığınındaki kayıtta saklar; çizim başlayınca hedef onu oradan okur.

**AD1.** Model rotada en çok bir argüman taşır ve onu metin olarak bırakır. Platformun argüman türlerini, varsayılan değerlerini ve rotadaki sorgu parametrelerini ayrıştırması modelde yoktur.

#### Gezinmeden Sonra

İkinci ölçümde gezinme tamamlanmıştır: kabın altında artık `Detail` vardır. Aynı kaptan aynı hedefe aynı argüman gider.

```kotlin
// Main.kt — gezinme bittikten sonra arguman
fun main() {
    val graph = listOf("List", "Detail")
    val target = routeTarget("detail/42", graph) ?: return
    val after = navTree(target)
    Move("arguman", "NavHost", target, after).describe()
}
```

```text
arguman: kimden=NavHost | kime=Detail -> asagi
```

Aynı iki uç, bu kez aşağı. Hedef ağaca girdiği için kabın doğrudan çocuğudur. Değişen yalnız ağaçtır: veri, kaynak ve hedef aynı.

**AD2.** Model gezinmeyi tek adımda, ağacın bir anda öncesinden sonrasına geçmesi olarak kurar. Platformdaki geçiş animasyonu sırasında eski ve yeni hedefin aynı anda ağaçta bulunması modelde yoktur.

#### Geri Dönen Sonuç

Gezinme tek yönlü değildir. Kullanıcı ayrıntıdan geri döndüğünde ayrıntı ekranı ağaçtan çıkar ve liste yeniden çizilir. Ayrıntı ekranı bir sonuç bırakmışsa, örneğin ürün sepete eklendiğini, bu sonuç listeye geri yığınındaki kayıt üzerinden ulaşır. Sonucun yazıldığı an ayrıntı ekranı hâlâ ağaçtadır, ama liste okuduğunda ayrıntı çoktan gitmiştir.

```kotlin
// Main.kt — geri donuste ayrintinin biraktigi sonuc
fun main() {
    val back = navTree("List")
    Move("sonuc", "Detail", "List", back).describe()
}
```

```text
sonuc: kimden=Detail | kime=List -> dis
```

Bu satırın iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Geri dönen sonucun dış olması, gönderen ucu `routeTarget` çıktısı olan sınırlayıcı ölçümdeki `sonuc` satırına da dayanır.

Dış. Geri dönüş tamamlandığında gönderen düğüm ağaçta yoktur. Sonuç ağaç üzerinden değil, gezinme denetleyicisinin tuttuğu kayıt üzerinden taşınır. Argüman ileri gezinmede, sonuç geri gezinmede ağaç dışıdır; her ikisi de geçişin iki yanından birinde ağaçta bulunmayan bir uca bağlıdır.

**AD3.** Model sonucu yalnız geri dönüş anından ölçer. Sonucun ayrıntı ekranı hâlâ ağaçtayken yazıldığı an modelde ayrı bir hareket olarak kurulmadı.

#### Sınırlayıcı Ölçüm

Dersin yönü, argümanın gezinmenin öncesi ve sonrasında nasıl bölündüğünden çıkar. Aşağıdaki ölçüm dört hareketi birlikte gösterir: ekranın gezinme kabına verdiği başlangıç rotası, gezinme öncesi argüman, gezinme sonrası argüman, geri dönen sonuç.

```kotlin
// Main.kt — gezinmenin dort hareketi
fun main() {
    val graph = listOf("List", "Detail")
    val target = routeTarget("detail/42", graph) ?: return
    val before = navTree("List")
    val after = navTree(target)
    val moves = listOf(
        Move("baslangic-rotasi", "Screen", "NavHost", before),
        Move("arguman-once", "NavHost", target, before),
        Move("arguman-sonra", "NavHost", target, after),
        Move("sonuc", target, "List", before),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
baslangic-rotasi: kimden=Screen | kime=NavHost -> asagi
arguman-once: kimden=NavHost | kime=Detail -> dis
arguman-sonra: kimden=NavHost | kime=Detail -> asagi
sonuc: kimden=Detail | kime=List -> dis
asagi: 2
yukari: 0
derin: 0
dis: 2
```

`baslangic-rotasi` satırının iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan üç satırın bir ucu `routeTarget` çıktısıdır: aşağı 1, dış 2.

İki aşağı, iki dış. Başlangıç rotası ve gezinmeden sonraki argüman, ağaçtaki iki bağlı düğüm arasında olduğu için aşağı gitti. Gezinmeden önceki argüman ve geri dönen sonuç, bir ucu ağaçta olmadığı için dış kaldı. Gezinme, ağaç dışı iki hareketi yönetmek için vardır; yön, hareketin hangi anda ölçüldüğüne bağlıdır. Dersin yönü: **dış**.

**AD4.** Model dört hareketi ağacın iki anından alır. Platformda bu anlar arasında geçen sürenin, argümanın hangi kayıtta durduğunun ya da süreç yeniden başlayınca neyin korunduğunun ölçümü bu derste yok.

#### Özet

- Bu dersin yönü: dış. Gezinmeden önceki argüman ve geri dönen sonuç, ağaçta bulunmayan bir uca bağlıdır.
- Rota hedefin metin adresidir; argüman rotanın içinde taşınır.
- Gezinme bitmeden hedef ağaçta değildir, bu yüzden ona giden argüman dıştır.
- Hedef ağaca girince aynı argüman kabın çocuğuna aşağı gider.
- Geri dönen sonuç, gönderen ağaçtan çıktıktan sonra okunduğu için dıştır.

#### Sonraki Adım

Gezinme hangi içeriğin çizileceğini seçer, tema ise çizilen her içeriğin nasıl görüneceğini belirler. Sonraki ders, kökte seçilen temanın derindeki metne kadar nasıl indiğini ve alt ağacın kendi temasını sağladığında yönün nasıl değiştiğini ölçer.

### Tema ve Tipografi

Source: https://academia.sh/tr/kurslar/compose/tema-ve-tipografi

Kökte sağlanan temanın ağaçta derindeki metne kadar inmesinin, bir alt ağacın kendi temasını sağlayınca en yakın sağlayıcının değişmesinin ve yönün buna bağlı değişiminin ölçümü.

Bir uygulamanın renkleri, yazı tipi boyutları ve köşe yuvarlaklıkları tek bir yerde seçilir ve her ekranda kullanılır. Bu değerleri her bileşen işlevine parametre olarak vermek mümkündür, ama ağaç derinleştikçe parametre zinciri uzar ve aradaki her işlev, yalnız iletmek için bir parametre taşır. Tasarım sistemleri bunu başka türlü çözer: değer ağacın bir noktasında sağlanır, altındaki herkes onu okur.

Bu ders, o çözümün yönünü ölçer. Tema kökte sağlanır ve derindeki metne iner; aradaki hiçbir işlev onu taşımaz. Bir alt ağaç kendi temasını sağladığında ise okuyucu için en yakın sağlayıcı değişir, ve hareketin hangi düğümden çıktığı da değişir.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Tema Bir Sağlayıcıdan İner

Tema, bir sarmalayıcı işlevle sağlanır: bu işlev renk şemasını, tipografiyi ve şekilleri içeren değerleri altındaki içerik için kurar. Altındaki bir metin bu değerlere tema nesnesi üzerinden erişir. Erişimin altında oluşturma yereli adı verilen mekanizma vardır: bir yerel, ağacın bir düğümünde değer alır ve o düğümün altındaki her işlev onu okuyabilir.

```kotlin
// Platform kodu: tema saglayici ve metnin temayi okumasi
@Composable
fun AppScreen() {
    MaterialTheme(colorScheme = lightColorScheme()) {
        Header {
            Text(
                text = "Siparisler",
                color = MaterialTheme.colorScheme.primary,
                style = MaterialTheme.typography.titleLarge,
            )
        }
    }
}
```

`Text`, rengi ve yazı stilini `AppScreen`'den parametre olarak almaz; temadan okur. Araya giren `Header` temadan habersizdir ve onu iletmez. Yerel, değeri kendiliğinden altındakilere ulaştırır.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yardımcı dosya iki şey yapar: bir ekranın ağacını kurar ve bir okuyucu için en yakın tema sağlayıcısını ataları arasından seçer.

```kotlin
// Theme.kt — agac ve en yakin saglayici (modelin hesabi)
fun themeTree(): Tree = Tree(
    mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "Title" to "Header",
        "Panel" to "Screen",
        "Label" to "Panel",
    )
)

fun closestTheme(tree: Tree, providers: List<String>, reader: String): String =
    tree.ancestors(reader).first { it in providers }
```

`ancestors` listesi okuyucudan köke doğru sıralıdır; ilk eşleşen sağlayıcı en yakın olandır.

#### Kökten Derine

İlk ölçümde yalnız ekran kökü tema sağlar. İki okuyucu vardır: başlık metni `Header` içinde, etiket `Panel` içinde. Her ikisi de temayı en yakın sağlayıcıdan, yani kökten alır.

```kotlin
// Main.kt — tek saglayici varken tema hareketleri
fun main() {
    val tree = themeTree()
    val providers = listOf("Screen")
    val moves = listOf("Title", "Label").map { reader ->
        Move("tema-$reader", closestTheme(tree, providers, reader), reader, tree)
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
tema-Title: kimden=Screen | kime=Title -> derin
tema-Label: kimden=Screen | kime=Label -> derin
asagi: 0
yukari: 0
derin: 2
dis: 0
```

İki hareket de derin. Tema `Screen`'den çıkıp iki kat aşağıdaki metne ulaştı, aradaki düğümleri atlayarak. Hiçbir ara düğüm temayı bilmedi.

**AD1.** Model sağlayıcıları ad listesi olarak alır ve temanın içeriğini taşımaz. Hangi rengin, hangi yazı stilinin geldiği modelde yoktur; yalnız değerin hangi düğümden hangi düğüme gittiği ölçülür.

#### Alt Ağaç Kendi Temasını Sağlar

İkinci ölçümde `Panel`, kendi temasını sağlar; örneğin koyu bir paneldir. `Title` için hiçbir şey değişmez, ama `Label` için en yakın sağlayıcı artık `Panel`'dir.

```kotlin
// Main.kt — Panel kendi temasini saglarken
fun main() {
    val tree = themeTree()
    val providers = listOf("Screen", "Panel")
    val moves = listOf("Title", "Label").map { reader ->
        Move("tema-$reader", closestTheme(tree, providers, reader), reader, tree)
    }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
tema-Title: kimden=Screen | kime=Title -> derin
tema-Label: kimden=Panel | kime=Label -> asagi
asagi: 1
yukari: 0
derin: 1
dis: 0
```

`Title` için hareket aynı kaldı: derin. `Label` için ise çıkış noktası kökten `Panel`'e geçti, ve `Label` panelin doğrudan çocuğu olduğundan hareket aşağı oldu. Aynı okuyucu, aynı tema nesnesi; ama sağlayıcı değişince yön değişti. Kökün temasını `Label` artık hiçbir yoldan almaz: araya giren sağlayıcı onu gölgeler.

**AD2.** Model bir okuyucunun tek bir sağlayıcıdan okuduğunu varsayar. Paneldeki bir değerin yalnız bir kısmını (örneğin yalnız rengi) değiştirip geri kalanını kökten bırakmak modelde yok.

#### Sınırlayıcı Ölçüm

Dersin yönü kökün temayı derindeki ögeye vermesinden gelir; panelin kendi sağlayıcılığı bunu daraltır. Aşağıdaki ölçüm üç hareket içerir: kökün başlığa verdiği tema, panelin etikete verdiği tema ve panelin kendi temasını nereden aldığı.

```kotlin
// Main.kt — panel kendi temasini saglarken uc hareket
fun main() {
    val tree = themeTree()
    val providers = listOf("Screen", "Panel")
    val moves = listOf(
        Move("baslik", closestTheme(tree, providers, "Title"), "Title", tree),
        Move("etiket", closestTheme(tree, providers, "Label"), "Label", tree),
        Move("panel-kaynagi", "Screen", "Panel", tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
baslik: kimden=Screen | kime=Title -> derin
etiket: kimden=Panel | kime=Label -> asagi
panel-kaynagi: kimden=Screen | kime=Panel -> asagi
asagi: 2
yukari: 0
derin: 1
dis: 0
```

`panel-kaynagi` satırının iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan `baslik` ve `etiket` satırlarının sağlayıcı ucu `closestTheme` işlevinden gelir: derin 1, aşağı 1.

Bir derin, iki aşağı. Panelin temayı kökten türetmesi de, panelin etikete vermesi de doğrudan çocuğa gider. Derin olan tek hareket, aradaki `Header` düğümünü atlayan başlık temasıdır; yerel mekanizmasının asıl işi budur. Doğrudan çocuğa tema vermek için yerele gerek olmazdı: parametre yeterdi. Yerel, aradaki düğüm sayısı bilinmediğinde ya da değiştiğinde anlam kazanır. Dersin yönü: **derin**.

**AD3.** Model tema değişince ne olacağını ölçmez. Sağlayıcının değeri değiştiğinde onu okuyan işlevlerin yeniden çalışması ve bunun maliyeti bu derste ele alınmadı.

#### Özet

- Bu dersin yönü: derin. Kökte sağlanan tema, aradaki `Header` düğümünü atlayarak başlığa ulaşır.
- Tema ağacın bir noktasında sağlanır; altındaki her işlev onu okuyabilir.
- Kökte sağlanan tema, aradaki düğümleri atlayarak derindeki metne ulaşır: derin.
- Okuyucu için sağlayıcı, ataları arasındaki en yakın sağlayıcıdır.
- Alt ağaç kendi temasını sağlarsa o ağaçtaki okuyucular için kök gölgelenir; doğrudan çocuğa giden değer aşağı gider.

#### Sonraki Adım

Tema, değerleri ağaçta sabit bir noktadan dağıtır. Animasyon ise değerin zamanla değişmesidir ve değişimi ağacın dışında bir saat sürer. Sonraki ders, kare saatinden gelen ara değerin ağaçta nereden geldiğini ve hedef değerin hangi yönde gittiğini ölçer.

### Animasyonlar

Source: https://academia.sh/tr/kurslar/compose/animasyonlar

Bir animasyonun iki hareket taşımasının ölçümü: hedef değer durumun sahibinden aşağı gider, kare başına ara değer ise ağacın dışındaki kare saatinden gelir ve düğüme dış bir hareketle ulaşır.

Düğme basıldığında bir panelin genişliği 0'dan 100'e çıkıyor olsun. Durum tek adımda değişir: genişlik 100 olmalıdır. Ekranda ise değişim bir dizi karede görünür ve her karede genişlik bir öncekinden biraz farklıdır. Bu ara değerleri üreten şey durumun sahibi değildir, çünkü sahibi yalnız hedefi bilir.

Bu ders, animasyonun taşıdığı iki veriyi ayırır. Biri hedeftir ve ağaçtaki bir atadan gelir. Öteki kare başına değerdir ve ağacın dışındaki bir saatten gelir. Aynı animasyon, aynı düğüme iki farklı yönden ulaşır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Hedef ve Ara Değer

Durum tabanlı animasyonlar bir hedef değer alır ve geçerli değeri hedefe doğru yürütür. Hedef bir durumdur ve durumun sahibi ağaçtaki bir bileşen işlevidir. Platformun kare saati ise ağaç dışında çalışır: ekran her yenilendiğinde saat bir kare ilerler ve animasyon o karedeki değeri hesaplar.

```kotlin
// Platform kodu: hedefe dogru animasyon
@Composable
fun ExpandingPanel(expanded: Boolean) {
    val width by animateDpAsState(
        targetValue = if (expanded) 100.dp else 0.dp,
        label = "panelWidth",
    )
    Box(Modifier.width(width))
}
```

`expanded`, `ExpandingPanel`'e dışarıdan verilen durumdur ve hedefi belirler. `animateDpAsState`, o hedefe giden bir ara değer üretir. `Box` her karede bu değerle yeniden ölçülür. Hedef değişince animasyon yeniden başlar; kare saati devam eder.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yardımcı dosya iki şey yapar: kare saatinin ürettiği ara değerleri doğrusal olarak hesaplar ve animasyonlu bir paneli ekranın altına yerleştirir. Doğrusal ara değer hesabı modelin hesabıdır; platformun yumuşatma eğrisi ve süre ayarı burada yoktur. Ölçümlerin uçlarını da iki işlev verir: `targetOwner` panelin ebeveynini ağaçtan okur, `clockSource` kare saatinin adını döndürür. Bu derste hareketin bir ucu ağaçtan bulunur (`targetOwner`); öteki uç, modelin adlandırdığı ve ağaçta olmayan bir kaynaktır (`clockSource` sabit bir ad döndürür).

```kotlin
// Frames.kt — kare saatinin urettigi ara degerler (modelin hesabi)
fun frameValues(from: Int, to: Int, steps: Int): List<Int> =
    (0..steps).map { from + (to - from) * it / steps }

fun animatedTree(): Tree = Tree(mapOf("Screen" to "App", "Panel" to "Screen"))

// hedefin sahibi: panelin ebeveyni, agactan okunur
fun targetOwner(tree: Tree): String = tree.parent("Panel") ?: error("panel kokte")

// ara degerleri ureten kaynak: agacin disindaki kare saati
fun clockSource(): String = "FrameClock"
```

#### Hedef Aşağı Gider

İlk ölçüm hedefi izler. `Screen`, panelin açık ya da kapalı olduğunu bilir ve hedef genişliği ona verir.

```kotlin
// Main.kt — animasyonun hedef degeri
fun main() {
    val tree = animatedTree()
    println("hedef: ${frameValues(0, 100, 2).last()}")
    Move("hedef", targetOwner(tree), "Panel", tree).describe()
}
```

```text
hedef: 100
hedef: kimden=Screen | kime=Panel -> asagi
```

Hedef `Screen`'den `Panel`'e aşağı gitti. Bu, önceki derslerdeki durum akışıyla aynı harekettir: sahibi atadadır, tüketici çocuktur. Animasyon varlığı hedefin yönünü değiştirmez.

**AD1.** Model hedefi tek bir tamsayı olarak taşır. Hedefin bir renk, bir uzaklık ya da bir saydamlık olması, ve animasyonun her türde ayrı bir işlevle yazılması modelde yoktur.

#### Ara Değer Dışarıdan Gelir

İkinci ölçüm kare başına değerleri izler. Panel her karede 0 ile 100 arasında yeni bir genişlik alır. Bu değerleri üreten kare saati ağaçta bir düğüm değildir.

```kotlin
// Main.kt — kare saatinden gelen ara degerler
fun main() {
    val tree = animatedTree()
    val frames = frameValues(0, 100, 2)
    println("kareler: ${frames.joinToString(",")}")
    val moves = frames.map { Move("kare-$it", clockSource(), "Panel", tree) }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
kareler: 0,50,100
kare-0: kimden=FrameClock | kime=Panel -> dis
kare-50: kimden=FrameClock | kime=Panel -> dis
kare-100: kimden=FrameClock | kime=Panel -> dis
asagi: 0
yukari: 0
derin: 0
dis: 3
```

Üç karenin üçü de dış. Saat ağaçta olmadığı için hareketin bir ucu ağaç dışıdır; hedef değerin aksine ara değer, ağaçtaki hiçbir atadan gelmez. Panel her kareyi bir ata aracılığıyla değil, doğrudan saatten alır.

**AD2.** Model üç kare üretir ve araları eşit böler. Platformun karelerinin sayısı ve süresi, yavaşlayıp hızlanan eğrileri ve animasyon sürerken hedefin değişmesi modelde ölçülmedi.

#### Ara Değeri Kim Tutar

Ara değerin dış olması bir tasarım sonucudur. Ara değer her karede değişir; sahibi bir ata olsaydı, her kare o atanın altındaki her şeyin yeniden hesaplanmasına yol açardı. Bunun yerine ara değeri okuyan düğüm, değeri doğrudan saatten izler ve yalnız kendisi yeniden çizilir. Hedef ise nadiren değişir ve değiştiğinde ağaçtan inmesi doğaldır. Hız farkı iki verinin farklı yollardan gelmesinin gerekçesidir; bu derste hız ölçülmez, yalnız yolun yönü ölçülür.

#### Sınırlayıcı Ölçüm

Bir animasyonun iki hareket taşıması, dersin ana bulgusudur. Üçüncü bir hareket de vardır: animasyon bittiğinde ekrana bildirim gider. Aşağıdaki ölçüm üçünü bir arada gösterir.

```kotlin
// Main.kt — bir animasyonun tasidigi hareketler
fun main() {
    val tree = animatedTree()
    val frames = frameValues(0, 100, 2)
    val moves = listOf(
        Move("hedef", targetOwner(tree), "Panel", tree),
        Move("kare-${frames[1]}", clockSource(), "Panel", tree),
        Move("bitis", "Panel", targetOwner(tree), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
hedef: kimden=Screen | kime=Panel -> asagi
kare-50: kimden=FrameClock | kime=Panel -> dis
bitis: kimden=Panel | kime=Screen -> yukari
asagi: 1
yukari: 1
derin: 0
dis: 1
```

Bir aşağı, bir yukarı, bir dış. Hedef ağaç içi bir hareket, ara değer ağaç dışı bir hareket, bitiş bildirimi ise yine ağaç içi ama ters yönde bir harekettir. Dersin yönü animasyona özgü olan harekettir: ara değer. Hedef ve bitiş bildirimi, animasyonun olmadığı bir ekranda da aynı biçimde bulunurdu. Ara değer yalnız animasyonda vardır, ve her zaman ağacın dışından gelir. Dersin yönü: **dış**.

Görünürlük animasyonu ve geçiş nesnesi aynı yapıdadır. Görünürlük animasyonu, bir ögenin ağaca girişini ve çıkışını animasyonla sarar; geçiş nesnesi (`updateTransition`), tek bir hedef durumdan birden çok değer türetir. İkisinde de hedef ağaçtan gelir, kare başına değerler saatten.

**AD3.** Model bitiş bildirimini tek bir hareketle kurar. Bildirimin hangi karede tetiklendiği ve animasyon kesildiğinde ne olduğu modelde ölçülmedi. Görünürlük animasyonu ve geçiş nesnesi bu derste yalnız kaynak parçasıyla anlatılır, ölçülmez.

#### Özet

- Bu dersin yönü: dış. Ara değeri üreten kare saati ağaçta olmadığı için dersin ölçtüğü hareket ağaç dışından gelir.
- Animasyon iki veri taşır: hedef değer ve kare başına ara değer.
- Hedef durumun sahibinden gelir ve çocuğa aşağı gider.
- Ara değeri kare saati üretir; saat ağaçta olmadığı için hareket dıştır.
- Animasyon bitince ekrana giden bildirim yukarı gider.

#### Sonraki Adım

Animasyon, ekranı herkese aynı değişimle gösterir. Ekran okuyucu kullanan biri için ise ekran, ağaçtan türetilen ayrı bir anlam ağacıdır. Sonraki ders, bu anlam ağacında çocukların etiketlerinin ataya nasıl birleştiğini ve bunun yönünü ölçer.

### Erişilebilirlik

Source: https://academia.sh/tr/kurslar/compose/erisilebilirlik

Anlamsal ağaçta çocukların etiketlerinin birleştiren ataya yukarı gitmesinin, doğrudan ve dolaylı çocuklar için aynı yönün korunmasının ve birleşen etiketin ekran okuyucuya dış bir hareketle ulaşmasının ölçümü.

Ekranı göremeyen bir kullanıcı arayüzü ekran okuyucuyla kullanır. Okuyucu piksellere değil, platformun arayüz ağacından türettiği anlamsal ağaca bakar. Bu ağaç, her ögenin ne olduğunu, ne dediğini ve ne yapabildiğini taşır. Bir satırda simge, ürün adı ve fiyat varsa, ekran okuyucunun üç ayrı duraktan geçmesi yerine tek bir cümle okuması istenir: ürün adı, fiyat.

Bu birleştirme ağaç üzerinde yönü olan bir harekettir. Çocukların etiketleri, birleştiren atanın etiketine katılır. Bu dersin ölçüsü o hareketin yönüdür.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Anlamsal Ağaç ve Birleştirme

Her arayüz ögesi anlamsal ağaçta bir düğümdür. Düğüm; bir içerik açıklaması, bir rol ve eylemler taşıyabilir. Varsayılan olarak her düğüm ayrı bir durak olarak sunulur. Bir ata düğüm alt ağacını birleştirmek istediğinde, çocuklarının açıklamaları ona katılır ve ekran okuyucu ata düğümü tek durak olarak okur.

```kotlin
// Platform kodu: satirin anlamsal olarak birlestirilmesi
@Composable
fun CartRow(name: String, price: String) {
    Row(Modifier.semantics(mergeDescendants = true) {}) {
        Icon(Icons.Default.ShoppingCart, contentDescription = null)
        Text(name)
        Text(price)
    }
}
```

`mergeDescendants = true`, bu satırın altındaki düğümlerin açıklamalarını satıra katar. Simgeye `contentDescription = null` verilmiştir: süs niteliğindeki bir ögenin okunması bir şey eklemez. Ekran okuyucu satırı "ürün adı, fiyat" olarak tek seferde okur. Birleştirmeyi bırakıp tüm alt ağacın yerine kendi açıklamasını vermek için `clearAndSetSemantics` kullanılır. Hazır arayüz bileşenleri, eylem alabildiklerinde dokunma hedefini en az bir boyuta tamamlar; kendi yazılan dokunulabilir ögede bu boyutu geliştirici verir. Bu boyut, düzen dersindeki boyut ve boşluk ilişkisi gibi platformun kuralıdır ve bu derste ölçülmez.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yardımcı dosya birleştiren satırın ağacını kurar, hangi düğümlerin etiket taşıdığını ve birleşen etiketin metnini hesaplar. Ölçümlerin uçlarını da iki işlev verir: `screenReader` ekran okuyucunun adını döndürür, `mergeOwner` satırın ebeveynini ağaçtan okur. Bu derste hareketin bir ucu ağaçtan bulunur (`mergeOwner`); öteki uç, modelin adlandırdığı ve ağaçta olmayan bir kaynaktır (`screenReader` sabit bir ad döndürür).

```kotlin
// Semantics.kt — anlamsal agac ve birlesen etiket (modelin hesabi)
fun semanticsTree(): Tree = Tree(
    mapOf(
        "Screen" to "App",
        "Row" to "Screen",
        "Icon" to "Row",
        "Title" to "Row",
        "Wrap" to "Row",
        "Price" to "Wrap",
    )
)

fun screenReader(): String = "ScreenReader"

fun mergeOwner(tree: Tree): String = tree.parent("Row") ?: error("satir kokte")

fun labelHolders(labels: Map<String, String>): List<String> = labels.keys.sorted()

fun mergedDescription(labels: Map<String, String>): String =
    labelHolders(labels).joinToString(", ") { labels.getValue(it) }
```

#### Etiketler Ataya Çıkar

İlk ölçümde iki düğüm etiket taşır: ürün adı doğrudan satırın çocuğudur, fiyat ise araya giren bir sarmalayıcının altındadır, yani satırın torunudur. Simge süs olduğu için etiketsizdir.

```kotlin
// Main.kt — etiket tasiyan dugumlerin birlesen atasi
fun main() {
    val tree = semanticsTree()
    val labels = mapOf("Title" to "Kalem", "Price" to "Bedel")
    println("birlesen etiket: ${mergedDescription(labels)}")
    val moves = labelHolders(labels).map { Move("etiket-$it", it, "Row", tree) }
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
birlesen etiket: Bedel, Kalem
etiket-Price: kimden=Price | kime=Row -> yukari
etiket-Title: kimden=Title | kime=Row -> yukari
asagi: 0
yukari: 2
derin: 0
dis: 0
```

Her iki hareket de yukarı. Doğrudan çocuğun etiketi bir kat, torunun etiketi iki kat yukarı çıktı. Derin yönü yalnız aşağıya doğrudur; ataya çıkışta çocuk ya da torun fark etmez, hareket yukarıdır. Araya giren sarmalayıcı etiketi ne taşır ne değiştirir.

**AD1.** Model etiketleri tek birer sözcük olarak alır ve onları ad sırasıyla birleştirir. Platformun etiketleri okuma sırası, rol ve eylem bilgilerini katması ve birleşme koşulları (örneğin tıklanabilir bir düğümün kendi sınırını koruması) modelde yoktur.

#### Etiket Ekran Okuyucuya Gider

Birleşen etiket satırda kalmaz; ekran okuyucuya iletilir. Ekran okuyucu arayüz ağacının bir düğümü değildir. Platformun erişilebilirlik hizmetidir ve ağacı dışarıdan okur.

```kotlin
// Main.kt — birlesen etiketin ekran okuyucuya gidisi
fun main() {
    val tree = semanticsTree()
    val labels = mapOf("Title" to "Kalem", "Price" to "Bedel")
    println("okunan: ${mergedDescription(labels)}")
    Move("okuma", "Row", screenReader(), tree).describe()
}
```

```text
okunan: Bedel, Kalem
okuma: kimden=Row | kime=ScreenReader -> dis
```

Dış. Ekran okuyucu ağaçta olmadığı için hareketin bir ucu ağaç dışındadır. Satır, etiketi okuyucuya vermez; okuyucu etiketi satırdan alır. İki ayrı yönün zinciri vardır: etiketler ağaçta yukarı çıkar, ağacın tepesinde bekleyen birleşik etiket dışarıdan okunur.

**AD2.** Model okuyucuyu ağaçta bulunmayan bir ad olarak kurar. Okuyucunun odak sırası, kullanıcının hangi düğüme dokunduğu ve etiketin ne zaman okunduğu modelde yok.

#### Sınırlayıcı Ölçüm

Dersin yönü, etiketlerin ataya çıkışından gelir. Etiketin okuyucuya ulaşması dış, birleştirmenin ata tarafından açılması ise aşağıdır: ekran satıra birleştirme bayrağını verir. Aşağıdaki ölçüm dördünü bir arada gösterir.

```kotlin
// Main.kt — erisilebilirlik hareketleri
fun main() {
    val tree = semanticsTree()
    val labels = mapOf("Title" to "Kalem", "Price" to "Bedel")
    val moves = labelHolders(labels).map { Move("etiket-$it", it, "Row", tree) } + listOf(
        Move("birlestirme-bayragi", mergeOwner(tree), "Row", tree),
        Move("okuma", "Row", screenReader(), tree),
    )
    for (move in moves) {
        move.describe()
    }
    countDirections(moves)
}
```

```text
etiket-Price: kimden=Price | kime=Row -> yukari
etiket-Title: kimden=Title | kime=Row -> yukari
birlestirme-bayragi: kimden=Screen | kime=Row -> asagi
okuma: kimden=Row | kime=ScreenReader -> dis
asagi: 1
yukari: 2
derin: 0
dis: 1
```

İki yukarı, bir aşağı, bir dış. Birleştirme bayrağı ekrandan satıra iner; etiketler satıra çıkar; birleşen etiket okuyucuya dışarıdan ulaşır. Yön ayrımı işlev ayrımıyla örtüşür: karar yukarıdan aşağı verilir, bilgi aşağıdan yukarı toplanır, okuma dışarıda gerçekleşir. Dersin yönü: **yukarı**.

**AD3.** Model birleştirmeyi tek bir satırda kurar. Birleştiren bir atanın başka bir birleştiren atanın içinde bulunması ve iç içe birleştirmenin etiketleri nasıl katacağı bu derste ölçülmedi.

#### Özet

- Bu dersin yönü: yukarı. Etiketler çocuklardan ve torunlardan birleştiren ataya çıkar.
- Ekran okuyucu piksellere değil, arayüz ağacından türetilen anlamsal ağaca bakar.
- Birleştiren ata, çocuklarının ve torunlarının etiketlerini toplar; etiketler ataya yukarı gider.
- Doğrudan çocuğun da torunun da etiketi aynı yönde, yukarı çıkar.
- Birleşen etiketin ekran okuyucuya gitmesi ağaç dışı, dış bir harekettir.

#### Sonraki Adım

Bu kursun ağacı bildirimsel arayüzün ağacıdır. Birçok uygulama ise henüz klasik görünüm sistemiyle yazılmış ekranlar taşır. Sonraki ders, iki dünyayı bir arada kullanmayı ve veri ağacın sınırını geçtiğinde yönün nasıl adlandırıldığını ölçer.

### Klasik Görünüm Sistemi

Source: https://academia.sh/tr/kurslar/compose/klasik-gorunum-sistemi

Klasik görünüm hiyerarşisi ile bildirimsel ağacın karşılaştırmasının, sarılan bir görünüme giden verinin dış yönünün ve iki ağacın sınırını geçen hareketin ölçümü; kursun on yedi dersinin kapanış tablosu.

Önceki ders birleşen etiketi ekran okuyucuya taşıyarak kursun ağacını ağaç dışındaki bir okuyucuyla kapattı. Bu kursun ağacı bildirimsel arayüzün ağacıdır. Birçok uygulama ise klasik görünüm sistemiyle yazılmış ekranlar taşır: arayüz bir `xml` dosyasında bildirilir, görünümler koşum anında dosyadan kurulur ve ekranın kodu bir görünüme kimliğiyle ulaşıp ona doğrudan değer yazar. İki dünya aynı ekranda bir arada durabilir. Bu dersin sorusu, bir veri ikisinin sınırını geçtiğinde yönün nasıl adlandırıldığıdır. Çekirdek dosya önceki derslerle aynıdır.

```kotlin
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
    fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
    fun parent(node: String): String? = parentOf[node]
    fun ancestors(node: String): List<String> {
        val chain = mutableListOf<String>()
        var current = parentOf[node]
        while (current != null) {
            chain.add(current)
            current = parentOf[current]
        }
        return chain
    }
}

class Move(val label: String, val from: String, val to: String, tree: Tree) {
    val direction: String = when {
        !tree.contains(from) || !tree.contains(to) -> "dis"
        to in tree.ancestors(from) -> "yukari"
        tree.parent(to) == from -> "asagi"
        from in tree.ancestors(to) -> "derin"
        else -> error("akrabasiz: $from ve $to")
    }

    fun describe() {
        println("$label: kimden=$from | kime=$to -> $direction")
    }
}

fun countDirections(moves: List<Move>) {
    for (name in listOf("asagi", "yukari", "derin", "dis")) {
        println("$name: ${moves.count { it.direction == name }}")
    }
}
```

#### Klasik Hiyerarşi

Klasik görünüm sisteminde ekran bir `xml` dosyasıdır. Dosyadaki iç içe geçme görünüm hiyerarşisini kurar: dıştaki öge ebeveyn, içindekiler çocuktur. Ekranın kodu hiyerarşiyi parametrelerle aşağı doğru beslemez; kimliğiyle bulduğu görünüme doğrudan yazar.

```xml
<!-- Platform kodu: klasik görünüm düzeni, res/layout/screen.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button android:id="@+id/pay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </FrameLayout>
</LinearLayout>
```

Model dosyanın girintisini düğüm listesi olarak alır ve ondan bir ağaç kurar. Ağaç aynı `Tree` sınıfıdır; yalnız kuruluş biçimi farklıdır. Hareketlerin uçları bu dersin ölçümlerinde elle yazılmaz: kurulan ağaçtan ve düğüm listesinden seçilir. `deepest` listedeki en derin düğümü, `firstAt` verilen girinti düzeyindeki ilk düğümü verir; ekran düğümü de ağacın `ancestors` zincirinin sonundan okunur.

```kotlin
// Inflate.kt — girintili xml dugum listesinden agac kurar ve dugum secer (modelin hesabi)
fun inflate(root: String, nodes: List<Pair<Int, String>>): Tree {
    val parents = linkedMapOf<String, String>()
    val path = mutableListOf(root)
    for ((depth, name) in nodes) {
        while (path.size > depth) path.removeAt(path.size - 1)
        parents[name] = path.last()
        path.add(name)
    }
    return Tree(parents)
}

fun deepest(nodes: List<Pair<Int, String>>): String = nodes.maxByOrNull { it.first }!!.second
fun firstAt(depth: Int, nodes: List<Pair<Int, String>>): String = nodes.first { it.first == depth }.second
```

İlk ölçüm aynı ekranı iki biçimde kurar ve aynı iki veriyi taşır: başlığın metni ve düğmenin tıklama olayı. Klasik tarafta ekranın kodu başlığa ara görünümleri atlayarak yazar. Bildirimsel tarafta metin sütundan geçerek iner.

```kotlin
// Main.kt — ayni ekran: xml hiyerarsisi ve bildirimsel agac
fun main() {
    val classicNodes = listOf(1 to "LinearLayout", 2 to "Title", 2 to "Frame", 3 to "PayButton")
    val classic = inflate("Screen", classicNodes)
    val button = deepest(classicNodes)
    val screen = classic.ancestors(button).last()
    val declarative = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "PayButton" to "Column"))
    val column = declarative.parent("Title")!!
    val declarativeScreen = declarative.parent(column)!!
    val classicMoves = listOf(
        Move("baslik-yaz", screen, firstAt(2, classicNodes), classic),
        Move("dugme-dinleyici", button, screen, classic),
    )
    val declarativeMoves = listOf(
        Move("baslik-yol-1", declarativeScreen, column, declarative),
        Move("baslik-yol-2", column, "Title", declarative),
        Move("dugme-olayi", "PayButton", declarativeScreen, declarative),
    )
    println("klasik:")
    classicMoves.forEach { it.describe() }
    countDirections(classicMoves)
    println("bildirimsel:")
    declarativeMoves.forEach { it.describe() }
    countDirections(declarativeMoves)
}
```

```text
klasik:
baslik-yaz: kimden=Screen | kime=Title -> derin
dugme-dinleyici: kimden=PayButton | kime=Screen -> yukari
asagi: 0
yukari: 1
derin: 1
dis: 0
bildirimsel:
baslik-yol-1: kimden=Screen | kime=Column -> asagi
baslik-yol-2: kimden=Column | kime=Title -> asagi
dugme-olayi: kimden=PayButton | kime=Screen -> yukari
asagi: 2
yukari: 1
derin: 0
dis: 0
```

Klasik ekranın kodu başlığa tek hareketle, derin gider. Bildirimsel ekranda aynı metin iki aşağı hareketle iner. Düğmenin olayı iki tarafta da yukarıdır. Fark kanaldadır: kimlikle erişim aradaki düğümleri atlar, parametre zinciri her düğümden geçer. Kural kanalı görmez; iki hareketin geometrisini sınıflar.

**AD6.** Model xml dosyasını girintili bir düğüm listesi olarak alır. Özniteliklerin, stillerin, kimlikle aramanın maliyetinin ve görünümlerin ölçülüp yerleştirildiği geçişin modelde karşılığı yoktur.

#### Bileşen İşlevi İçindeki Görünüm

Klasik bir görünümü bildirimsel ağaca koymanın yolu `AndroidView`'dir. Bileşen işlevi, görünümün kurulmasını, ardından kurulduğunda ve güncelleme bloğunun okuduğu durum değiştiğinde güncellenmesini ister.

```kotlin
// Platform kodu: klasik bir görünümü bileşen işlevinin içine koymak
@Composable
fun LabelHost(caption: String) {
    AndroidView(
        factory = { context -> TextView(context) },
        update = { view -> view.text = caption },
    )
}
```

`LabelHost` ağaçta bir düğümdür. İçindeki `TextView` ise değildir: bildirimsel ağaç onu çocuk olarak bilmez, platformun görünüm hiyerarşisi ona sahiptir. Modelde sarılan görünüm, ağaçta bulunmayan bir ad olarak kurulur.

```kotlin
// Bridge.kt — sarilan gorunum agacin disinda kalir (modelin hesabi)
class ClassicHost(val node: String, val view: String, tree: Tree) {
    val viewInTree: Boolean = tree.contains(view)
}
```

Ölçüm, veriyi sarmalayıcıya, sarılı görünüme ve görünümden geriye doğru izler.

```kotlin
// Main.kt — sarilan gorunume giden ve ondan donen veri
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Header" to "Screen", "LabelHost" to "Screen"))
    val host = ClassicHost("LabelHost", "LabelView", tree)
    println("sarilan gorunum agacta: ${host.viewInTree}")
    val moves = listOf(
        Move("sarmalayici", "Screen", host.node, tree),
        Move("guncelleme", host.node, host.view, tree),
        Move("dogrudan", "Screen", host.view, tree),
        Move("dokunus", host.view, host.node, tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
sarilan gorunum agacta: false
sarmalayici: kimden=Screen | kime=LabelHost -> asagi
guncelleme: kimden=LabelHost | kime=LabelView -> dis
dogrudan: kimden=Screen | kime=LabelView -> dis
dokunus: kimden=LabelView | kime=LabelHost -> dis
asagi: 1
yukari: 0
derin: 0
dis: 3
```

Sarmalayıcıya giden veri aşağıdır: ebeveyn çocuğuna verir. Sarılı görünüme ulaşan her hareket dış, görünümden dönen dokunuş da dış. Sarmalayıcı üzerinden gitmek ile ekrandan doğrudan gitmek aynı sonucu verir, çünkü ağaçta olmayan bir uca giden hareketin yolu kuralı değiştirmez.

**AD7.** Model sarılan görünümü ağaçta olmayan bir ad olarak kurar. `factory` ile `update` ayrımı, güncellemenin hangi yeniden oluşturmada çalıştığı ve görünümün yaşam döngüsü modelde yoktur.

#### Görünüm İçindeki Bildirimsel İçerik

Ters yön de mümkündür: klasik bir hiyerarşinin içine `ComposeView` konur ve bildirimsel içerik onun içinde oluşturulur.

```kotlin
// Platform kodu: bildirimsel içeriği klasik görünüm hiyerarşisine koymak
val composeView = ComposeView(context).apply {
    setContent {
        Panel { Label(text = "Sepet") }
    }
}
frame.addView(composeView)
```

Bu durumda iki ağaç vardır. Klasik hiyerarşide `ComposeView` bir yapraktır. İçindeki `Panel` ve `Label` ise ikinci bir bildirimsel ağaca aittir. Ölçüm iki ağacı ayrı kurar ve sınırı geçen hareketi iki taraftan da sınıflar.

```kotlin
// Main.kt — iki agacin siniri
fun main() {
    val classicNodes = listOf(1 to "LinearLayout", 2 to "Frame", 3 to "ComposeView")
    val classic = inflate("Screen", classicNodes)
    val host = deepest(classicNodes)
    val screen = classic.ancestors(host).last()
    val inner = Tree(mapOf("Label" to "Panel"))
    val panel = inner.parent("Label")!!
    val moves = listOf(
        Move("duzen-veri", screen, host, classic),
        Move("sinir-klasik", host, panel, classic),
        Move("sinir-bildirimsel", host, panel, inner),
        Move("icerde", panel, "Label", inner),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
duzen-veri: kimden=Screen | kime=ComposeView -> derin
sinir-klasik: kimden=ComposeView | kime=Panel -> dis
sinir-bildirimsel: kimden=ComposeView | kime=Panel -> dis
icerde: kimden=Panel | kime=Label -> asagi
asagi: 1
yukari: 0
derin: 1
dis: 2
```

Klasik ağaçta ekrandan `ComposeView`'e giden veri derindir. Sınırın ötesine geçen hareket ise hangi ağaçtan bakılırsa bakılsın dıştır: kaynak ya da hedef, bakılan ağaçta yoktur. Ağaç içi hareket ancak `Panel`'den `Label`'a, yani ikinci ağacın içinde aşağıdır.

**AD8.** Model iki ağacı iki ayrı `Tree` olarak kurar. `ComposeView`'in yaşam döngüsü sahibine bağlanması, yeniden oluşturma kapsamının sınırı ve iki ağacın tek pencerede nasıl bütünleştiği ölçülmedi.

#### Sınırlayıcı Ölçüm

Dersin yönü, bir uç ağaçta olmadığında dış adının verilmesinden gelir. Kural, ögenin klasik mi bildirimsel mi olduğunu görmez; hangi ağaçta bulunduğunu görür. Aynı etiket iki biçimde kurulsun: yerel bir bileşen işleviyle ve `AndroidView` ile sarılı bir görünümle. Sarılı görünümün kendi iç hiyerarşisi ayrı bir ağaç olarak ölçülebilir.

```kotlin
// Main.kt — ayni etiket iki bicimde
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Header" to "Screen", "Label" to "Screen", "LabelHost" to "Screen"))
    val wrappedNodes = listOf(1 to "Glyphs")
    val wrapped = inflate("LabelView", wrappedNodes)
    val glyphs = deepest(wrappedNodes)
    val labelView = wrapped.parent(glyphs)!!
    val screen = tree.parent("Label")!!
    val moves = listOf(
        Move("yerel-etiket", screen, "Label", tree),
        Move("sarili-etiket", screen, labelView, tree),
        Move("sarili-ici", labelView, glyphs, wrapped),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
yerel-etiket: kimden=Screen | kime=Label -> asagi
sarili-etiket: kimden=Screen | kime=LabelView -> dis
sarili-ici: kimden=LabelView | kime=Glyphs -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 1
```

Aynı etiket yerelde aşağı, sarılı görünümde dış gider. Sarılı görünümün içinde de bir hiyerarşi vardır ve o kendi ağacında aşağıdır; ama bildirimsel ağaç bu hiyerarşiyi göremez. Dersin yönü: **dış**.

Bu ayrım iki başka soruyla karıştırılmaz. M31/K02'nin sorusu, bir isteği platformun nasıl karşıladığıdır; burada istenenle verilen karşılaştırılmaz. M30/K04'ün sorusu, bir ekranın modelle uyuşup uyuşmadığıdır; burada ekranın uyumu hiçbir yerde sorulmaz. İkisi de kategori ya da ölçü adı yapılmaz: ölçü baştan sona verinin ağaçtaki yönüdür.

**AD9.** Model sarılı görünümün iç yapısını tek bir çocukla kurar. Gerçek bir görünümün çocuk sayısı, kendi içindeki yeniden çizim ve ölçüm sırası ölçülmedi.

#### Özet

- Bu dersin yönü: dış. Sarılan görünüm ağaçta olmayan bir uçtur ve bir ucu ağaçta olmayan her hareket, ölçüm `inflate` dönüşünden seçilen düğümlerle kurulduğunda da dış çıkar.
- Klasik görünüm hiyerarşisi xml dosyasından kurulur; ekranın kodu derindeki görünüme kimliğiyle doğrudan ulaşır ve bu hareket derindir.
- Bir bileşen işlevine konan klasik görünüm bildirimsel ağaçta değildir; ona giden ve ondan dönen her hareket dıştır.
- Klasik hiyerarşinin içine konan bildirimsel içerik ikinci bir ağaçtır; sınırı geçen hareket iki taraftan da dıştır.
- Aynı etiket yerelde aşağı, sarılı görünümde dış gider; kural öge türünü değil, ağaç üyeliğini görür.

#### Kurs Kapanışı

Kurs, tek bir soruyla on yedi dersi ölçtü: bu veri arayüz ağacında hangi yöne gidiyor? Her dersin ölçülen hareketi, yönü ve sınırlayıcı ölçümü aşağıdadır. Yön hücreleri kaynak dersin özetinden alınmıştır.

| Ders | Ölçülen hareket | Yön | Sınırlayıcı ölçüm |
|---|---|---|---|
| Bileşen İşlevleri | parametre ve sağlayıcı değeri | aşağı | aynı sağlayıcı: doğrudan çocuğa aşağı, torununa derin |
| Durum ve Hatırlama | sahibinden okuyucuya değer | aşağı | yerel durum hiçbir harekete girmez |
| Durum Kaldırma | olay sahibe, değer okuyucuya | yukarı | App'e kaldırınca aşağı 3, yukarı 2 |
| Yeniden Oluşturma | değişimin okuyucuya ulaşması | derin | parametre iki aşağı, ertelenmiş okuma tek derin |
| Yan Etkiler | etkiden servise istek | dış | iç yazı yukarı, yanıt da dış |
| Değişmezlik ve Kararlılık | sahibe giden değişiklik | yukarı | iki kanal aynı yön, gözlenme farklı |
| Temel Düzenler | kısıt, ölçü, konum | aşağı | ölçü yukarı, kısıt ve konum aşağı |
| Değiştiriciler | kısıt, zincirin sırası | aşağı | sıra değişince kısıt derin |
| Kısıt Tabanlı Düzen | kardeş kısıtı | yukarı | kardeş hareketi akrabasız, iki harekete bölünür |
| Tembel Listeler | kaydırma konumu | yukarı | görünmeyen satıra giden veri dış |
| Hazır Arayüz Bileşenleri | içerik rengi | derin | doğrudan çocuğa aşağı, sarılınca derin |
| İskele Yapısı | iç boşluk | aşağı | yakalanan durum iskeleyi atlar, derin |
| Gezinme ve Rotalar | gezinme öncesi argüman | dış | ağaca girince aşağı, geri sonuç dış |
| Tema ve Tipografi | kökten derine tema | derin | alt ağaç teması doğrudan çocuğa aşağı |
| Animasyonlar | kare başına ara değer | dış | hedef aşağı, bitiş yukarı |
| Erişilebilirlik | etiketlerin ataya birleşmesi | yukarı | birleşen etiket okuyucuya dış |
| Klasik Görünüm Sistemi | sarılı görünüme giden veri | dış | aynı etiket yerelde aşağı, sarılıda dış |

On yedi dersin dağılımı: 5 aşağı, 5 yukarı, 3 derin, 4 dış. Çekirdeğin 3/2/1/2 dağılımı ayrı bir sayımdır: çekirdek açılış dersindeki sekiz hareketi sayar, tablo ise on yedi dersin yönünü sayar; ikisi aynı şey değildir.

Sıradaki kurs (M31/K04) katmanlı mimariyi konu alır. Bu kursun ölçü ekseni orada sürmez; K04 kendi sorusunu kendisi sorar.

