---
title: Flutter
source: 'https://academia.sh/tr/kurslar/flutter'
language: tr
updated: '2026-10-21T05:02:51+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'Dart dilinin temel yapılarıyla uygulama yazar'
    - 'Bileşen ağacı ve çizim modelini açıklar'
    - 'Durum yönetimi yaklaşımlarını karşılaştırarak seçer'
---

# Flutter

Flutter, Dart ile yazılan bileşen ağacını kendi çizim motoruyla ekrana basan bir çerçevedir; düzen tek geçişte yürür: kısıtlar ağaçta aşağı iner, boyutlar yukarı çıkar. Kurs her konuyu tek bir soruyla ölçer — bu bileşenin genişliğini kim belirliyor? Bir genişlik dayatılır, seçilir, kırpılır ya da esnetilir; Dart temelleri, durumlu bileşenler, üç ağaç modeli, durum yönetimi, gezinme, veri ve depolama katmanları, animasyonlar, platform kanalları, test, başarım ve yayın aynı çerçeveden geçirilir.

## Dart ve Bileşen Modeli

Dart temelleri ve eşzamansız Dart, bileşen kavramı, durumsuz ve durumlu bileşenler, üç ağaç modeli, düzen bileşenleri ve kısıt akışı, platform bileşen aileleri.

### Dart Temelleri

Source: https://academia.sh/tr/kurslar/flutter/dart-temelleri

Bir bileşenin genişliğini kimin belirlediğini sınıflayan küçük bir Dart çekirdeği. Ders Dart'ın tiplerini, null güvenliğini ve sınıflarını bu çekirdek üzerinden anlatır ve dört boyutlanmayı tanıtır: dayatılan, seçilen, kırpılan, esnetilen.

Bu kurs bir arayüz çerçevesini tek bir soruyla açar: **bu bileşenin genişliğini kim belirliyor?** Burada **bileşen**, ekranda yer kaplayan arayüz parçasıdır: bir metin, bir görsel, bir düğme ya da başka bileşenleri içeren bir kapsayıcı. Sözcük kurs boyunca hep bu anlamda kalır; yazılım mimarisindeki modül ya da servis anlamına gelmez.

Her bileşene ebeveyninden bir **kısıt** iner: kabul edilen en küçük ve en büyük genişlik. Bileşenin kendi isteği olabilir ya da olmayabilir. Kısıt ile isteğin birleşimi genişliği verir ve genişliğin sahibi dört taraftan biridir. Adları kurs boyunca değişmez; çıktıda ASCII yazılır, düzyazıda Türkçe karşılığıyla geçer.

- **dayatılan** (`dayatilan`): kısıt sıkıdır, yani en küçük ve en büyük genişlik aynıdır. Bileşenin isteği sonucu değiştiremez.
- **seçilen** (`secilen`): bileşenin isteği kısıtın içinde kalır ve genişliği bileşen seçer.
- **kırpılan** (`kirpilan`): istek kısıtın dışına taşar ya da kısıt sınırsızken istek yoktur. Genişliği kısıt keser.
- **esnetilen** (`esnetilen`): istek yoktur, kısıtın üst sınırı vardır ve bileşen o sınıra kadar genişler.

Seçilen dışındaki üçünde genişliği bileşen değil, üstten gelen kısıt belirler. Bu üçünün toplamına **ebeveyn** denir; çıktının son satırı onu sayar.

#### Düzen Sözleşmesi

Kısıtın nereden geldiğini üç cümle söyler. Kısıtlar aşağı iner: ebeveyn, çocuğa kabul edeceği aralığı verir. Boyutlar yukarı çıkar: çocuk o aralığın içinde bir genişlik seçer ve ebeveyne bildirir. Ebeveyn konumu belirler: çocuğun nerede duracağına çocuk değil, ebeveyn karar verir. Bu kurs yalnız ilk iki cümlenin genişlik ekseniyle ilgilenir.

**DB1.** Model, düzen sözleşmesini yalnız genişlik ekseninde kurar: her düğüm tek bir `min..max` aralığı alır ve tek bir tamsayı genişlik döndürür. Yükseklik, konum ve çizim modelde yoktur; model çerçevenin kendisi değildir.

#### Çekirdek Model

Sınıflayıcı tek bir Dart dosyasıdır, `sizing.dart`. Kurs boyunca her ders onu ilk Dart bloğu olarak taşır ve içeriği hiç değişmez; sonraki derslerde yeniden okumak yerine yalnız üstüne eklenen ağaçlara bakılır. Bu ders dosyayı satır satır açar, çünkü Dart'ın temel yapıları burada birlikte çalışır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

**Tipler ve null güvenliği.** `Limit` sınıfının `min` alanı `int`, `max` alanı `int?` tipindedir. Dart 3.13.5'te bir tip `null` kabul etmez; kabul ediyorsa adının sonunda `?` taşır. `max`'ın `null` olması "üst sınır yok" demektir ve `bounds()` bunu `sonsuz` diye yazar. `null` olabilen bir değeri `null` olamayan bir yere vermek derleme hatasıdır. Değerin sonuna konan ünlem işareti derleyiciye "bu değer burada `null` değil" der; çekirdek bunu yalnız önce `null` denetimi yapılmış yerlerde kullanır. `max ?? 'sonsuz'` ise `max` `null` ise sağ tarafı seçer.

**Sınıflar ve değişmezlik.** `Limit` ve `Part` sınıflarının alanları `final`, kurucuları `const` olduğundan nesneler kurulduktan sonra değişmez. `loosen()` ve `deflate()` mevcut kısıtı değiştirmez, yenisini döndürür. `bool get tight => max == min;` bir **getter**'dır: çağrılırken parantez almaz, `limit.tight` diye okunur.

**Parametreler.** `Part` kurucusunun ilk dört parametresi konumsaldır. Küme parantezi içindeki `factor` ve `tightFlex` **adlandırılmış** parametrelerdir, varsayılan değerleri vardır ve çağrıda adıyla verilir: `flex('Note', 1, fill('Text'), tight: false)`. Yardımcı işlevler `leaf`, `fill`, `pad` gibi kısa adlarla ağaç kurar. `fill`, genişlik isteğini `null` bırakır: "alabildiğim kadar genişlerim".

**Koleksiyonlar ve ifadeler.** `[for (final _ in part.children) <Sizing>[]]` her çocuk için boş bir liste kurar. `log..add(own)..addAll(below)` bir **zincirleme** çağrıdır; her `..` aynı nesneye yeni bir işlem uygular. `~/` tamsayı bölmedir. `switch` içindeki `case` dalları `break` taşımaz; her dal kendi bloğunda biter.

##### Kural sırası

`classify` işlevi sınıflamanın tek yeridir ve soruları şu sırayla sorar:

1. Kısıt sıkıysa sonuç dayatılandır.
2. İstek yoksa: üst sınır sonluysa esnetilen, sınırsızsa kırpılan.
3. İstek en küçük genişliğin altındaysa ya da üst sınırın üstündeyse kırpılan.
4. Aksi hâlde seçilen.

**DB2.** Sıkı kısıt ilk sorulur, çünkü kısıt tek bir genişliğe izin veriyorsa bileşenin isteği sonucu değiştiremez. Bu sıra modelin kararıdır; sınıflamayı başka bir sırayla da kurmak mümkündür, ama çıktıdaki ad ancak bu sırayla anlam taşır.

#### Açılış Düzeni

Aşağıdaki ağaç kursun bağlayıcı açılış örneğidir. Kök `Screen`, her yandan 16 iç boşluk bırakır ve içinde `Body` sütunu durur. Sütunda 120 isteyen `Title`, 400 isteyen `Banner` ve bir satır olan `Toolbar` vardır. Satırda 48 isteyen `Back` ve `Menu` ile, aralarında esneklik çarpanı 1 olan `Search` yer alır; `Search` içindeki `Field` genişlik istemez.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final screen = pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', 400),
    row('Toolbar', [
      leaf('Back', 48),
      flex('Search', 1, fill('Field')),
      leaf('Menu', 48),
    ]),
  ]));

  final log = measure(screen, 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
kirpilan: /Screen/Body/Banner genislik=328 kisit=0..328
esnetilen: /Screen/Body/Toolbar genislik=328 kisit=0..328
secilen: /Screen/Body/Toolbar/Back genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Body/Toolbar/Search genislik=232 kisit=232..232
dayatilan: /Screen/Body/Toolbar/Search/Field genislik=232 kisit=232..232
secilen: /Screen/Body/Toolbar/Menu genislik=48 kisit=0..sonsuz
dayatilan: 4
secilen: 3
kirpilan: 1
esnetilen: 1
ebeveyn: 6
```

Çıktı kuralları sırayla işletir. `Screen` 360 sıkı kısıt aldığı için dayatılandır. `pad` iki yandan 16 çıkarır; `Body`'ye 328 sıkı kısıt iner ve `Body` da dayatılandır. Sütun kısıtı gevşetir: çocuklar `0..328` alır. `Title` 120 ister ve aralığın içinde kaldığından seçilendir. `Banner` 400 ister, üst sınır 328'dir; genişliğini kısıt keser ve kırpılandır. `Toolbar` genişlik istemez, üst sınırı 328 olduğundan esnetilendir. Satır, esnek olmayan çocuklara `0..sonsuz` verir; `Back` ve `Menu` bu aralıkta 48'i seçer. Kalan 328 - 48 - 48 = 232, çarpanı 1 olan `Search`'e sıkı kısıt olarak iner ve `Search` ile `Field` dayatılan olur.

Sayım dört sınıfı ve ebeveyn toplamını verir: 4 dayatılan, 3 seçilen, 1 kırpılan, 1 esnetilen; ebeveyn 4 + 1 + 1 = 6. Bir ayrıntı şimdiden not edilmeli: `Search` esnek çocuktur, ama esnetilen değil dayatılandır. Esnetilen olan `Toolbar`'dır. İkisi aynı şey değildir; altıncı ders bu ayrımı açar.

#### Bileşen Sınıfı

Dart'ta bir bileşen bir sınıftır. Aşağıdaki parça çerçevenin durumsuz bileşen sınıfından türeyen en küçük bileşeni gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: bilesen sinifi
class TitleLabel extends StatelessWidget {
  const TitleLabel({super.key, required this.text});
  final String text;

  @override
  Widget build(BuildContext context) {
    return Text(text);
  }
}
```

Sınıf `StatelessWidget`'ten türer (`extends`). `required` adlandırılmış parametreyi zorunlu yapar; `text` verilmeden bileşen kurulamaz. `build` yöntemi bileşenin ne göstereceğini başka bir bileşen olarak döndürür, burada bir `Text`. `@override` üst sınıftaki yöntemi yeniden tanımladığını bildirir.

Sınıfın kendisi genişlik hakkında hiçbir şey söylemez. `build` yalnız ne gösterileceğini tarif eder; genişliği bileşenin ağaçta durduğu yer ve ona inen kısıt belirler. Bu kursun işi o ikinci yarıyı ölçmektir.

#### İsteğin Yokluğu

Çekirdekteki `int?` yalnız bir kolaylık değildir; "istek yok" ile "istek sıfır" ayrımını taşır. Aşağıdaki blok iki yaprak kurar, isteklerini yazdırır ve ikisini bir sütunda ölçer.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final label = leaf('Label', 120);
  final space = fill('Space');
  print('Label istegi: ${label.width}');
  print('Space istegi: ${space.width}');

  final screen = pad('Screen', 16, column('Body', [label, space]));
  printSizes(measure(screen, 360));
}
```

```text
Label istegi: 120
Space istegi: null
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Label genislik=120 kisit=0..328
esnetilen: /Screen/Body/Space genislik=328 kisit=0..328
```

`Label` 120 ister ve seçilendir. `Space` hiçbir şey istemez; isteği `null` olduğundan kısıt `0..328` iken üst sınıra kadar genişler ve esnetilendir. `null` ve `0` aynı şey olsaydı bu iki satır ayırt edilemezdi.

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

Bir ilke ancak çürütebilecek bir ölçümle sınanır. Açılış ağacını bu kez iki ekran genişliğinde ölçüyoruz ve yalnız odak düğümü, `/Screen/Body/Banner`'ı süzüp yazdırıyoruz. Ağaç değişmez; yalnız kök kısıt değişir. Son satırlar 480'deki sayımı verir.

```dart
// main.dart
import 'sizing.dart';

Part buildScreen() {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', 400),
    row('Toolbar', [
      leaf('Back', 48),
      flex('Search', 1, fill('Field')),
      leaf('Menu', 48),
    ]),
  ]));
}

void main() {
  for (final screenWidth in [480, 360]) {
    print('ekran=$screenWidth');
    final log = measure(buildScreen(), screenWidth);
    printSizes(log.where((entry) => entry.path == '/Screen/Body/Banner').toList());
  }
  print('sayim ekran=480');
  countSizings(measure(buildScreen(), 480));
}
```

```text
ekran=480
secilen: /Screen/Body/Banner genislik=400 kisit=0..448
ekran=360
kirpilan: /Screen/Body/Banner genislik=328 kisit=0..328
sayim ekran=480
dayatilan: 4
secilen: 4
kirpilan: 0
esnetilen: 1
ebeveyn: 5
```

**Bu dersin boyutlanması: kırpılan.** 480 genişlikte `Banner`'ın 400 isteği `0..448` aralığının içinde kalır ve genişliği `Banner` seçer. 360'ta aynı 400 isteği `0..328` aralığına sığmaz ve genişliği kısıt keser. Bileşen, ağaç ve istek aynıdır; sonucun sahibi yalnız kök kısıt değiştiği için değişir. 480'deki sayım da bunu doğrular: kırpılan sıfırdır, seçilen dörde çıkar.

**DB3.** `Banner`'ın 400 isteği modelde hazır bir sayıdır. Gerçek bir görselin genişliği çoğu zaman yüklendikten sonra belli olur; model bunu önceden bilinen bir istek sayar. Genişliğin sonradan geldiği durum bir sonraki dersin konusudur.

#### Özet

- Bu dersin boyutlanması: kırpılan. `/Screen/Body/Banner` 400 ister, 360 ekranda kısıt `0..328` verir ve genişliği kısıt keser.
- Dart'ta `int?` "değer yok" ihtimalini tipin içine yazar; `null` olamayan tipe `null` vermek derleme hatasıdır ve çekirdek `int?` ile "istek yok" durumunu taşır.
- Düzen sözleşmesi üç cümledir: kısıtlar aşağı iner, boyutlar yukarı çıkar, ebeveyn konumu belirler.
- Genişliğin sahibi dört taraftan biridir: dayatılan, seçilen, kırpılan, esnetilen. Açılış ağacında 4 dayatılan, 3 seçilen, 1 kırpılan, 1 esnetilen vardır; ebeveyn 6'dır.
- Aynı ağaç 480'de seçilen, 360'ta kırpılan sonuç verir: genişliğin sahibi bileşenin tek başına özelliği değil, bileşenle kısıtın birleşimidir.

#### Sonraki Adım

`Banner`'ın genişliği bu derste hazır bir sayıydı. Gerçek uygulamalarda böyle bir değer çoğu zaman hemen gelmez. **Eşzamansız Dart** dersi, genişliğin sonradan geldiği durumu `Future` ve `await` ile kurar ve aynı bileşenin değer gelmeden önce ve geldikten sonra hangi boyutlanmayı aldığını ölçer.

### Eşzamansız Dart

Source: https://academia.sh/tr/kurslar/flutter/eszamansiz-dart

Genişlik sonradan gelen bir bileşenin iki ölçümü. Ders gelecek, async ve await, akış, olay döngüsü ve yalıtılmış işlemci kavramlarını ağacın kurulduğu sıra üzerinden anlatır.

Önceki derste `Banner`'ın 400 isteği hazır bir sayıydı. Gerçek bir ekranda bir bileşenin genişliği çoğu zaman sonradan gelir: bir görselin boyutu okunur, bir dosyanın içeriği çözülür, bir yanıt beklenir. O ana kadar ağaç yine de kurulmak zorundadır. Bu dersin sorusu, değer gelmeden önce ve geldikten sonra aynı bileşenin genişliğini kimin belirlediğidir.

Dart bu duruma **gelecek** (`Future`) tipiyle cevap verir. `Future<int>` şimdi var olmayan, ileride bir `int` ya da bir hata olarak tamamlanacak bir değerdir. `async` ile işaretlenen bir işlev her zaman bir gelecek döndürür; `await` ise bir gelecek tamamlanana kadar o işlevin geri kalanını bekletir, işlevi çağıran taraf bu sırada akışına devam eder.

#### Ağaç ve Yardımcı Dosya

Ders boyunca ağaç tek bir yerden kurulur. `buildBody` işlevi genişlik bilinmiyorsa `fill('Avatar')`, biliniyorsa `leaf('Avatar', width)` kullanır. `printAvatar` ağacı 360 genişliğinde ölçer ve yalnız `/Screen/Body/Avatar` satırını yazar. Bu dosya çıktısızdır ve `main` içermez; aşağıdaki bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

```dart
// avatar_body.dart — avatar genisligine gore sutun agaci kurar ve avatari olcer
import 'sizing.dart';

Part buildBody(int? width) {
  final avatar = width == null ? fill('Avatar') : leaf('Avatar', width);
  return pad('Screen', 16, column('Body', [leaf('Name', 120), avatar]));
}

void printAvatar(int? width) {
  final log = measure(buildBody(width), 360);
  printSizes(log.where((entry) => entry.path == '/Screen/Body/Avatar').toList());
}
```

`int? width` parametresi ilk dersteki fikri taşır: `null`, genişliğin henüz bilinmediğini söyler. `width == null ? fill('Avatar') : leaf('Avatar', width)` bir **koşul ifadesidir**; `width` `null` değilse ikinci kolda `int` olarak kullanılabilir, çünkü derleyici `null` denetimini izler.

**DB1.** Modelde bekleme süresi yoktur, yalnız sıra vardır. `loadWidth` hemen tamamlanır; ölçülen şey, değerin geldiği anın öncesi ve sonrasındaki iki ağaçtır. Model çerçevenin kendisi değildir.

#### Gelecek ve await

Aşağıdaki blok bir gelecek kurar, çağıran tarafın akışına devam ettiğini gösterir ve değeri `await` ile alır. Her adım bir satır basar.

```dart
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';

Future<int> loadWidth() async {
  print('yukleme basladi');
  return 200;
}

Future<void> main() async {
  print('istek gonderildi');
  final pending = loadWidth();
  print('beklemeden devam');
  final width = await pending;
  print('deger geldi: $width');
  printAvatar(width);
}
```

```text
istek gonderildi
yukleme basladi
beklemeden devam
deger geldi: 200
secilen: /Screen/Body/Avatar genislik=200 kisit=0..328
```

`loadWidth` çağrıldığında gövdesi ilk `await`'e ya da sona kadar hemen çalışır; bu yüzden `yukleme basladi` satırı `beklemeden devam`'dan önce gelir. `loadWidth`'ın döndürdüğü gelecek `pending` değişkenine girer. `await pending` ifadesine gelindiğinde `main` durur ve değer gelince kaldığı yerden sürer. Değer gelmiştir, `Avatar` 200 ister ve aralığın içinde kaldığı için seçilendir.

#### Olay Döngüsü

Dart kodu tek bir iş parçacığında, bir **olay döngüsü** ile çalışır. Çalışan kod sonuna kadar sürer. Sonra döngü iki kuyruğa bakar: önce **mikro görev kuyruğunu** boşaltır, ardından **görev kuyruğundan** bir görev alır. Bir `await` ya da bir `Future(...)` çağrısı, devamını bu kuyruklardan birine bırakır. Aşağıdaki blok sırayı numaralarla gösterir.

```dart
// main.dart
import 'sizing.dart';
import 'dart:async';
import 'avatar_body.dart';

void main() {
  print('1 senkron: ilk kurulus');
  printAvatar(null);
  scheduleMicrotask(() {
    print('3 mikro gorev');
    printAvatar(200);
  });
  Future(() {
    print('4 gorev kuyrugu');
    printAvatar(320);
  });
  print('2 senkron: main sonu');
}
```

```text
1 senkron: ilk kurulus
esnetilen: /Screen/Body/Avatar genislik=328 kisit=0..328
2 senkron: main sonu
3 mikro gorev
secilen: /Screen/Body/Avatar genislik=200 kisit=0..328
4 gorev kuyrugu
secilen: /Screen/Body/Avatar genislik=320 kisit=0..328
```

Numaralar kodun yazılış sırasına değil, çalışma sırasına aittir. `main` bitmeden hiçbir kuyruk işlenmez; bu yüzden 2 numaralı satır 3'ten önce basılır. İlk ölçümde değer yoktur ve `Avatar` esnetilendir. Mikro görev değeri 200 yapar, görev kuyruğundaki iş 320 yapar; ikisinde de istek aralığın içindedir ve `Avatar` seçilendir.

#### Hata ve Akış

Bir gelecek bir değerle değil, bir hatayla da tamamlanabilir. `await` o hatayı çağıran tarafa fırlatır ve `try`/`catch` onu yakalar. Aşağıdaki blokta yükleme başarısız olur, işlev yedek bir genişliğe döner ve ağaç bu genişlikle kurulur. Hata yakalandığı için programa yayılmaz; çıktıda yedek genişliği bildiren tek satırın ardından ağacın ölçümü yazılır.

```dart
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';

Future<int> loadWidth() async {
  throw StateError('baglanti yok');
}

Future<int> loadWidthOrFallback() async {
  try {
    return await loadWidth();
  } catch (_) {
    print('yukleme basarisiz, 96 kullanilir');
    return 96;
  }
}

Future<void> main() async {
  final width = await loadWidthOrFallback();
  printAvatar(width);
}
```

```text
yukleme basarisiz, 96 kullanilir
secilen: /Screen/Body/Avatar genislik=96 kisit=0..328
```

Gelecek tek bir değer taşır. Zamana yayılmış birden çok değer için Dart **akış** (`Stream`) tipini kullanır. `async*` ile işaretlenen bir işlev `yield` ile değerleri sırayla üretir; `await for` döngüsü her değeri geldiği sırayla alır.

```dart
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';

Stream<int> widths() async* {
  yield 200;
  yield 320;
  yield 400;
}

Future<void> main() async {
  await for (final width in widths()) {
    print('akis degeri: $width');
    printAvatar(width);
  }
}
```

```text
akis degeri: 200
secilen: /Screen/Body/Avatar genislik=200 kisit=0..328
akis degeri: 320
secilen: /Screen/Body/Avatar genislik=320 kisit=0..328
akis degeri: 400
kirpilan: /Screen/Body/Avatar genislik=328 kisit=0..328
```

Her yeni değer ağacın yeniden ölçülmesini getirir. İlk iki değer `0..328` aralığında kalır ve seçilendir; üçüncüsü 400 olduğundan kısıt tarafından kırpılır. Aynı bileşende, aynı yerde, değer akarken boyutlanma sahibi değişir.

**DB2.** Akış modelde üç sabit değerden oluşur. Gerçek bir akış değerlerini zamana yayar; model yalnız sırayı korur ve değerler arasındaki bekleme yoktur.

#### Yalıtılmış İşlemci

Olay döngüsü işi bölüştürür ama paralel çalıştırmaz: ağır bir hesap döngüyü tutarsa kuyruklar işlenemez ve arayüz donar. Dart bunun için **yalıtılmış işlemci** (isolate) sunar. Yalıtılmış işlemci kendi belleği ve kendi olay döngüsü olan ayrı bir yürütme birimidir; ana birimle bellek paylaşmaz, yalnız ileti alışverişi yapar. Aşağıdaki parça ağır bir çözümlemeyi ayrı bir birime taşır. Parça ölçülmez; model yalıtılmış işlemciyi kurmaz.

```dart
// Platform kodu: yalitilmis islemci
import 'dart:isolate';

Future<int> parseInBackground(String raw) {
  return Isolate.run(() => int.parse(raw));
}
```

Dönüş tipi yine bir `Future<int>`'tir. Çağıran taraf için fark yoktur: değer sonradan gelir ve ağaç, yukarıdaki iki ölçümde olduğu gibi, değer gelmeden ve geldikten sonra kurulur. Ağacın kuruluşu ana birimde kalır; yalıtılmış işlemci yalnız genişliği hesaplayan işi taşır.

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

Aşağıdaki blok bir bileşenin genişliği gelmeden önce ve geldikten sonra ağacın tamamını ölçer ve her ölçümün sayımını verir. Odak, ikinci ölçümdeki `/Screen/Body/Avatar`'dır.

```dart
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';

Future<int> loadWidth() async => 200;

Future<void> main() async {
  print('olcum 1: deger gelmeden once');
  final before = measure(buildBody(null), 360);
  printSizes(before);
  countSizings(before);

  final width = await loadWidth();
  print('olcum 2: deger geldikten sonra');
  final after = measure(buildBody(width), 360);
  printSizes(after);
  countSizings(after);
}
```

```text
olcum 1: deger gelmeden once
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Name genislik=120 kisit=0..328
esnetilen: /Screen/Body/Avatar genislik=328 kisit=0..328
dayatilan: 2
secilen: 1
kirpilan: 0
esnetilen: 1
ebeveyn: 3
olcum 2: deger geldikten sonra
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Name genislik=120 kisit=0..328
secilen: /Screen/Body/Avatar genislik=200 kisit=0..328
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 2
```

**Bu dersin boyutlanması: seçilen.** Değer gelmeden `Avatar` istek bildirmez, sütun kısıtı `0..328` olduğundan alabildiği kadar genişler ve esnetilen olur. Değer gelince `Avatar` 200 ister, 200 aralığın içindedir ve genişliği `Avatar` seçer. İki ölçüm arasında ağaçta yalnız `Avatar`'ın isteği değişmiştir ve boyutlanma bu tek düğümde el değiştirir. Sayım bunu özetler: ebeveyn 3'ten 2'ye iner.

#### Özet

- Bu dersin boyutlanması: seçilen. Değer gelmeden `/Screen/Body/Avatar` esnetilendir; 200 gelince isteği `0..328` aralığında kalır ve genişliği `Avatar` seçer.
- `Future<int>` ileride tamamlanacak bir değerdir. `async` işlev gelecek döndürür, `await` değer gelene kadar işlevi bekletir ve çağıran taraf bu sırada devam eder.
- Olay döngüsü senkron kodu bitirir, mikro görev kuyruğunu boşaltır, sonra görev kuyruğundan bir görev alır. Çıktıdaki numaralar bu sırayı gösterir.
- Başarısız bir gelecek `try`/`catch` ile yakalanıp yedek değere çevrilir. Akış zamana yayılmış değerler taşır ve her değer ağacı yeniden ölçtürür.
- Yalıtılmış işlemci ağır işi ayrı bir bellekte yürütür ve sonucu yine bir gelecek olarak döndürür.

#### Sonraki Adım

Ağaç şimdiye kadar `leaf`, `pad` ve `column` çağrılarının bileşimiydi. **Bileşen Kavramı** dersi bu çağrıları çerçevenin bileşen kavramına bağlar: bir bileşenin ne olduğunu, ağaçta nasıl iç içe girdiğini ve bir kartın başka bir kısıtta neden başka bir sahiple karşılaştığını ölçer.

### Bileşen Kavramı

Source: https://academia.sh/tr/kurslar/flutter/bilesen-kavrami

Her şeyin bileşen olduğu bir arayüz modelinde iç boşluk da bir bileşendir. Ders bileşenin değişmez bir tarif olduğunu ve bir kartın aynı isteğe rağmen konumuna göre neden başka bir sahiple karşılaştığını ölçer.

Önceki iki ders bileşeni bir ağaç düğümü olarak kullandı: `pad`, `column` ve `leaf` çağrıları iç içe geçip bir ekran kurdu. Bu ders o düğümün ne olduğunu söyler. Bu arayüz modelinde ekranda gördüğünüz hemen her şey bir **bileşendir**: metin, görsel, düğme; ama aynı zamanda iç boşluk, hizalama, sabit boyut ve sütun da. Bileşenler başka bileşenleri çocuk olarak alır ve ağaç bu iç içe geçişten oluşur.

#### Hemen Her Şey Bir Bileşendir

Birçok arayüz aracında iç boşluk bir bileşenin özelliğidir: kartın `margin` ya da `padding` ayarı vardır. Burada ise iç boşluk kendi başına bir bileşendir ve çocuğunu sarar. Hizalama ve sabit boyut da öyledir. Önce dersin çekirdeği gelir; bu, ilk dersin `sizing.dart` dosyasıdır ve hiç değişmemiştir.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Aşağıdaki parça bir kartı iç boşluk bileşeninin içine koyar. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: ic bosluk bileseni
Widget buildCard() {
  return const Padding(
    padding: EdgeInsets.all(16),
    child: Card(child: Text('Ozet')),
  );
}
```

`Padding` kartı sarar ve her yandan 16 birim boşluk bırakır; kart `Padding`'in `child` parametresidir. Modelde bu parçanın karşılığı `pad('Screen', 16, leaf('Card', 200))` çağrısıdır.

**DB1.** Model iç boşluğu, hizalamayı ve sabit boyutu birer düğüm olarak kurar (`pad`, `center`, `fixed`). Çerçevenin bileşen listesinin tamamını sunmaz; model çerçevenin kendisi değildir.

**DB2.** `Card` modelde yalnız genişlik isteyen bir yapraktır. Kartın içeriği, gölgesi ve köşeleri yoktur; geriye ölçülebilen tek şey olan 200 birimlik istek kalır.

İç boşluk bir bileşen olduğundan üst üste konabilir. Aşağıdaki blok iki iç boşluğu iç içe koyar ve her birinin kartın kısıtını ne kadar daralttığını gösterir.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Outer', 8, pad('Inner', 8, leaf('Card', 200)));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Outer genislik=360 kisit=360..360
dayatilan: /Outer/Inner genislik=344 kisit=344..344
dayatilan: /Outer/Inner/Card genislik=328 kisit=328..328
```

Her `pad` iki yandan kendi değerini çıkarır: 360 sıkı kısıttan 344, ondan 328 çıkar. Bileşenlerin hiçbiri kendi başına bir şey "hesaplamaz"; her biri kısıtı alır, çocuğa değiştirilmiş hâlini verir ve çocuğun cevabını işler.

#### Bileşen Bir Tariftir

Bir bileşen nesnesi ekranda duran bir şeyin kendisi değil, onun **tarifidir**. Nesne bir kez kurulur ve değişmez: alanları `final`, kurucusu `const`. Bir şey değişecekse yeni bir bileşen kurulur ve eskisi atılır. Bu seçim iki sonuç doğurur.

Birincisi, bileşenin kuruluşu ucuzdur. Tarif ekranın kendisini taşımadığı için bir ekranın tarifini yeniden kurmak, ekranı yeniden çizmek anlamına gelmez. İkincisi, aynı bileşen nesnesi birden çok yerde durabilir. Nesne hiçbir konum bilgisi taşımaz; konumu ağaçtaki yeri verir ve her yerde o yerin kısıtı ona iner. Aşağıdaki ölçümler bu ikinci sonucu gösterir.

#### Kısıt Aşağı, Boyut Yukarı

Bir ağaçta iki akış vardır ve ters yönlere gider. **Kısıtlar aşağı iner**: ebeveyn, çocuğa kabul edeceği aralığı verir. **Boyutlar yukarı çıkar**: çocuk o aralıkta bir genişlik seçer ve ebeveyne bildirir. Ebeveyn bu boyutu alır, kendi genişliğini ona göre belirler ve çocuğu yerleştirir.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, center('Center', column('Body', [
    leaf('A', 100),
    leaf('B', 250),
  ])));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Center genislik=328 kisit=328..328
secilen: /Screen/Center/Body genislik=250 kisit=0..328
secilen: /Screen/Center/Body/A genislik=100 kisit=0..328
secilen: /Screen/Center/Body/B genislik=250 kisit=0..328
```

`Screen`'den `Center`'a 328 sıkı kısıt iner. `Center` bu kısıtı gevşetir ve `Body`'ye `0..328` verir; `A` ve `B`'ye de aynı gevşek aralık ulaşır. Yukarı çıkan boyutlar şunlardır: `A` 100, `B` 250 bildirir; sütun bunların en genişini, 250'yi kendi genişliği yapar. Kısıt aşağı yönde azalarak, boyut yukarı yönde büyüyerek ilerlemiştir.

Jetpack Compose ile Arayüz kursunda "bu veri arayüz ağacında hangi yöne gidiyor?" diye sorulur ve yön, sorunun cevabıdır. Bu kursta yön sabittir: kısıt aşağı, boyut yukarı gider. Yön her ağaçta aynı olduğundan düğümleri birbirinden ayırmaz; boyutlanma ise ayırır. Boyutlanma, o iki akışın sonunda genişliği kimin belirlediğini sınıflar. Yukarıdaki çıktıda `Body` 250 genişliğindedir, çünkü çocuklarının en genişi onu seçmiştir; `Center` ise 328'dir, çünkü ebeveyni ona 328'i dayatmıştır.

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

Odak düğüm, iç boşluğun içindeki karttır: `/Screen/Card`. Kart 200 ister. `Screen` 360'lık sıkı kısıtı alır; iç boşluk bunu iki yandan 16 daraltır ve sıkı kalacak biçimde çocuğa iletir.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, leaf('Card', 200));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Card genislik=328 kisit=328..328
```

**Bu dersin boyutlanması: dayatılan.** `/Screen/Card` 200 ister ve 328..328 alır. Kısıt sıkıdır, yani en küçük ve en büyük genişlik aynıdır; kartın isteği sonucu değiştirmez. Kart 200 değil, 328 genişliğindedir ve bu genişliği ona `Screen` ile iç boşluk vermiştir.

Aynı kartı bir sütunun içine koyalım. Sütun kısıtı gevşetir ve kart ilk kez kendi isteğini söyleyebilir.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, column('Body', [leaf('Card', 200)]));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Card genislik=200 kisit=0..328
```

Kart aynı kart, isteği aynı 200'dür. `Body` sütunu 328 sıkı kısıtı alır ama çocuğuna `0..328` verir; bu aralıkta 200 geçerli bir seçimdir ve genişliği `Card` seçer. Aynı bileşen, aynı istek, iki farklı sonuç sahibi: konum sonucu belirlemiştir.

#### Özet

- Bu dersin boyutlanması: dayatılan. `/Screen/Card` 200 ister, iç boşluk sıkı kısıtı 328..328 olarak iletir ve genişliği kısıt belirler.
- Bu arayüz modelinde iç boşluk, hizalama ve sabit boyut birer bileşendir; ağaç bileşenlerin iç içe geçmesiyle kurulur.
- Bileşen değişmez bir tariftir: alanları `final`, kurucusu `const`. Aynı nesne birden çok yerde durabilir ve her yerde o yerin kısıtını alır.
- Kısıtlar aşağı iner, boyutlar yukarı çıkar. Boyutlanma yönü değil, bu iki akışın sonunda genişliğin sahibini sınıflar.
- Aynı kart, iç boşluğun içinde dayatılan, sütunun içinde seçilen olur: sonucu bileşenin isteği değil, konumu belirler.

#### Sonraki Adım

Bileşen değişmez bir tariftir; peki ekranda değişen bir şey nasıl gösterilir? **Durumsuz ve Durumlu Bileşenler** dersi, tarifin hiç değişmediği bileşeni ve kendi durumunu taşıyan bileşeni ayırır; durum değişince bir arama kutusunun genişliğini kimin verdiğini ölçer.

### Durumsuz ve Durumlu Bileşenler

Source: https://academia.sh/tr/kurslar/flutter/durumsuz-ve-durumlu-bilesenler

Durumsuz bileşen tarifini bir kez verir, durumlu bileşen kendi durumunu ayrı bir nesnede taşır. Ders durum değişince tarifin yeniden kurulduğunu ve bir arama kutusunun genişliğini yeniden düzenden sonra kimin verdiğini ölçer.

Bileşen değişmez bir tariftir. Peki bir arama kutusu açılıp kapandığında, ya da bir sayaç arttığında ne değişir? Tarif değişmediğine göre değişen bir şey başka bir yerde durmalıdır. Bu yer **durum**dur ve bileşenler durumu nerede tuttuklarına göre ikiye ayrılır.

#### Durumsuz Bileşen

**Durumsuz bileşen** yalnız aldığı parametrelerden kurulur. Aynı parametreyle her çağrıda aynı tarifi verir ve kendi başına hiçbir şey hatırlamaz. Başlık etiketi, simge ve bir satırın metni böyledir. Dersin çekirdeği yine ilk dersin `sizing.dart` dosyasıdır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Durumsuz bir bileşeni modelde, parametre alıp ağaç döndüren sıradan bir işlev olarak yazabiliriz. Aşağıdaki işlev bir arama kutusunun tarifini üretir; kutu açıksa genişliği boş yere yayılır, kapalıysa sabit bir istek bildirir.

**DB1.** Modelde durum, bir sınıfın alanıdır ve değişiklik `toggle` gibi sıradan bir yöntemle yapılır. Çerçevenin durum bildirme düzeni (`setState`) ölçülmez, KAYNAK parça olarak gösterilir.

```dart
// main.dart
import 'sizing.dart';

class SearchModel {
  bool expanded = false;
  void toggle() {
    expanded = !expanded;
  }
}

Part buildSearch(bool expanded) {
  final search = expanded ? fill('Search') : leaf('Search', 120);
  return pad('Screen', 16, column('Body', [search]));
}

void main() {
  final model = SearchModel();
  print('kapali');
  printSizes(measure(buildSearch(model.expanded), 360));
  model.toggle();
  print('acik');
  printSizes(measure(buildSearch(model.expanded), 360));
}
```

```text
kapali
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Search genislik=120 kisit=0..328
acik
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
esnetilen: /Screen/Body/Search genislik=328 kisit=0..328
```

`buildSearch` durumsuzdur: `expanded` değeri aynıysa her çağrı aynı ağacı verir. Durum `SearchModel` içinde durur ve işlevin dışındadır. Kutu kapalıyken `Search` 120 ister ve gevşek kısıtta 120 alır. Açıkken istek yoktur, üst sınır sonludur ve `Search` 328'in tamamını alır.

#### Durumlu Bileşen

**Durumlu bileşen** iki nesneden oluşur: değişmez tarifi taşıyan bileşen ve durumu taşıyan, ağaçta yaşamaya devam eden durum nesnesi. Çerçeve, durum nesnesinin `build` yöntemini her çağırdığında yeni bir tarif alır. Durumu değiştirmek için `setState` çağrılır; bu çağrı durumun değiştiğini bildirir ve çerçeveyi `build`'i yeniden çağırmaya yönlendirir.

```dart
// Platform kodu: durumlu bilesen
class SearchBox extends StatefulWidget {
  const SearchBox({super.key});

  @override
  State<SearchBox> createState() => _SearchBoxState();
}

class _SearchBoxState extends State<SearchBox> {
  bool expanded = false;

  void toggle() {
    setState(() {
      expanded = !expanded;
    });
  }

  @override
  Widget build(BuildContext context) {
    return expanded ? const TextField() : const SizedBox(width: 120);
  }
}
```

Bu parçada tarif (`SearchBox`) hiç değişmez; `expanded` değeri durum nesnesinde yaşar. `toggle` çağrıldığında `build` yeni bir alt ağaç döndürür. Modeldeki karşılığı `SearchModel` ile `buildSearch` ikilisidir.

#### Tarif Değişince Ne Değişir

Durum değiştiğinde yeniden kurulan şey tariftir. Modelde boyutu etkilemeyen bir durum, örneğin kutunun içindeki ipucu metni değiştiğinde, ağacın yapısı ve istekleri aynı kalır. Bu durumda ölçüm de aynı kalır.

```dart
// main.dart
import 'sizing.dart';

Part buildSearch(bool expanded, String hint) {
  final search = expanded ? fill('Search') : leaf('Search', 120);
  return pad('Screen', 16, column('Body', [search]));
}

void main() {
  for (final hint in ['Ara', 'Ders ara']) {
    print('ipucu: $hint');
    printSizes(measure(buildSearch(true, hint), 360));
  }
}
```

```text
ipucu: Ara
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
esnetilen: /Screen/Body/Search genislik=328 kisit=0..328
ipucu: Ders ara
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
esnetilen: /Screen/Body/Search genislik=328 kisit=0..328
```

`hint` işleve verilir ama ağaca girmez; iki çağrının çıktısı birebir aynıdır. Bir durum değişikliği yeniden kurmayı tetikler, fakat yeniden kurulan tarif aynı isteği bildiriyorsa genişliklerin hiçbiri oynamaz.

**DB2.** Modelde ipucu metni genişliği etkilemez; gerçek bir kutuda çok uzun bir ipucu metni kutunun isteğini değiştirebilir. Burada o durum modellenmez, çünkü model metin ölçmez.

SwiftUI ile Arayüz kursunda durum değişince arayüzün yeniden hesaplanmasını neyin tetiklediği sorulur: değişen değer, ona bağlı görünümler ve yeniden hesaplama zinciri. Bu derste soru o değildir. Burada "neden yeniden kuruldu" sorusu sorulmaz; yeniden kurulan ağaçta genişliği **kimin verdiği** sorulur. Tetikleyici aynı olsa da sonuç sahibi değişebilir: yukarıdaki kutu kapalıyken kendi isteğini, açıkken ebeveyninin kısıtını izler.

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

Odak düğüm arama kutusudur: `/Screen/Body/Search`. Kutu açıkken istek bildirmez ve üstteki aralık sonludur. İki ekran genişliğinde ölçelim.

```dart
// main.dart
import 'sizing.dart';

Part buildSearch(bool expanded) {
  final search = expanded ? fill('Search') : leaf('Search', 120);
  return pad('Screen', 16, column('Body', [search]));
}

void main() {
  for (final width in [480, 320]) {
    print('ekran: $width');
    printSizes(measure(buildSearch(true), width));
  }
}
```

```text
ekran: 480
dayatilan: /Screen genislik=480 kisit=480..480
dayatilan: /Screen/Body genislik=448 kisit=448..448
esnetilen: /Screen/Body/Search genislik=448 kisit=0..448
ekran: 320
dayatilan: /Screen genislik=320 kisit=320..320
dayatilan: /Screen/Body genislik=288 kisit=288..288
esnetilen: /Screen/Body/Search genislik=288 kisit=0..288
```

**Bu dersin boyutlanması: esnetilen.** `/Screen/Body/Search` açıkken istek bildirmez, üst sınır 448 ya da 288'dir ve düğüm onu doldurur. Kutunun genişliği ekran genişliğiyle birlikte değişir; kapalı hâlinde ise hiçbir ekranda oynamayan 120'dir. Aynı düğümün iki durumu iki farklı sonuç sahibine denk gelir: kapalıyken kendi isteği (seçilen), açıkken ebeveynin üst sınırı (esnetilen).

#### Özet

- Bu dersin boyutlanması: esnetilen. Açık arama kutusu istek bildirmez, ebeveynin üst sınırını doldurur ve genişliği 480 ekranda 448, 320 ekranda 288'dir.
- Durumsuz bileşen yalnız parametrelerden kurulur ve aynı parametreyle aynı tarifi verir.
- Durumlu bileşen, değişmez tarifini ve değişen durumunu iki ayrı nesnede taşır; durum değişince tarif yeniden kurulur.
- Yeniden kurulan tarif aynı isteği bildiriyorsa genişlikler oynamaz; ipucu metni değişse de iki ölçüm aynıdır.
- Aynı kutu kapalıyken seçilen, açıkken esnetilen olur: durum değişince sonucun sahibi de değişebilir.

#### Sonraki Adım

Bir bileşenin durumu değiştiğinde çerçeve ağacı yeniden kurar, ama ekrandaki nesneleri baştan yaratmaz. **Üç Ağaç Modeli** dersi, tarifin, yaşayan nesnenin ve ekrandaki düzenin üç ayrı ağaç olarak nasıl birbirine bağlandığını anlatır.

### Üç Ağaç Modeli

Source: https://academia.sh/tr/kurslar/flutter/uc-agac-modeli

Çerçeve aynı arayüzü üç ayrı ağaçta tutar: tarif ağacı, yaşayan öge ağacı ve düzeni yapan çizim nesnesi ağacı. Ders hangi ağacın ne yaptığını anlatır ve düzen sonucunun neden yalnız son ağaca ait olduğunu ölçer.

Bir önceki derste durum değişince tarifin yeniden kurulduğunu gördük. Ama ekrandaki nesneler her seferinde baştan yaratılsaydı, bir kaydırma konumu ya da bir yazı alanındaki imleç her yeniden kurulumda kaybolurdu. Çerçeve bu yüzden aynı arayüzü **üç ağaçta** tutar ve her ağacın ömrü başka bir hızda akar.

#### Tarif Ağacı

Birinci ağaç, yazdığınız bileşenlerin ağacıdır. Bu ağaç değişmez tariflerden oluşur ve her yeniden kurulumda baştan üretilir. Ucuzdur, çünkü yalnız "ne istiyorum" sorusunu cevaplar: ekranda bir iç boşluk olsun, içinde ortalanmış bir logo olsun.

Dersin çekirdeği yine ilk dersin `sizing.dart` dosyasıdır; model bu ağacın karşılığıdır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Modeldeki `pad('Screen', 16, center('Center', fixed('Logo', 96, leaf('Image', 200))))` çağrısı bir tarif ağacıdır. Düğümler yalnız ad, tür ve istek taşır; konum ya da kalıcı bir durum taşımaz.

**DB1.** Modelin ağacı tek bir ağaçtır. Çerçevedeki üç ağaç modelde ayrı ayrı kurulmaz; üçüncü ağacın yaptığı iş, yani kısıtı aşağı indirip boyutu yukarı çıkarmak `measure` çağrısıdır.

#### Öge Ağacı

İkinci ağaç, **öge ağacı**dır. Çerçeve bir tarifi ilk kez gördüğünde ona karşılık gelen bir öge yaratır. Öge, ağaçta o tarifin yerini tutar ve yaşar: durum nesneleri ögelere bağlıdır. Yeniden kurulumda çerçeve yeni tarifi eski ögeyle karşılaştırır. Tür ve anahtar aynıysa öge korunur ve yeni tarifi alır; farklıysa öge atılır ve yenisi yaratılır.

Bu karşılaştırma durumun neden yaşadığını açıklar. Durumlu bir bileşenin durum nesnesi ögeye bağlıdır; öge korunduğu sürece durum da korunur. Bir önceki dersteki arama kutusunun `expanded` değeri, kutunun ögesi yaşadığı için yaşamaya devam eder.

#### Çizim Nesnesi Ağacı

Üçüncü ağaç **çizim nesnesi ağacı**dır. Boyutu hesaplayan, düğümü yerleştiren ve ekrana çizen nesneler buradadır. Kısıtın aşağı indiği ve boyutun yukarı çıktığı akış bu ağaçta işler. Aşağıdaki parça bir çizim nesnesinin düzeni nasıl yaptığını gösterir; model dışıdır ve ölçülmez.

```dart
// Platform kodu: cizim nesnesi
class RenderLogo extends RenderBox {
  @override
  void performLayout() {
    size = constraints.constrain(const Size(96, 96));
  }
}
```

`performLayout`, nesnenin kısıtı alıp boyutunu seçtiği yerdir. `constraints.constrain` isteği kısıta sıkıştırır; istek aralığın içindeyse olduğu gibi kalır. Modelin `classify` kuralı bu davranışı sadeleştirir.

Üç ağaç şöyle bölüşür: tarif "ne istediğini" söyler, öge "kim kimdir, kimin durumu var" bilgisini taşır, çizim nesnesi "ne kadar yer kaplıyor" sorusunu cevaplar. Genişliği belirleyen ağaç her zaman sonuncusudur.

#### Aynı Düğüm, Başka Kısıt

Şimdi aynı ağacı ölçelim. `center` kısıtı gevşetir, `fixed` ise çocuğuna sıkı bir genişlik verir.

```dart
// main.dart
import 'sizing.dart';

Part logoTree(int imageWidth) {
  return pad('Screen', 16, center('Center', fixed('Logo', 96, leaf('Image', imageWidth))));
}

void main() {
  printSizes(measure(logoTree(200), 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Center genislik=328 kisit=328..328
secilen: /Screen/Center/Logo genislik=96 kisit=0..328
dayatilan: /Screen/Center/Logo/Image genislik=96 kisit=96..96
```

`Center`, ebeveyninden 328 sıkı kısıt alır ve onu `Logo`'ya `0..328` olarak gevşetir. `Logo` 96 ister ve aralıkta olduğu için 96'yı seçer. `Logo`'nun içindeki `Image` ise 200 ister ama `fixed` ona 96..96 verir ve genişliği dayatılır.

Şimdi iki şeyi karşılaştıralım. Önce tarif değişir: `Image` isteği 200'den 120'ye iner ve yollar aynı kalır, yani öge ağacı korunur. Sonra tarif aynı kalır, ekran 360'tan 320'ye iner.

```dart
// main.dart
import 'sizing.dart';

Part logoTree(int imageWidth) {
  return pad('Screen', 16, center('Center', fixed('Logo', 96, leaf('Image', imageWidth))));
}

void main() {
  final first = measure(logoTree(200), 360);
  final newRecipe = measure(logoTree(120), 360);
  final newScreen = measure(logoTree(200), 320);
  final samePaths = [for (final e in first) e.path].join(',') ==
      [for (final e in newRecipe) e.path].join(',');
  print('korunan yollar: $samePaths');
  print('Image, yeni tarif: ${first[3].width} -> ${newRecipe[3].width}');
  print('Center, yeni ekran: ${first[1].width} -> ${newScreen[1].width}');
  print('Logo, yeni ekran: ${first[2].width} -> ${newScreen[2].width}');
}
```

```text
korunan yollar: true
Image, yeni tarif: 96 -> 96
Center, yeni ekran: 328 -> 288
Logo, yeni ekran: 96 -> 96
```

Tarif değişince düğümlerin yolları aynı kalır, yani çerçeve ögeleri korur. `Image` isteğini 120'ye indirmiştir ama `fixed` yine 96 dayattığından genişliği değişmez. Ekran değişince ise ağaç aynıdır, yine de `Center` 328'den 288'e iner: korunan düğüm kısıtı değişince başka bir genişlik alır. `Logo` ise ikisinde de 96'dır.

React Native kursunda güncellemenin görünüm ağacındaki bir düğüme ne yaptığı sorulur: korundu mu, güncellendi mi, kuruldu mu, söküldü mü. Bu akıbet iki ağacın karşılaştırmasıdır. Boyutlanma ise tek ağacın düzenidir ve o karşılaştırmanın sonucunu bilmeden de sorulur. Yukarıdaki çıktıda `Center`'ın korunduğunu biliyoruz; yine de genişliği ekrana göre değişiyor. Korunma genişliği garanti etmez.

**DB2.** Ögenin korunması yalnız durumun ve kimliğin korunması demektir. Bir düğümün bir önceki ölçümdeki genişliğini koruyacağı anlamına gelmez; genişliği her ölçümde o anki kısıt belirler.

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

Odak düğüm `/Screen/Center/Logo`'dur. Aynı ağacı iki ekran genişliğinde ölçelim.

```dart
// main.dart
import 'sizing.dart';

Part logoTree() {
  return pad('Screen', 16, center('Center', fixed('Logo', 96, leaf('Image', 200))));
}

void main() {
  for (final width in [480, 320]) {
    print('ekran: $width');
    printSizes(measure(logoTree(), width));
  }
}
```

```text
ekran: 480
dayatilan: /Screen genislik=480 kisit=480..480
dayatilan: /Screen/Center genislik=448 kisit=448..448
secilen: /Screen/Center/Logo genislik=96 kisit=0..448
dayatilan: /Screen/Center/Logo/Image genislik=96 kisit=96..96
ekran: 320
dayatilan: /Screen genislik=320 kisit=320..320
dayatilan: /Screen/Center genislik=288 kisit=288..288
secilen: /Screen/Center/Logo genislik=96 kisit=0..288
dayatilan: /Screen/Center/Logo/Image genislik=96 kisit=96..96
```

**Bu dersin boyutlanması: seçilen.** `/Screen/Center/Logo` 96 ister; `Center` kısıtı gevşettiği için aralık `0..448` ya da `0..288` olur ve 96 bunun içindedir. Genişliği düğümün kendisi seçer. Ekran değişse de `Logo` 96'da kalır, `Center` ise 448 ve 288 alır. İçindeki `Image` bu durumda dayatılandır: genişliğini `fixed` verir.

#### Özet

- Bu dersin boyutlanması: seçilen. `/Screen/Center/Logo` gevşetilmiş bir aralıkta 96 ister ve iki ekran genişliğinde de 96'yı seçer.
- Çerçeve aynı arayüzü üç ağaçta tutar: değişmez tariflerden oluşan bileşen ağacı, tarifin yerini tutan öge ağacı ve düzeni yapan çizim nesnesi ağacı.
- Yeniden kurulumda tarif yeni gelir; tür ve anahtar aynıysa öge korunur, durum da onunla birlikte korunur.
- Genişliği her zaman çizim nesnesi ağacı belirler; korunma genişliği garanti etmez.
- Aynı ağaçta `Center` 328, 448 ve 288 alır, `Logo` 96'da kalır: korunan düğüm kısıtı değişince başka genişlik alabilir.

#### Sonraki Adım

Çizim nesnesi ağacında kısıtı geçiren bileşenlerin çoğu düzen bileşenidir. **Düzen Bileşenleri** dersi sütun, satır ve esnek çocukları ele alır; esnek bir çocuğun içindeki bileşenin genişliğini kimin verdiğini ölçer.

### Düzen Bileşenleri

Source: https://academia.sh/tr/kurslar/flutter/duzen-bilesenleri

Sütun, satır ve esnek çocuk, kısıtı aşağı geçiren düzen bileşenleridir. Ders sıkı ve gevşek esnek çocuğu ayırır ve esnek bir çocuk olmanın içindeki bileşeni esnetilen yapmadığını ölçerek gösterir.

Önceki derste çizim nesnesi ağacının kısıtı aşağı geçirdiğini gördük. Bu geçişi ağaçta en çok **düzen bileşenleri** yapar: sütun, satır ve esnek çocuk. Bu bileşenlerin kendileri ekranda bir şey göstermez; çocuklarına hangi aralığın verileceğine karar verirler.

#### Sütun ve Satır

**Sütun**, çocuklarını alt alta dizer. Kendisine gelen kısıtı gevşetir ve her çocuğa `0..en büyük` aralığını verir. Kendi genişliği, çocuklarının en genişidir.

**Satır**, çocuklarını yan yana dizer. Burada fark vardır: satırın genişliği çocuklar arasında paylaşılır. Esnek olmayan çocuğa `0..sonsuz` verilir, yani çocuk istediği kadar geniş olabilir. Esnek çocuklar ise geriye kalan payı paylaşır. Dersin çekirdeği yine ilk dersin `sizing.dart` dosyasıdır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

**DB1.** Modelde satır, esnek olmayan çocukları önce yerleştirir, kalan payı sonra esnek çocuklara dağıtır. Çerçevenin ana eksen hizalama ve ikinci eksen seçenekleri modellenmez; model yalnız genişliği ölçer.

#### Esnek Çocuk

Esnek çocuk, satırın kalan payından bir dilim alan çocuktur. Dilimin büyüklüğünü **esneklik çarpanı** belirler: çarpanı 2 olan çocuk, çarpanı 1 olandan iki kat pay alır. Çerçevede esnek çocuk iki biçimdedir ve fark, payı nasıl kullandıklarındadır.

```dart
// Platform kodu: esnek cocuklar
Row(
  children: [
    const Icon(Icons.person),
    const Expanded(child: Text('Ad Soyad')),
    const Flexible(child: Text('Not')),
  ],
)
```

`Expanded` çocuğa payın tamamını **sıkı** olarak verir: çocuk payı mutlaka doldurur. `Flexible` ise payı **gevşek** verir: çocuk payın içinde istediği genişliği seçer ve daha azını alabilir. Modeldeki karşılıkları `flex(..., tight: true)` ve `flex(..., tight: false)` çağrılarıdır.

**DB2.** Modelde esnek çocuk, çarpanı ve sıkı ya da gevşek oluşu dışında hiçbir şey bilmez. Çerçevenin esnek çocuğa uyguladığı başka bir kuralı, örneğin taşma davranışını, model içermez.

##### Sıkı Esnek Çocuk

Bir satırda soldaki simge sabit 48 genişliğinde, yanındaki ad ise sıkı esnek çocuktur.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Name', 1, fill('Text')),
  ]));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
dayatilan: /Screen/Line/Name genislik=280 kisit=280..280
dayatilan: /Screen/Line/Name/Text genislik=280 kisit=280..280
```

Satır önce esnek olmayan çocuğu yerleştirir: `Icon` 48 ister ve 48'i seçer. Geriye 328 eksi 48, yani 280 kalır. Tek esnek çocuk olan `Name` payın tamamını sıkı olarak alır. Sıkı bir kısıt, çocuğun isteği ne olursa olsun genişliği belirler; bu yüzden hem `Name` hem de içindeki `Text` dayatılandır.

##### Gevşek Esnek Çocuk

Şimdi aynı satırda ad yerine gevşek bir esnek çocuk koyalım.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Note', 1, fill('Text'), tight: false),
  ]));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=280 kisit=0..280
esnetilen: /Screen/Line/Note/Text genislik=280 kisit=0..280
```

Pay yine 280'dir ama bu kez `Note`'a `0..280` olarak verilir. `Text` istek bildirmediği için üst sınırı doldurur ve **esnetilen** olur. `Note` ise çocuğunun bildirdiği 280'i aralığın içinde seçilmiş bir değer olarak alır: **seçilen**. Esnek çocuk olan `Note`'un kendisi esnetilen değildir; esnetilen, onun içindeki `Text`'tir.

Gevşek çocuğun içi istek bildiren bir yaprak olursa durum değişir.

```dart
// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Note', 1, leaf('Text', 120), tight: false),
  ]));
  printSizes(measure(tree, 360));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=120 kisit=0..280
secilen: /Screen/Line/Note/Text genislik=120 kisit=0..280
```

`Text` 120 ister ve `0..280` aralığında 120'yi seçer; `Note` da çocuğu kadar, yani 120 kadar yer alır. Pay 280 olmasına rağmen ikisi de 120'de kalır. Gevşek esnek çocuk payı kullanma hakkı verir, kullanma zorunluluğu getirmez.

Bu üç ölçüm bir ayrımı gösterir: **esnek çocuk olmak ile esnetilen olmak aynı şey değildir.** Esnek çocuk, satırın bir yerleşim rolüdür. Esnetilen ise bir düğümün genişliğini nasıl aldığını anlatan bir sınıftır: istek yok, üst sınır sonlu ve düğüm onu dolduruyor. Sıkı esnek çocuk dayatılan, gevşek esnek çocuk seçilen çıktı; esnetilen olan, gevşek çocuğun içindeki isteksiz yapraktı.

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

Odak düğüm, gevşek esnek çocuğun içindeki metindir: `/Screen/Line/Note/Text`. Aynı satırı iki ekran genişliğinde ölçelim.

```dart
// main.dart
import 'sizing.dart';

void main() {
  for (final width in [480, 320]) {
    print('ekran: $width');
    final tree = pad('Screen', 16, row('Line', [
      fixed('Icon', 48, leaf('Glyph', 48)),
      flex('Note', 1, fill('Text'), tight: false),
    ]));
    printSizes(measure(tree, width));
  }
}
```

```text
ekran: 480
dayatilan: /Screen genislik=480 kisit=480..480
dayatilan: /Screen/Line genislik=448 kisit=448..448
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=400 kisit=0..400
esnetilen: /Screen/Line/Note/Text genislik=400 kisit=0..400
ekran: 320
dayatilan: /Screen genislik=320 kisit=320..320
dayatilan: /Screen/Line genislik=288 kisit=288..288
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=240 kisit=0..240
esnetilen: /Screen/Line/Note/Text genislik=240 kisit=0..240
```

**Bu dersin boyutlanması: esnetilen.** `/Screen/Line/Note/Text` istek bildirmez, üst sınırı sonludur ve onu doldurur: 480 ekranda 400, 320 ekranda 240. Genişliği ne `Text`'in isteği ne de kendi seçimi belirler; ebeveyninin payı belirler. `Note` ise ikisinde de seçilendir, çünkü çocuğunun bildirdiği genişlik aralığın içinde kalır.

#### Özet

- Bu dersin boyutlanması: esnetilen. Gevşek esnek çocuğun içindeki metin istek bildirmez ve payı doldurur: 360 ekranda 280, 480 ekranda 400, 320 ekranda 240.
- Sütun kısıtı gevşetir ve çocuklarının en genişini kendi genişliği yapar; satır esnek olmayan çocuklara `0..sonsuz` verir, kalan payı esnek çocuklara dağıtır.
- Sıkı esnek çocuk payın tamamını sıkı kısıt olarak alır ve dayatılan olur; gevşek esnek çocuk payı üst sınır olarak alır.
- Gevşek esnek çocuğun içi sabit bir yaprak olursa pay boşta kalır; çocuk seçilen olur.
- Esnek çocuk olmak bir yerleşim rolüdür; esnetilen olmak ise genişliğin nasıl alındığının sınıfıdır. İkisi aynı şey değildir.

#### Sonraki Adım

Bu konunun son dersi, aynı arayüzü farklı platform bileşen aileleriyle kurmayı ele alır. **Platform Bileşen Aileleri** dersi, iki tasarım dilinin bileşen kümelerini ve bir eylem düğmesinin genişliğini sabit bir yuvanın nasıl koruduğunu ölçer.

### Platform Bileşen Aileleri

Source: https://academia.sh/tr/kurslar/flutter/platform-bilesen-aileleri

Çerçeve iki platformun tasarım dili için ayrı bileşen kümeleri sunar. Ders ailelerin farkını, platforma göre seçimi ve aynı eylem düğmesinin iki ailede farklı istek bildirmesine rağmen sabit bir yuvada aynı genişliği almasını ölçer.

Önceki derslerde bileşenin değişmez bir tarif olduğunu, kısıtın aşağı indiğini ve düzen bileşenlerinin payı paylaştırdığını gördük. Bu son derste bileşenlerin hangi **kümeden** geldiğine bakıyoruz. Çerçeve, iki platformun tasarım diline ait ayrı bileşen kümeleri sunar. Bir eylem düğmesinin bir kümedeki biçimi ile diğerindeki biçimi farklı görünür, farklı içeriğe ve farklı isteğe sahip olabilir.

#### İki Tasarım Dili, İki Küme

Birinci küme Material tasarım dilini uygular ve Android'de varsayılan görünümdür; ikinci küme iOS'un tasarım dilini uygular. İki kümede de düğme, anahtar, iletişim kutusu ve gezinme çubuğu gibi bileşenlerin karşılığı vardır, ama her biri kendi platformunun alışkanlıklarını taşır: boşluklar, köşe yuvarlaklığı ve dokunma davranışı küme başına farklıdır.

Bu fark boyuta da yansır. Aynı iş için kurulan iki düğme, genişlik olarak farklı bir istek bildirebilir. Dersin çekirdeği yine ilk dersin `sizing.dart` dosyasıdır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

**DB1.** Modelde aile, düğmenin bildirdiği istek olarak temsil edilir: bir ailede 88, diğerinde 64. Düğmenin biçimi, rengi ve dokunma davranışı modellenmez; yalnız genişlik isteği ölçülür.

#### Platforma Göre Seçim

Bir arayüzü iki platformda da doğal göstermek için uygulama hangi kümeyi kullanacağını çalışma anında seçer. Çerçeve bu seçimi iki yolla destekler. İlki, platformu sorup uygun bileşeni döndürmektir. İkincisi, iki kümeyi birlikte sarmalayan **uyarlanır** bileşenlerdir: bu bileşenler platforma göre uygun kümenin karşılığını kendileri üretir.

```dart
// Platform kodu: platforma gore secim
Widget buildAction(BuildContext context) {
  final platform = Theme.of(context).platform;
  return switch (platform) {
    TargetPlatform.iOS => CupertinoButton(onPressed: () {}, child: const Text('Kaydet')),
    _ => ElevatedButton(onPressed: () {}, child: const Text('Kaydet')),
  };
}
```

Seçim bir koşuldur, bir sınıf hiyerarşisi değildir: iOS ise ikinci kümeden, değilse birinci kümeden bileşen döner. Modelde karşılığı, platform adını alıp aile adını döndüren sıradan bir işlevdir.

```dart
// main.dart
String familyFor(String platform) => platform == 'ios' ? 'ios' : 'android';

void main() {
  for (final platform in ['android', 'ios', 'linux']) {
    print('$platform: ${familyFor(platform)}');
  }
}
```

```text
android: android
ios: ios
linux: android
```

Tanınmayan bir platform için varsayılan aile `android`'dir; işlev her platform adına bir aile döndürür. Seçim kodu ölçüm yapmaz, yalnız kimin bileşeninin kullanılacağını söyler.

Ortak kodun ve platform kodunun nasıl paylaşıldığı Çapraz Platform Yaklaşımları kursunda sorulur: bu özelliğin kodu platformlar arasında nasıl paylaşılıyor. Bu derste soru başkadır. Aile, aynı kısıtta başka bir istek bildirebilir; boyutlanma ise isteğin sonuca yansıyıp yansımadığını, yani genişliği kimin verdiğini sınıflar. Ailenin farkı, ancak bir şey bu farkı sınırlamıyorsa genişlikte görünür.

#### Yuva Genişliği Farkı Siler

Eylem düğmesini önce sabit genişlikli bir **yuvanın** içine koyalım. Yuva, `fixed` ile kurulan 120 genişliğinde bir hücredir ve düğme onun çocuğudur.

```dart
// main.dart
import 'sizing.dart';

Part buildBar(String family) {
  final requested = family == 'android' ? 88 : 64;
  return pad('Screen', 16, column('Bar', [
    fixed('Action', 120, leaf('Button', requested)),
  ]));
}

void main() {
  for (final family in ['android', 'ios']) {
    print('aile: $family');
    printSizes(measure(buildBar(family), 360));
  }
}
```

```text
aile: android
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Bar genislik=328 kisit=328..328
secilen: /Screen/Bar/Action genislik=120 kisit=0..328
dayatilan: /Screen/Bar/Action/Button genislik=120 kisit=120..120
aile: ios
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Bar genislik=328 kisit=328..328
secilen: /Screen/Bar/Action genislik=120 kisit=0..328
dayatilan: /Screen/Bar/Action/Button genislik=120 kisit=120..120
```

İki ailenin düğmesi farklı isteklerle gelir, 88 ve 64. Yuva ise çocuğuna kendi genişliğini sıkı olarak verir: `Button` her iki ailede de 120..120 alır ve genişliği dayatılır. İstekteki fark genişliğe yansımaz.

Yuvayı kaldırırsak durum değişir.

```dart
// main.dart
import 'sizing.dart';

Part buildBar(String family) {
  final requested = family == 'android' ? 88 : 64;
  return pad('Screen', 16, column('Bar', [leaf('Button', requested)]));
}

void main() {
  for (final family in ['android', 'ios']) {
    print('aile: $family');
    printSizes(measure(buildBar(family), 360));
  }
}
```

```text
aile: android
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Bar genislik=328 kisit=328..328
secilen: /Screen/Bar/Button genislik=88 kisit=0..328
aile: ios
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Bar genislik=328 kisit=328..328
secilen: /Screen/Bar/Button genislik=64 kisit=0..328
```

Yuva yokken sütun kısıtı gevşetir, düğme kendi isteğini bildirir ve iki aile iki ayrı **seçilen** genişlik verir: 88 ve 64. Aile farkı artık genişlikte görünür. Yuva, bu farkı silen sıkı kısıttır.

**DB2.** Yuvanın 120 olması bir tasarım kararıdır; model bu sayıyı açıklamaz. Ölçülen tek şey, sıkı bir kısıtın isteği genişlikten silmesidir.

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

Odak düğüm yuvanın içindeki düğmedir: `/Screen/Bar/Action/Button`. Yuvayla kurulmuş çubuğu iki ekran genişliğinde ve iki ailede ölçelim.

```dart
// main.dart
import 'sizing.dart';

Part buildBar(String family) {
  final requested = family == 'android' ? 88 : 64;
  return pad('Screen', 16, column('Bar', [
    fixed('Action', 120, leaf('Button', requested)),
  ]));
}

void main() {
  for (final width in [480, 320]) {
    for (final family in ['android', 'ios']) {
      print('ekran: $width, aile: $family');
      final log = measure(buildBar(family), width);
      print('${log[3].sizing}: ${log[3].path} genislik=${log[3].width}');
    }
  }
}
```

```text
ekran: 480, aile: android
dayatilan: /Screen/Bar/Action/Button genislik=120
ekran: 480, aile: ios
dayatilan: /Screen/Bar/Action/Button genislik=120
ekran: 320, aile: android
dayatilan: /Screen/Bar/Action/Button genislik=120
ekran: 320, aile: ios
dayatilan: /Screen/Bar/Action/Button genislik=120
```

**Bu dersin boyutlanması: dayatılan.** `/Screen/Bar/Action/Button` iki ailede ve iki ekran genişliğinde 120'ye dayatılır. Düğmenin 88 ya da 64 istemesi sonucu değiştirmez; genişliği yuva verir. Ailenin seçimi yalnız yuva olmadığında genişliği belirler.

#### Özet

- Bu dersin boyutlanması: dayatılan. `/Screen/Bar/Action/Button` iki ailede ve her ekran genişliğinde 120'ye dayatılır; isteğin farkı genişliğe yansımaz.
- Çerçeve iki platformun tasarım diline ait ayrı bileşen kümeleri sunar; uyarlanır bileşenler uygun kümeyi platforma göre kendileri seçer.
- Platforma göre seçim bir koşuldur: platform sorulur, uygun kümeden bileşen döner.
- Aileler aynı kısıtta farklı istek bildirebilir: yuva olmadan 88 ve 64, iki ayrı seçilen genişlik.
- Sabit bir yuva sıkı kısıt verir ve ailenin istek farkını siler.

#### Sonraki Adım

Bu konu Dart dilinden başlayıp bileşenlerin ağaç içindeki yerine ve kümelerine kadar geldi. Sıradaki konu, bileşenlerin durumunun uygulama genelinde nasıl yönetildiğini ele alır. **Durum Yönetimi Yaklaşımları** dersi, sağlayıcı, olay tabanlı ve tepkisel modelleri karşılaştırır.

## Uygulama Yapısı

Durum yönetimi yaklaşımları, gezinme ve yönlendirme, ağ ve dizileştirme, yerel depolama, varlıklar ve yazı tipleri, animasyonlar, platform kanalları.

### Durum Yönetimi Yaklaşımları

Source: https://academia.sh/tr/kurslar/flutter/durum-yonetimi-yaklasimlari

Paylaşılan durum bir bileşenin isteğini değiştirir, genişliğini kimin belirlediğini değiştirmez. Ders bir sayaçtan beslenen rozetin genişliğini ölçer ve satırın sınırsız kısıtında rozetin seçilen kaldığını gösterir.

Önceki ders, platforma göre değişen bileşen ailelerinin eylem düğmesine farklı genişlikler istetse de sabit bir yuvada aynı sonucu verdiğini ölçtü. Bu ders isteğin kaynağını uygulamanın durumuna taşır. Bir sayaç, bir oturum bilgisi ya da bir sepet toplamı birden çok bileşenin birlikte okuduğu değerdir ve böyle bir değer bileşenin isteğini değiştirebilir. Kursun sorusu aynı kalır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, paylaşılan bir sayaçtan beslenen bir rozet üzerinde arar.

#### Durumu Paylaşmanın Üç Yolu

Uygulamanın durumu tek bir bileşenin içine sığmadığında durum, ağacın üstünde ortak bir yerde tutulur ve alttaki bileşenler onu okur. Bu paylaşımın üç biçimi vardır. Kitaplık adları bu derste yazılmaz; her biri işleviyle anılır.

**Sağlayıcı (provider)** yaklaşımında değer ağacın üst bir noktasında durur. Altındaki bileşenler değeri bağlamdan okur ve değer değişince yalnız onu okuyan bileşenler yeniden kurulur. Paylaşılan durum kitaplıkları bu kalıbı kullanır.

**Olay tabanlı (event-based)** yaklaşımda bileşen durumu doğrudan değiştirmez, bir olay gönderir. Bir işleyici olayı alır ve yeni durumu üretir. Olay tabanlı durum kitaplıkları değişikliğin yolunu bu biçimde tek bir noktaya toplar.

**Tepkisel (reactive)** yaklaşımda durum dinlenebilir bir değerdir. Dinleyiciler değere abone olur ve her değişimde haber alır; değer bir akış ya da bir bildirim nesnesi olabilir.

Üç yaklaşım da aynı yere varır: durum değişir, ilgili bileşenin `build` yöntemi yeni durumdan yeni bir bileşen alt ağacı üretir ve çerçeve düzeni yeniden koşturur.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça sağlayıcı kalıbının çerçeve tarafını gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: saglayici bileseni
class CounterScope extends InheritedNotifier<ValueNotifier<int>> {
  const CounterScope({super.key, required ValueNotifier<int> counter, required super.child})
      : super(notifier: counter);

  static int of(BuildContext context) {
    final scope = context.dependOnInheritedWidgetOfExactType<CounterScope>();
    return scope!.notifier!.value;
  }
}
```

Parçada sayaç değeri ağacın üstünde bir bildirim nesnesinde durur. `of` çağrısı yapan bileşen o değere bağımlı olur; değer değişince çerçeve yalnız bu bağımlı bileşenleri yeniden kurar. Hangi yaklaşım seçilirse seçilsin düzen aşamasının girdisi değişmez: kısıt aşağıdan gelir, bileşenin isteği ise artık durumdan hesaplanır.

#### Sayaçtan Rozete

Modelin durumu paylaşılan bir sayaçtır. Sayacı bir yardımcı dosya tutar: değeri saklar, dinleyicileri çağırır ve çıktıya hiçbir şey basmaz. Bu, tepkisel kalıbın en küçük hâlidir.

```dart
// store.dart — paylasilan sayac deposu; her degisimde dinleyicilere haber verir
class CounterStore {
  int count;
  final List<void Function(int)> _listeners = [];
  CounterStore(this.count);

  void listen(void Function(int) listener) => _listeners.add(listener);

  void update(int value) {
    count = value;
    for (final listener in _listeners) {
      listener(count);
    }
  }
}
```

Rozetin isteği sayacın basamak sayısına bağlıdır: her basamak 12 birim ekler ve rozet 18 birimlik sabit bir boşlukla başlar. `buildHeader` depodan ağaç üretir: ekranın içinde bir sütun, sütunun içinde başlık ile rozeti taşıyan bir satır. Genişlikler modelin birimidir.

```dart
// main.dart
import 'sizing.dart';
import 'store.dart';

Part buildHeader(CounterStore store, {int title = 120}) {
  final digits = '${store.count}'.length;
  return pad('Screen', 16, column('Body', [
    row('Header', [
      leaf('Title', title),
      leaf('Badge', 18 + 12 * digits),
    ]),
  ]));
}

void main() {
  final store = CounterStore(9);
  final log = measure(buildHeader(store), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
esnetilen: /Screen/Body/Header genislik=328 kisit=0..328
secilen: /Screen/Body/Header/Title genislik=120 kisit=0..sonsuz
secilen: /Screen/Body/Header/Badge genislik=30 kisit=0..sonsuz
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 1
ebeveyn: 3
```

Sayaç 9 iken rozet tek basamaklıdır ve 30 birim ister. Satır sınırlı bir kısıt aldığı için isteğini bildirmez; ama esnek olmayan iki çocuğuna sınırsız kısıt verir. `Title` 120, `Badge` 30 ister ve ikisi de sınırsız aralığın içinde kaldığından **seçilen**dir. Genişliği başlık ve rozet kendileri belirler; ebeveyn yalnız ekran, sütun ve satır kayıtlarında sayılır.

#### Sayaç Değişince

Şimdi sayaca abone olan bir dinleyici ekleyelim. Dinleyici her değişimde ağacı yeniden üretir, yeniden ölçer ve yalnız rozetin kaydını basar. Sayaç 9'dan 1000'e ilerlerken 10 ve 99 aynı genişliği verecek, 100 ve 1000 farklı basamak sayısına geçecek.

```dart
// main.dart
import 'sizing.dart';
import 'store.dart';

Part buildHeader(CounterStore store, {int title = 120}) {
  final digits = '${store.count}'.length;
  return pad('Screen', 16, column('Body', [
    row('Header', [
      leaf('Title', title),
      leaf('Badge', 18 + 12 * digits),
    ]),
  ]));
}

void main() {
  final store = CounterStore(9);
  store.listen((value) {
    final log = measure(buildHeader(store), 360);
    final badge = log.firstWhere((entry) => entry.path.endsWith('/Badge'));
    print('sayac=$value ${badge.sizing} genislik=${badge.width} kisit=${badge.limit.bounds()}');
  });
  for (final value in [9, 10, 99, 100, 1000]) {
    store.update(value);
  }
}
```

```text
sayac=9 secilen genislik=30 kisit=0..sonsuz
sayac=10 secilen genislik=42 kisit=0..sonsuz
sayac=99 secilen genislik=42 kisit=0..sonsuz
sayac=100 secilen genislik=54 kisit=0..sonsuz
sayac=1000 secilen genislik=66 kisit=0..sonsuz
```

Rozet 9'da 30, 99'a kadar 42, 100'de 54 ve 1000'de 66 birim genişliğindedir. Her satırda boyutlanma aynı kalır: `secilen`. Genişlik büyüdü, çünkü rozetin isteği büyüdü; ama genişliği hâlâ rozet belirliyor. Kısıt `0..sonsuz` olduğu için isteği hiçbir değer aralığın dışına itemez. Kural durumun kendisini değil, durumun isteğe yansıyan kısmını görür: 10 ile 99 farklı sayılardır, ama aynı basamak sayısını verdikleri için aynı kayıt çıkar.

Olay tabanlı bir kitaplıkla da tepkisel bir kitaplıkla da sonuç aynıdır. Yaklaşımlar durumun nasıl taşındığını değiştirir; düzen aşamasına giren şey yine kısıt ve istektir.

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

Satır esnek olmayan çocuklarına sınırsız kısıt verdiği için rozetin büyümesini hiçbir şey durdurmaz. Çocukların toplamı satırın kendi aralığını aşabilir. Bunu görmek için ekranı 200 birime daraltalım ve başlığa 160 birim isteyelim. Sayaç 1000'de, rozet 66 birimdir. Model taşmayı göstermez: kaydın dili boyutlanmadır ve her kayıt yalnız kendi kısıtına bakar. Çocukların toplamını ders ayrı bir satırda hesaplar ve basar.

```dart
// main.dart
import 'sizing.dart';
import 'store.dart';

Part buildHeader(CounterStore store, {int title = 120}) {
  final digits = '${store.count}'.length;
  return pad('Screen', 16, column('Body', [
    row('Header', [
      leaf('Title', title),
      leaf('Badge', 18 + 12 * digits),
    ]),
  ]));
}

void main() {
  final store = CounterStore(1000);
  final log = measure(buildHeader(store, title: 160), 200);
  printSizes(log);
  final header = log.firstWhere((entry) => entry.path == '/Screen/Body/Header');
  var total = 0;
  for (final entry in log.where((entry) => entry.path.startsWith('/Screen/Body/Header/'))) {
    total += entry.width;
  }
  print('cocuklar toplami: $total satir: ${header.width}');
}
```

```text
dayatilan: /Screen genislik=200 kisit=200..200
dayatilan: /Screen/Body genislik=168 kisit=168..168
esnetilen: /Screen/Body/Header genislik=168 kisit=0..168
secilen: /Screen/Body/Header/Title genislik=160 kisit=0..sonsuz
secilen: /Screen/Body/Header/Badge genislik=66 kisit=0..sonsuz
cocuklar toplami: 226 satir: 168
```

Odak düğüm `/Screen/Body/Header/Badge`'tır. Kaydı `secilen`, genişliği 66, kısıtı `0..sonsuz`'dur: ekran daralmış, başlık büyümüş ve satır 168 birime inmiştir, ama rozet yine kendi isteğini seçmiştir. Son satır ölçüme değil, toplama aittir: iki çocuk birlikte 226 birim ister, satırın aralığı ise 168 birimdir. Taşma buradadır; gerçek çerçevede satırın çocukları sınırsız kısıtla ölçüldüğü için bu toplam bir taşma uyarısına dönüşür. Kural bunu görmez: ne taşmanın uyarısını, ne ekranda görünen şeridini, ne de hangi çocuğun kesileceğini kaydeder.

**UY1.** Model yalnız genişliği ölçer. Rozetin yüksekliği, metnin satıra kırılması ve bir durum değişiminin kaç kez yeniden kurmaya yol açtığı modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Basamak sayısından genişlik hesabı dersin varsayımıdır; gerçek bir rozet genişliğini yazı tipinin ölçülerinden alır.

Bu tür taşmayı önlemenin yolu durum yönetiminde değil, düzendedir: rozeti esnek bir çocuğa sarmak ya da başlığı kırpılabilir bir alana koymak gerekir. Durum yaklaşımları isteği değiştirir; isteğin sığıp sığmadığını kısıt belirler.

#### Özet

- Bu dersin boyutlanması: seçilen. Odak düğüm `/Screen/Body/Header/Badge`'tır: satırın sınırsız kısıtında 66 birim isteyen rozet kendi isteğini seçmiştir.
- Sağlayıcı, olay tabanlı ve tepkisel yaklaşımlar durumun nasıl paylaşıldığını değiştirir; düzen aşamasına giren kısıt ve istek aynı kalır.
- Sayaç 9'dan 1000'e çıkınca rozetin genişliği 30'dan 66'ya büyüdü, boyutlanma her adımda `secilen` kaldı; aynı basamak sayısını veren sayılar aynı kaydı verdi.
- Satır esnek olmayan çocuklarına sınırsız kısıt verir; çocukların toplamı (226) satırın aralığını (168) aşabilir ve model bunu göstermez.
- Kural isteğe girmeyen değeri görmez: durumun genişliğe yansımayan farkları kaydı değiştirmez.

#### Sonraki Adım

Bu derste genişliği durumdan gelen bir değer değiştirdi, ama kısıt hep sınırsız ya da gevşekti. Sıradaki ders, `Gezinme ve Yönlendirme`, bir sayfanın genişliğini kimin verdiğini sorar: hangi rota açılırsa açılsın gezinme kabının sayfaya ne dayattığını ölçer.

### Gezinme ve Yönlendirme

Source: https://academia.sh/tr/kurslar/flutter/gezinme-ve-yonlendirme

Sayfa hangi rotayla açılırsa açılsın genişliğini gezinme kabı belirler. Ders üç rotayı ve üç derin bağlantıyı ölçer ve sayfanın isteğinin sonuca girmediğini gösterir.

Önceki ders, paylaşılan bir sayaçtan beslenen rozetin genişliğini ölçtü ve durumun isteği değiştirdiğini, kısıtı değiştirmediğini gösterdi. Bu ders ağacın bir üst katmanına çıkar: uygulamanın hangi ekranı gösterdiğine karar veren gezinmeye. Bir sayfa rota yığınına konur, yığından çıkar ya da dışarıdan gelen bir bağlantıyla doğrudan açılır. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, sayfayı saran gezinme kabında arar.

#### Rota, Yığın ve Derin Bağlantı

Uygulamanın her ekranı bir **rota (route)** ile adlandırılır. Rota bir ad ve o adın açacağı sayfayı üreten bir işlevdir. Açık rotalar bir **yığında** tutulur: yeni rota yığının tepesine konur, geri dönüş tepeyi çıkarır ve ekranda her zaman tepedeki rotanın sayfası görünür. Yönlendirme kitaplıkları rota tanımını ve yığın yönetimini bildirimsel hâle getirir; kitaplık adı bu derste yazılmaz, işleviyle anılır.

**Derin bağlantı (deep link)** uygulamayı dışarıdan, bir adresle açar. Adresin yolu bir rota adına çözülür ve çözülen rota yığına konur. Kullanıcı sayfaya ana ekrandan gelmemiştir, ama sayfanın ekranda durduğu yer değişmez.

Gezinmenin çerçeve tarafı, rota yığınını yöneten bir kap ve o kabın içine konan sayfalardır.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça bir rotanın yığına konmasını gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: rota yigini
Future<void> openOrders(BuildContext context) {
  return Navigator.of(context).push(
    MaterialPageRoute<void>(
      builder: (context) => const OrdersPage(),
    ),
  );
}

void closeOrders(BuildContext context) {
  Navigator.of(context).pop();
}
```

`push` yığının tepesine bir rota koyar, `pop` tepeyi çıkarır. Sayfayı kuran `builder` işlevidir; gezinme kabı onu çağırırken sayfaya bir kısıt verir. Bu dersin sorusu o kısıtın nereden geldiği ve sayfanın kendi isteğinin ona ne yaptığıdır.

#### Rotadan Sayfaya

Modelin gezinme kabı, ekran genişliğinde sabit bir kaptır: `fixed('Navigator', screen, page)`. Sayfa kabın tek çocuğudur ve çocuğa kabın genişliği sıkı kısıt olarak iner. Her rotanın sayfasının bir isteği vardır; istekler bir yardımcı dosyada durur. Yardımcı dosya ayrıca rota yığınını ve bir adresi rota adına çeviren işlevi taşır. Üçü de çıktıya hiçbir şey basmaz.

```dart
// routes.dart — rota yigini, sayfa istekleri ve baglanti cozumu
const pageRequests = {'Orders': 200, 'Profile': 480, 'Settings': 120};

const _linkRoutes = {'orders': 'Orders', 'profile': 'Profile', 'settings': 'Settings'};

class RouteStack {
  final List<String> _names;
  RouteStack(String first) : _names = [first];

  String get top => _names.last;
  int get depth => _names.length;

  void push(String name) => _names.add(name);

  void pop() {
    if (_names.length > 1) {
      _names.removeLast();
    }
  }
}

String routeFromLink(String link) {
  final segments = Uri.parse(link).pathSegments;
  return _linkRoutes[segments.first]!;
}
```

`buildRoute` yığının tepesindeki rotanın adını alır ve kabın içine o adın sayfasını koyar. İlk ölçüm `Orders` sayfasını açar.

```dart
// main.dart
import 'sizing.dart';
import 'routes.dart';

Part buildRoute(String name, {int screen = 360}) {
  final page = leaf(name, pageRequests[name]!);
  return fixed('Navigator', screen, page);
}

void main() {
  final stack = RouteStack('Orders');
  final log = measure(buildRoute(stack.top), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Navigator genislik=360 kisit=360..360
dayatilan: /Navigator/Orders genislik=360 kisit=360..360
dayatilan: 2
secilen: 0
kirpilan: 0
esnetilen: 0
ebeveyn: 2
```

`Orders` sayfası 200 birim ister, ama `/Navigator/Orders` kaydı **dayatılan**dır: genişliği 360, kısıtı `360..360`. Gezinme kabı sayfaya ekranın tamamını sıkı bir kısıt olarak verir; sayfanın isteği kayda girer girmez aralığın tek noktasına eşitlenir. Kap da ekranın verdiği kısıtla dayatılandır. Genişliği ne sayfa ne kap kendisi seçmiştir: ikisi de dışarıdan almıştır.

#### Rota Değiştikçe

Şimdi yığına üç rota koyalım ve her konuşta sayfanın kaydına bakalım. Sonra iki kez geri dönelim. Yığın derinliği düzyazıda anlam taşır; ölçümün girdisi yalnız tepedeki rotanın adıdır.

```dart
// main.dart
import 'sizing.dart';
import 'routes.dart';

Part buildRoute(String name, {int screen = 360}) {
  final page = leaf(name, pageRequests[name]!);
  return fixed('Navigator', screen, page);
}

void show(RouteStack stack) {
  final log = measure(buildRoute(stack.top), 360);
  final page = log.firstWhere((entry) => entry.path == '/Navigator/${stack.top}');
  print('${stack.top} derinlik=${stack.depth} istek=${pageRequests[stack.top]} ${page.sizing} genislik=${page.width}');
}

void main() {
  final stack = RouteStack('Orders');
  show(stack);
  stack.push('Profile');
  show(stack);
  stack.push('Settings');
  show(stack);
  stack.pop();
  show(stack);
  stack.pop();
  show(stack);
}
```

```text
Orders derinlik=1 istek=200 dayatilan genislik=360
Profile derinlik=2 istek=480 dayatilan genislik=360
Settings derinlik=3 istek=120 dayatilan genislik=360
Profile derinlik=2 istek=480 dayatilan genislik=360
Orders derinlik=1 istek=200 dayatilan genislik=360
```

Üç sayfanın istekleri 200, 480 ve 120'dir ve üçü de aynı genişlikte, 360'ta durur. Her satırda boyutlanma `dayatilan`dır. `Profile` ekrandan geniş bir sayfa ister; yine de kap onu 360'a indirir. `Settings` yalnız 120 ister; yine de 360'ı kaplar. Geri dönüşte `Profile` ve `Orders` aynı genişlikle geri gelir. Sayfa yığında ne kadar kalırsa kalsın, yığından çıkıp yeniden açılsa da genişliğini sayfa vermez; her açılışta kap yeniden verir.

**Ömür nerede bitiyor** sorusu bir değerin yaşam süresinin sonunu arar: sayfa yığından çıkınca sayfanın değerleri biter. Bu ders onu sormaz. Rota sayfasının ömrü ne olursa olsun, genişliğini kap dayatır; ölçüm ömrün başladığı ya da bittiği anı değil, sayfanın bir kez ekrandayken genişliğinin sahibini sınıflar.

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

Dışarıdan gelen bir bağlantı, sayfaya farklı bir yol açar ama kısıtı değiştirmez. Aşağıdaki ölçüm üç bağlantıyı rota adına çevirir, her birini iki ekran genişliğinde ölçer. Birinci genişlik 360, ikincisi 280'dir. Sayfaların istekleri yine 200, 480 ve 120'dir.

```dart
// main.dart
import 'sizing.dart';
import 'routes.dart';

Part buildRoute(String name, {int screen = 360}) {
  final page = leaf(name, pageRequests[name]!);
  return fixed('Navigator', screen, page);
}

void main() {
  final links = [
    'app://shop/orders/42',
    'app://shop/profile',
    'app://shop/settings/theme',
  ];
  for (final screen in [360, 280]) {
    for (final link in links) {
      final name = routeFromLink(link);
      final log = measure(buildRoute(name, screen: screen), screen);
      final page = log.firstWhere((entry) => entry.path == '/Navigator/$name');
      print('ekran=$screen $name istek=${pageRequests[name]} ${page.sizing} genislik=${page.width} kisit=${page.limit.bounds()}');
    }
  }
}
```

```text
ekran=360 Orders istek=200 dayatilan genislik=360 kisit=360..360
ekran=360 Profile istek=480 dayatilan genislik=360 kisit=360..360
ekran=360 Settings istek=120 dayatilan genislik=360 kisit=360..360
ekran=280 Orders istek=200 dayatilan genislik=280 kisit=280..280
ekran=280 Profile istek=480 dayatilan genislik=280 kisit=280..280
ekran=280 Settings istek=120 dayatilan genislik=280 kisit=280..280
```

Odak düğüm bağlantıyla açılan sayfadır: `/Navigator/Orders`, `/Navigator/Profile` ve `/Navigator/Settings`. Altı satırın altısında da boyutlanma `dayatilan`, genişlik ekran genişliğine, kısıt `ekran..ekran` aralığına eşittir. Sayfanın isteği kayda girmez; çıktıdaki `istek` değerini ders satırı ayrıca basar. 480 isteyen `Profile` 280'lik ekranda 280'e iner. Derin bağlantı sayfaya farklı bir kısıt vermez: kap hangi yoldan gelinirse gelinsin ekran genişliğinde sabit kaldığı için yığına nasıl girildiği genişliğe yansımaz.

**UY1.** Model yalnız genişliği ölçer. Sayfanın yüksekliği, geçiş animasyonu, kabın üst çubuğu ve geri dönüşte durumun korunması modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Gezinme kabını ekran genişliğinde sabit saymak dersin varsayımıdır; gerçek bir kap, yığındaki bir sayfaya kendi kısıtını farklı verebilir.

Bu sonuç sayfa yazarı için bir sözleşmedir: sayfa kendi içindeki bileşenler için bir genişlik bekleyebilir, ama kendi genişliğini kap verdiği için sayfa kendini ekrandan geniş ya da dar isteyerek ayarlayamaz. Sayfanın içinde isteğin anlam taşıdığı yer, sayfanın çocuklarıdır; onlar sayfanın verdiği kısıtla ölçülür.

#### Özet

- Bu dersin boyutlanması: dayatılan. Odak düğüm `/Navigator/Orders`'tır ve ekranın verdiği `360..360` kısıtıyla sayfanın isteği 200 olsa da 360 alır.
- Rota yığına konur ya da yığından çıkar; sayfanın genişliği her açılışta gezinme kabından gelir ve rotaya göre değişmez.
- Üç sayfa 200, 480 ve 120 istedi ve üçü de 360'ta dayatılan çıktı.
- Derin bağlantıyla açılan sayfa, yığına ana ekrandan konmuş sayfayla aynı kısıtı aldı; ekran 280'e inince genişlik 280'e indi.
- Kural sayfanın ömrünü değil, genişliğinin sahibini görür: sayfa kaç kez açılıp kapanırsa kapansın kayıt aynıdır.

#### Sonraki Adım

Bu derste sayfanın genişliğini kap dayattı ve sayfanın isteği sonuca girmedi. Sıradaki ders, `Ağ ve Dizileştirme`, isteğin sunucudan geldiği durumu ölçer: dışarıdan gelen bir genişlik aralığın dışına çıkınca kuralın o isteğe ne yaptığına bakar.

### Ağ ve Dizileştirme

Source: https://academia.sh/tr/kurslar/flutter/ag-ve-dizilestirme

Sunucudan, önbellekten ya da varsayılandan gelen bir genişlik isteği aralığın dışındaysa kırpılır. Ders bir afişin isteğini JSON'dan çözer ve kökenin boyutlanmayı değiştirmediğini ölçer.

Önceki ders, gezinme kabının sayfaya ekran genişliğinde sıkı bir kısıt verdiğini ve sayfanın isteğinin sonuca girmediğini gösterdi. Bu derste istek ağdan gelir. Bir afişin genişliğini sunucu bildirir; uygulama onu bir yanıttan okur, çözer ve ağaca verir. Yanıt gelmeden afişin genişliği bilinemez, çözülen değer ise uygulamanın kontrolünde değildir. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, sunucudan gelen bir isteğin üzerinden arar.

#### İstek Katmanı, JSON ve Model

Ağdan veri alan bir uygulama üç işi ayırır. **İstek katmanı** adresi çağırır ve yanıtı bir dizgi olarak döndürür. **JSON çözme** dizgiyi anahtar–değer yapısına çevirir. **Model dönüşümü** o yapıyı uygulamanın kendi türüne, örneğin bir afişin genişliğini taşıyan nesneye aktarır. İstek eşzamansızdır; Dart'ta bu iş bir `Future` ile ifade edilir ve `await` ile beklenir. Bu derste ağ yoktur: yanıt sabit bir dizgidir ve `Future` yalnız sırayı gösterir.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça bu üç işin çerçeve tarafıdır. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: istek istemcisi
abstract class ApiClient {
  Future<String> get(String path);
}

class BannerDto {
  final int width;
  const BannerDto(this.width);

  factory BannerDto.fromJson(Map<String, dynamic> json) =>
      BannerDto(json['width'] as int);
}

Future<BannerDto> loadBanner(ApiClient client) async {
  final body = await client.get('/banner');
  return BannerDto.fromJson(jsonDecode(body) as Map<String, dynamic>);
}
```

`ApiClient` yanıtı getirir, `fromJson` yapıyı türe çevirir ve `loadBanner` ikisini bağlar. Bu dersin sorusu bunun ardından gelir: çözülen genişlik ağaca girdiğinde kuralın o değere ne yaptığı.

#### Yanıttan Afişe

Yanıtlar bir yardımcı dosyada durur. `fetchBanner` kaynağın adını alır ve o kaynağın yanıtını, ya da kaynak çevrimdışıysa `null` döndürür. Dört kaynak vardır: `ag` sunucudan gelen güncel yanıt, `onbellek` daha önce saklanmış bir kopya, `varsayilan` uygulamanın içine gömülü değer ve `dar` küçük bir afiş. Yardımcı dosya çıktıya hiçbir şey basmaz.

```dart
// api.dart — sabit yanitlar; her kaynak bir Future doner, ag yoktur
Future<String?> fetchBanner(String source) async {
  switch (source) {
    case 'ag':
      return '{"banner": {"width": 1200}}';
    case 'onbellek':
      return '{"banner": {"width": 1000}}';
    case 'varsayilan':
      return '{"banner": {"width": 400}}';
    case 'dar':
      return '{"banner": {"width": 300}}';
    default:
      return null;
  }
}
```

`decodeBanner` yanıtı `jsonDecode` ile çözer ve afişin genişliğini ağaca verir. Ağaç ekranın içinde bir sütun, sütunun içinde başlık ile afiştir. Çözme `dart:convert` kitaplığındandır ve ağ ya da dosya erişimi gerektirmez.

```dart
// main.dart
import 'dart:convert';
import 'sizing.dart';
import 'api.dart';

Part decodeBanner(String json) {
  final data = jsonDecode(json) as Map<String, dynamic>;
  final banner = data['banner'] as Map<String, dynamic>;
  final width = banner['width'] as int;
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', width),
  ]));
}

Future<void> main() async {
  final json = await fetchBanner('ag');
  print('yanit: $json');
  final log = measure(decodeBanner(json!), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
yanit: {"banner": {"width": 1200}}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
kirpilan: /Screen/Body/Banner genislik=328 kisit=0..328
dayatilan: 2
secilen: 1
kirpilan: 1
esnetilen: 0
ebeveyn: 3
```

Sunucu 1200 birim genişliğinde bir afiş bildirmiştir. Sütun çocuklarına yatayda sınırlı bir aralık verir; kısıt `0..328`'dir. `/Screen/Body/Banner` bu aralığın dışında bir istek getirmiştir ve kural isteği üst sınıra çeker: kayıt **kırpılan**, genişlik 328'dir. Başlık 120 ister ve aralığın içinde kaldığı için **seçilen**dir. Sayım bunu gösterir: iki düğüm dayatılan, biri seçilen, biri kırpılan; ebeveyn üçtür.

Afişin genişliğini sunucu istemiştir, ama genişliği sunucu vermemiştir. İstek ağdan geldi, ağaç onu olduğu gibi taşıdı ve kısıt onu kırptı.

#### Kökeni Değiştirmek

Şimdi aynı afişi dört kaynaktan okuyalım ve her seferinde yalnız afişin kaydını basalım. İlk üç kaynak aralığın dışında bir istek getirir, dördüncüsü içinde kalan bir istek.

```dart
// main.dart
import 'dart:convert';
import 'sizing.dart';
import 'api.dart';

Part decodeBanner(String json) {
  final data = jsonDecode(json) as Map<String, dynamic>;
  final banner = data['banner'] as Map<String, dynamic>;
  final width = banner['width'] as int;
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', width),
  ]));
}

Future<void> main() async {
  for (final source in ['ag', 'onbellek', 'varsayilan', 'dar']) {
    final json = await fetchBanner(source);
    final log = measure(decodeBanner(json!), 360);
    final banner = log.firstWhere((entry) => entry.path == '/Screen/Body/Banner');
    print('kaynak=$source ${banner.sizing} genislik=${banner.width} kisit=${banner.limit.bounds()}');
  }
}
```

```text
kaynak=ag kirpilan genislik=328 kisit=0..328
kaynak=onbellek kirpilan genislik=328 kisit=0..328
kaynak=varsayilan kirpilan genislik=328 kisit=0..328
kaynak=dar secilen genislik=300 kisit=0..328
```

1200, 1000 ve 400 isteyen üç kaynakta da kayıt `kirpilan`, genişlik 328'dir. 300 isteyen `dar` kaynakta kayıt `secilen`, genişlik 300'dür. Boyutlanmayı kaynağın adı belirlemez; isteğin aralıkla ilişkisi belirler. **Bu okumanın yanıtı nereden geliyor** sorusu bellek, disk, ağ ya da varsayılan arasında bir köken seçer; bu ders onu sormaz. Köken ne olursa olsun, aralık dışı istek kırpılandır.

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

Köken yalnız ağ olsa da önbellek ya da varsayılan olsa da kural aynı kalır. Bunu uç bir durumda görelim: ağ çevrimdışıdır, `fetchBanner` `null` döndürür ve uygulama sırasıyla önbelleğe, sonra varsayılana düşer. Kullanılan yanıtı iki ekran genişliğinde ölçelim. Aralık 360'ta `0..328`, 480'de `0..448`'dir.

```dart
// main.dart
import 'dart:convert';
import 'sizing.dart';
import 'api.dart';

Part decodeBanner(String json) {
  final data = jsonDecode(json) as Map<String, dynamic>;
  final banner = data['banner'] as Map<String, dynamic>;
  final width = banner['width'] as int;
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', width),
  ]));
}

Future<void> main() async {
  final order = ['cevrimdisi', 'onbellek', 'varsayilan'];
  var used = '';
  String? json;
  for (final source in order) {
    json = await fetchBanner(source);
    if (json != null) {
      used = source;
      break;
    }
  }
  for (final screen in [360, 480]) {
    final log = measure(decodeBanner(json!), screen);
    final banner = log.firstWhere((entry) => entry.path == '/Screen/Body/Banner');
    print('ekran=$screen kaynak=$used ${banner.sizing} genislik=${banner.width} kisit=${banner.limit.bounds()}');
  }
}
```

```text
ekran=360 kaynak=onbellek kirpilan genislik=328 kisit=0..328
ekran=480 kaynak=onbellek kirpilan genislik=448 kisit=0..448
```

Odak düğüm `/Screen/Body/Banner`'dır. Ağ çevrimdışı olduğu için yanıt önbellekten gelmiştir ve istek 1000'dir. 360'lık ekranda kayıt `kirpilan`, genişlik 328'dir; 480'lik ekranda aralık genişlediği için genişlik 448'dir, ama kayıt yine `kirpilan`dır. İsteğin değeri 1000 olarak kaldı, kırpılan genişlik ise ekrana göre değişti: kırpılan düğümün genişliğini kısıtın üst sınırı belirler. Afişin 328'e ya da 448'e inmesi sunucunun, önbelleğin ya da varsayılanın kararı değildir.

**UY1.** Model yalnız genişliği ölçer. Yanıtın gecikmesi, hatalı bir yanıtın işlenmesi, afişin yüksekliği ve görselin kendisi modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Yanıtın her zaman geçerli bir `banner` anahtarı taşıdığı dersin varsayımıdır; gerçek bir istemci eksik ya da bozuk bir yanıtı ayrıca ele alır.

Pratik sonuç şudur: sunucudan gelen bir genişliği güvenilir saymak, o genişliğin ekrana sığdığını varsaymak anlamına gelmez. Sığma kararı kısıtın tarafındadır; istek katmanı ve model dönüşümü isteği olduğu gibi taşır. Sunucunun gönderdiği değer ne kadar büyük olursa olsun, düğüm kırpılanken ağaçtaki öteki kayıtlar değişmez.

#### Özet

- Bu dersin boyutlanması: kırpılan. Odak düğüm `/Screen/Body/Banner`'dır: sunucunun bildirdiği 1200, sütunun verdiği `0..328` aralığının dışında kalır ve 328'e çekilir.
- İstek katmanı yanıtı getirir, JSON çözme yapıyı çıkarır, model dönüşümü türe aktarır; üçü de isteği taşır, genişliği vermez.
- Ağdan, önbellekten ve varsayılandan gelen 1200, 1000 ve 400 isteklerinin üçü de kırpılan çıktı; 300 isteyen kaynak aralığın içinde kalıp seçilen oldu.
- Ağ çevrimdışıyken önbellekten gelen 1000, 360'lık ekranda 328'e, 480'lik ekranda 448'e kırpıldı; kırpılan genişliği kısıt belirler.
- Köken kayda girmez: kural yalnız isteği ve aralığı görür.

#### Sonraki Adım

Bu derste istek dışarıdan geldi ve kural onu aralığa çekti. Sıradaki ders, `Yerel Depolama`, isteğin telefonda saklanan bir tercihten geldiği durumu ölçer: kayıtlı bir tercih bir bileşeni esnek yaptığında ve tercih bayatladığında boyutlanmaya ne olduğuna bakar.

### Yerel Depolama

Source: https://academia.sh/tr/kurslar/flutter/yerel-depolama

Kaydedilmiş bir tercih bir kartı esnek yapabilir, bayatlamış bir tercih de aynı kaydı üretmeye devam eder. Ders tercihi bir anahtar–değer deposundan okur ve kartın genişliğinin sahibini ölçer.

Önceki ders, sunucudan gelen bir genişlik isteğinin aralığın dışında kaldığında kırpıldığını ve kökenin sonucu değiştirmediğini gösterdi. Bu derste istek telefondan gelir: kullanıcının daha önce seçip saklattığı bir tercih. Bir uygulama kapanıp açıldığında hatırlaması gereken her şey yerel depoda durur. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, depodan okunan bir tercihin etrafında arar.

#### Tercih, Anahtar–Değer ve Veritabanı

Yerel depolamanın üç biçimi vardır. **Tercih (preference)** küçük bir ayardır: tema, dil ya da bir ekranın yoğun ve geniş görünümü. **Anahtar–değer deposu** tercihleri bir ad altında saklayan en basit düzendir; yazma ve okuma bir ada bağlıdır. **Gömülü veritabanı** tablo ve sorgu gerektiren, daha büyük ve ilişkili veriyi saklar. İlk ikisi bu dersin modelidir; üçüncü yalnız anılır.

Depoya erişim eşzamansızdır, çünkü veri bellekte değil kalıcı ortamdadır. Bu derste depo bellek içi bir `Map`'tir ve erişim bir `Future` ile ifade edilir; gerçek bir dosya ya da veritabanı yoktur.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça tercihin okunup yazılmasının çerçeve tarafını gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: tercih deposu
Future<String> loadMode(SharedPreferencesAsync prefs) async {
  final saved = await prefs.getString('mode');
  return saved ?? 'compact';
}

Future<void> saveMode(SharedPreferencesAsync prefs, String mode) async {
  await prefs.setString('mode', mode);
}
```

`loadMode` kayıtlı tercihi okur, kayıt yoksa `compact` döndürür; `saveMode` yeni tercihi yazar. Okuma sonucu bir ağacın kuruluşunu değiştirir. Bu dersin sorusu tam olarak o kuruluşta, kartın isteğinin nasıl çıktığıdır.

#### Tercihten Karta

Tercih deposu bir yardımcı dosyada durur. Anahtar–değer eşlemini bellek içi bir `Map` tutar; okuma ve yazma birer `Future` döndürür. Yardımcı dosya çıktıya hiçbir şey basmaz.

```dart
// prefs.dart — bellek ici tercih deposu; okuma ve yazma Future doner
class PrefStore {
  final Map<String, String> _data;
  PrefStore([Map<String, String>? initial]) : _data = {...?initial};

  Future<String?> read(String key) async => _data[key];

  Future<void> write(String key, String value) async {
    _data[key] = value;
  }
}
```

`buildPrefs` tercihe göre kartı kurar. `full` tercihinde kartın istediği bir genişlik yoktur ve kart kendisine verilen yere yayılır: `fill('Card')`. `compact` tercihinde kart 200 birim ister. Ağaç ekranın içinde bir sütun, sütunun içinde başlık ile karttır.

```dart
// main.dart
import 'sizing.dart';
import 'prefs.dart';

Part buildPrefs(String mode) {
  final card = mode == 'full' ? fill('Card') : leaf('Card', 200);
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    card,
  ]));
}

Future<void> main() async {
  final store = PrefStore({'mode': 'full'});
  final mode = await store.read('mode') ?? 'compact';
  print('tercih=$mode');
  final log = measure(buildPrefs(mode), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
tercih=full
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
esnetilen: /Screen/Body/Card genislik=328 kisit=0..328
dayatilan: 2
secilen: 1
kirpilan: 0
esnetilen: 1
ebeveyn: 3
```

Tercih `full` okunmuştur ve kartın isteği yoktur. Sütun çocuklarına `0..328` aralığı verir; üst sınır sonlu olduğu için kart bu aralığa yayılır. `/Screen/Body/Card` kaydı **esnetilen**dir: genişlik 328'dir, ama bu genişliği kart istememiştir; aralığın üst sınırıdır. Sayımda bir esnetilen, bir seçilen ve iki dayatılan görünür; ebeveyn üçtür.

#### Tercih Değişince

Şimdi depoyu üç durumda okuyalım: tercih hiç yazılmamışken, `compact` yazılmışken ve `full` yazılmışken. Her durumda ağacı yeniden kurup yalnız kartın kaydını basalım.

```dart
// main.dart
import 'sizing.dart';
import 'prefs.dart';

Part buildPrefs(String mode) {
  final card = mode == 'full' ? fill('Card') : leaf('Card', 200);
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    card,
  ]));
}

Future<void> main() async {
  final store = PrefStore();
  for (final saved in [null, 'compact', 'full']) {
    if (saved != null) {
      await store.write('mode', saved);
    }
    final mode = await store.read('mode') ?? 'compact';
    final log = measure(buildPrefs(mode), 360);
    final card = log.firstWhere((entry) => entry.path == '/Screen/Body/Card');
    print('kayit=${saved ?? 'yok'} tercih=$mode ${card.sizing} genislik=${card.width} kisit=${card.limit.bounds()}');
  }
}
```

```text
kayit=yok tercih=compact secilen genislik=200 kisit=0..328
kayit=compact tercih=compact secilen genislik=200 kisit=0..328
kayit=full tercih=full esnetilen genislik=328 kisit=0..328
```

Kayıt yokken tercih `compact`'a düşer ve kart 200 ister; bu istek `0..328` aralığının içinde kaldığı için kayıt **seçilen**dir. `compact` yazılınca sonuç değişmez. `full` yazılınca kart esnetilene döner ve 328'i kaplar. Boyutlanmayı tercih adının kendisi değil, tercihin kartın isteğini var ya da yok yapması belirler. İstek kalktığında genişliğin sahibi kart olmaktan çıkar, kısıta geçer.

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

Depodaki bir değer ekranın okuduğundan farklı olabilir: kullanıcı tercihi başka bir yerde değiştirmiştir ve ekran henüz yeniden kurulmamıştır. Aşağıdaki ölçüm ekranı `full` tercihiyle kurar, sonra depoya `compact` yazar. Ekranın elindeki ağaç değişmez; ağaç ancak tercih yeniden okunup yeniden kurulduğunda değişir.

```dart
// main.dart
import 'sizing.dart';
import 'prefs.dart';

Part buildPrefs(String mode) {
  final card = mode == 'full' ? fill('Card') : leaf('Card', 200);
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    card,
  ]));
}

void show(String label, String shown, String stored, Part tree) {
  final log = measure(tree, 360);
  final card = log.firstWhere((entry) => entry.path == '/Screen/Body/Card');
  print('$label ekranda=$shown depoda=$stored ${card.sizing} genislik=${card.width}');
}

Future<void> main() async {
  final store = PrefStore({'mode': 'full'});
  final shown = (await store.read('mode'))!;
  final onScreen = buildPrefs(shown);
  show('okuma', shown, shown, onScreen);

  await store.write('mode', 'compact');
  final stored = (await store.read('mode'))!;
  show('bayat', shown, stored, onScreen);

  final fresh = buildPrefs(stored);
  show('yenilenmis', stored, stored, fresh);
}
```

```text
okuma ekranda=full depoda=full esnetilen genislik=328
bayat ekranda=full depoda=compact esnetilen genislik=328
yenilenmis ekranda=compact depoda=compact secilen genislik=200
```

Odak düğüm `/Screen/Body/Card`'dır. İlk satırda kart esnetilen ve 328'dir. İkinci satırda depo `compact` olmuştur, ama ekranın elindeki ağaç aynıdır ve kayıt birebir aynı çıkar: `esnetilen`, 328. Bayat bir tercih de esnetilen bir genişlik doğurur. Kural depoyu görmez; yalnız ölçtüğü ağacı görür. Üçüncü satırda ağaç yeniden kurulmuştur ve kart `secilen` olur, genişlik 200'dür.

**Bu ekran modelle uyuşuyor mu** sorusu ekranın verinin güncel hâliyle örtüşüp örtüşmediğini arar. Bu ders onu sormaz: bayat ağaç da kendi içinde tutarlı bir kayıt üretir ve kural o kaydı sınıflar.

**UY1.** Model yalnız genişliği ölçer. Yazmanın kalıcı olup olmadığı, iki eşzamanlı yazmanın sırası, depodaki verinin büyüklüğü ve kartın içeriği modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Tercihin `full` ya da `compact` olarak iki değerden birini taşıması dersin varsayımıdır; gerçek bir depo başka değerler ve sürüm farkları da taşıyabilir.

Pratik bir sonuç: tercihin ekrandaki karşılığı depodaki kayıtla birebir olmak zorunda değildir. Genişliğin sahibini görmek için depoya değil, o anda ekrandaki ağaca bakmak gerekir.

#### Özet

- Bu dersin boyutlanması: esnetilen. Odak düğüm `/Screen/Body/Card`'dır: `full` tercihinde kartın isteği yoktur ve sütunun verdiği `0..328` aralığının üst sınırını, 328'i alır.
- Tercih, anahtar–değer deposunda küçük bir ayardır; gömülü veritabanı daha büyük, ilişkili veriyi saklar. Erişim eşzamansızdır.
- Kayıt yokken ve `compact` yazılıyken kart 200 istedi ve seçilen çıktı; `full` yazılınca istek kalktı ve kart esnetilen oldu.
- Ekran `full` ile kurulmuş, depo sonra `compact` olmuşken ölçüm aynı kaydı verdi: `esnetilen`, 328. Yeniden kurulan ağaçta kart 200 ile seçilen oldu.
- Kural depoyu değil, ölçtüğü ağacı sınıflar; bayat tercih bayat olduğunu kayda yazmaz.

#### Sonraki Adım

Bu derste isteği kaldıran şey kaydedilmiş bir tercihti. Sıradaki ders, `Varlıklar ve Yazı Tipleri`, isteği büyüten bir girdiyi ölçer: yazı tipi ölçeği bir düğmenin isteğini artırınca, düğmenin aralığa sığıp sığmadığına bakar.

### Varlıklar ve Yazı Tipleri

Source: https://academia.sh/tr/kurslar/flutter/varliklar-ve-yazi-tipleri

Yazı tipi ölçeği bir düğmenin isteğini büyütür ve büyüyen istek aralığı aşınca düğme kırpılır. Ders ölçeği 1, 2 ve 3 verir ve aralığı genişletmenin sonucu nasıl değiştirdiğini ölçer.

Önceki ders, kayıtlı bir tercihin bir kartın isteğini kaldırdığını ve kartı esnetilen yaptığını gösterdi. Bu ders tersini yapan bir girdiyi ölçer: isteği büyüten bir girdiyi. Kullanıcı sistem ayarlarından yazı boyutunu büyüttüğünde uygulamanın her yazısı ve yazıyı taşıyan her düğme büyür. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, ölçekle büyüyen bir düğmenin üzerinden arar.

#### Varlıklar, Çözünürlük Çeşitleri ve Yazı Tipleri

Bu derste **varlık (asset)** uygulamayla birlikte paketlenen bir dosyadır: bir görsel, bir yazı tipi dosyası ya da bir ses. Veri modelindeki varlık (entity) ile ilgisi yoktur. Uygulama yapılandırmasında varlıkların hangi dosyalar olduğu bildirilir; bildirilmeyen dosya pakete girmez.

Görseller ekranın yoğunluğuna göre çözünürlük çeşitleriyle gelir: aynı görselin 1.0x, 2.0x gibi büyütülmüş dosyaları yan yana durur ve çerçeve ekrana uygun olanı seçer. Yazı tipleri de varlıktır; bir aile adı altında bildirilir.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça yapılandırmayı gösterir. Paket adı kurgusaldır ve parça ölçülmez.

```yaml
# Platform kodu: varlik bildirimi
name: demo_shop
flutter:
  assets:
    - assets/images/logo.png
  fonts:
    - family: Body
      fonts:
        - asset: assets/fonts/body-regular.ttf
```

`assets` görsel dosyalarını, `fonts` yazı tipi ailesini ve dosyasını bildirir. Çözünürlük çeşitleri `assets/images/2.0x/logo.png` gibi bir alt dizinde yan yana durur ve ayrıca listelenmez. Bildirimden sonra kod, yazı tipinin ölçeğini kullanıcının ayarından okur.

```dart
// Platform kodu: yazi tipi olcegi
double labelSize(BuildContext context) {
  final scaler = MediaQuery.textScalerOf(context);
  return scaler.scale(14);
}
```

`textScalerOf` kullanıcının ayarladığı ölçeği verir ve `scale` temel boyutu o ölçekle çarpar. Yazı büyüyünce yazıyı taşıyan düğmenin isteği de büyür. Bu dersin sorusu o büyümenin aralıkla buluştuğu yerdir.

#### Ölçekten İsteğe

Modelin düğmesi bir etiketin genişliğini taşır ve etraftaki çerçeve payını ekler. Etiketin genişliği ölçekle çarpılan bir tamsayıdır: temel genişlik 160 birimse ölçek 1'de etiket 160, ölçek 2'de 320 birim genişliğindedir. Çerçeve payı sabit 24 birimdir. İsteği hesaplayan işlev ve temel genişlikler bir yardımcı dosyada durur; yardımcı dosya çıktıya hiçbir şey basmaz.

```dart
// fonts.dart — etiket genisligi ve olcege bagli istek
const labelBase = {'Kaydet': 160, 'Tamam': 80};

int frameWidth(int base, int scale) => base * scale + 24;
```

`buildForm` ölçeği alır ve düğmeyi bu istekle kurar. Ağaç ekranın içinde bir sütun, sütunun içinde başlık ile düğmedir. İlk ölçüm ölçek 1'dedir.

```dart
// main.dart
import 'sizing.dart';
import 'fonts.dart';

Part buildForm(String label, int scale) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Button', frameWidth(labelBase[label]!, scale)),
  ]));
}

void main() {
  final log = measure(buildForm('Kaydet', 1), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Button genislik=184 kisit=0..328
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 2
```

Ölçek 1'de düğme 184 ister; aralık `0..328` olduğu için istek aralığın içinde kalır ve `/Screen/Body/Button` **seçilen**dir. Başlık da seçilendir. Sayım iki dayatılan, iki seçilen gösterir; kırpılan ve esnetilen yoktur, ebeveyn ikidir.

#### Ölçek Büyüdükçe

Şimdi ölçeği 1'den 3'e artıralım ve iki etiketi karşılaştıralım. `Kaydet` uzun bir etikettir; `Tamam` kısadır.

```dart
// main.dart
import 'sizing.dart';
import 'fonts.dart';

Part buildForm(String label, int scale) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Button', frameWidth(labelBase[label]!, scale)),
  ]));
}

void main() {
  for (final label in ['Kaydet', 'Tamam']) {
    for (final scale in [1, 2, 3]) {
      final log = measure(buildForm(label, scale), 360);
      final button = log.firstWhere((entry) => entry.path == '/Screen/Body/Button');
      print('$label olcek=$scale istek=${frameWidth(labelBase[label]!, scale)} ${button.sizing} genislik=${button.width}');
    }
  }
}
```

```text
Kaydet olcek=1 istek=184 secilen genislik=184
Kaydet olcek=2 istek=344 kirpilan genislik=328
Kaydet olcek=3 istek=504 kirpilan genislik=328
Tamam olcek=1 istek=104 secilen genislik=104
Tamam olcek=2 istek=184 secilen genislik=184
Tamam olcek=3 istek=264 secilen genislik=264
```

`Kaydet` ölçek 1'de 184 ister ve seçilen kalır. Ölçek 2'de istek 344'e çıkar, 328'i aşar ve kayıt `kirpilan` olur; genişlik 328'dir. Ölçek 3'te istek 504'tür ve kayıt yine kırpılan, genişlik yine 328'dir. `Tamam` üç ölçekte de 104, 184 ve 264 ister ve üçünde de seçilen kalır. Ölçek tek başına boyutlanmayı belirlemez: ölçek isteği büyütür, boyutlanmayı isteğin aralıkla ilişkisi belirler.

**Bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi** sorusu bir işlemin duyarlılığını arar. Bu ders onu sormaz. Yazı tipi ölçeği bir girdidir ve isteği büyütür; boyutlanma sonucun yinelemeye dayanıklılığını değil, büyüyen isteği aralığa kimin sığdırdığını sınıflar.

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

Ölçek 2'de `Kaydet` kırpılandı. Aralık genişlerse aynı istek sığabilir mi? Aynı ağacı iki ekran genişliğinde ölçelim: 360'ta aralık `0..328`, 480'de `0..448`'dir. İstek 344'te sabit kalır.

```dart
// main.dart
import 'sizing.dart';
import 'fonts.dart';

Part buildForm(String label, int scale) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Button', frameWidth(labelBase[label]!, scale)),
  ]));
}

void main() {
  for (final screen in [360, 480]) {
    final log = measure(buildForm('Kaydet', 2), screen);
    final button = log.firstWhere((entry) => entry.path == '/Screen/Body/Button');
    print('ekran=$screen olcek=2 istek=344 ${button.sizing} genislik=${button.width} kisit=${button.limit.bounds()}');
  }
}
```

```text
ekran=360 olcek=2 istek=344 kirpilan genislik=328 kisit=0..328
ekran=480 olcek=2 istek=344 secilen genislik=344 kisit=0..448
```

Odak düğüm, ölçek 2'deki `/Screen/Body/Button`'dır. 360'lık ekranda istek 344, aralık `0..328` olduğundan kayıt `kirpilan`, genişlik 328'dir. 480'lik ekranda aralık `0..448` olur, istek içinde kalır ve kayıt `secilen`, genişlik 344'tür. Ekran ya da ölçek değiştiğinde sonuç değişebilir; sonucu değiştiren yazı tipi ölçeğinin kendisi değil, büyüyen isteğin aralığa sığıp sığmadığıdır.

**UY1.** Model yalnız genişliği ölçer. Etiketin satıra kırılması, taşan metnin görüntüsü, düğmenin yüksekliği ve yazı tipinin gerçek harf ölçüleri modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. İsteğin ölçekle doğrusal büyümesi ve 24 birimlik sabit çerçeve payı dersin varsayımlarıdır; gerçek bir etiketin genişliği yazı tipinin ölçülerinden gelir.

Sonuç şudur: büyük yazı ayarı olan bir kullanıcıda düğme kırpılıyorsa çözüm ölçeği bastırmak değildir. Seçenekler iki yöndedir: etiketi kısaltarak isteği küçültmek ya da düğmeyi aralığı geniş bir yere taşıyarak aralığı genişletmek.

#### Özet

- Bu dersin boyutlanması: kırpılan. Odak düğüm ölçek 2'deki `/Screen/Body/Button`'dır: 344 istek, `0..328` aralığını aştığı için 328'e çekilir.
- Varlık bildirimi paketlenecek dosyaları söyler; yazı tipi ölçeği ise kullanıcının ayarından okunur ve etiketin genişliğini büyütür.
- `Kaydet` ölçek 1'de 184 ile seçilen, ölçek 2 ve 3'te 344 ve 504 ile kırpılan çıktı; `Tamam` üç ölçekte de seçilen kaldı.
- Ekran 480'e çıkınca aralık `0..448` oldu ve 344 isteği yeniden seçilen çıktı; genişliği ölçek değil, istekle aralığın ilişkisi belirledi.
- Kural ölçeğin kendisini değil, büyüyen isteğin sığıp sığmadığını görür.

#### Sonraki Adım

Bu derste isteği ölçek büyüttü ve sonuç iki ölçüm arasında değişti. Sıradaki ders, `Animasyonlar`, isteğin zamanla küçük adımlarla büyüdüğü durumu ölçer: her karede ağaç yeniden ölçülür ve her karede genişliğin sahibine bakılır.

### Animasyonlar

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

Bir animasyon düzeni her karede yeniden koşturur, ama her karede genişliğin sahibi değişmeyebilir. Ders bir panelin genişliğini 80'den 240'a beş karede ilerletir ve her karenin boyutlanmasını ölçer.

Önceki ders, yazı tipi ölçeğinin bir düğmenin isteğini büyüttüğünü ve büyüyen isteğin aralığa sığıp sığmadığını ölçtü. Bu derste istek zamanla değişir: bir panelin genişliği her karede biraz daha büyür. Animasyon düzeni tek seferlik bir hesap olmaktan çıkarır ve her kare için yeniden koşturur. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, kareler boyunca büyüyen bir panelin üzerinden arar.

#### Denetleyici, Eğri ve Ara Değer

Bir animasyonun üç parçası vardır. **Animasyon denetleyicisi (controller)** ilerlemeyi 0'dan 1'e taşır; kimin ne zaman başlattığı ve durdurduğu ondadır. **Eğri (curve)** ilerlemeyi biçimlendirir: doğrusal bir eğri eşit adımlarla, ivmelenen bir eğri önce yavaş sonra hızlı ilerler. **Ara değer (tween)** ilerlemeyi bir başlangıçla bir bitiş arasındaki değere çevirir. Bu derste ara değer 80'den 240'a genişliktir.

**Örtük (implicit)** animasyonda bileşen yeni değeri alır ve geçişi kendisi kurar; denetleyiciyi çerçeve yönetir. **Açık (explicit)** animasyonda denetleyici kodda tutulur ve ilerleme elle sürülür.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki parça açık animasyonun çerçeve tarafını gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```dart
// Platform kodu: acik animasyon
class _PanelState extends State<PanelView> with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(vsync: this);
  late final Animation<double> _width = Tween<double>(begin: 80, end: 240)
      .animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));

  void open() => _controller.animateTo(1.0, duration: widget.span);
}
```

Denetleyici ilerlemeyi sürer, `CurvedAnimation` eğriyi uygular ve `Tween` genişliği üretir. Her kare yeni bir genişlik demektir ve yeni genişlik düzenin yeniden koşması demektir. Bu dersin sorusu o koşunun sonucunda genişliğin sahibidir.

#### Kareden Ağaca

Modelde zaman yoktur; yalnız kare sırası vardır. Beş kare, 0'dan 4'e numaralanır ve ara değer her karede bir tamsayı üretir. Yardımcı dosya iki eğri ve bir kare sayacı taşır. Doğrusal eğri eşit adım atar; ivmelenen eğri adımı karesiyle ölçekler. Sayaç ders-yereldir ve kareleri sayar. Yardımcı dosya çıktıya hiçbir şey basmaz.

```dart
// tween.dart — ara degerler ve kare sayaci
int linearWidth(int from, int to, int step, int steps) =>
    from + (to - from) * step ~/ steps;

int easedWidth(int from, int to, int step, int steps) =>
    from + (to - from) * step * step ~/ (steps * steps);

class FrameCounter {
  int frames = 0;
  void tick() => frames++;
}
```

`buildPanel` panelin genişliğini alır ve ağacı kurar: ekranın içinde bir sütun, sütunun içinde başlık ile panel. İlk ölçüm animasyonun başlangıç karesidir.

```dart
// main.dart
import 'sizing.dart';
import 'tween.dart';

Part buildPanel(int width) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Panel', width),
  ]));
}

void main() {
  final log = measure(buildPanel(linearWidth(80, 240, 0, 4)), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Panel genislik=80 kisit=0..328
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 2
```

Başlangıç karesinde panel 80 ister, aralık `0..328`'dir. `/Screen/Body/Panel` **seçilen**dir ve genişliği 80'dir. Başlık da seçilendir. Animasyon henüz hiçbir şeyi değiştirmemiştir; kare sıfırdaki ağaç, animasyon olmasaydı da aynı olurdu.

#### Beş Kare

Şimdi iki eğriyle beş kareyi sırayla ölçelim. Her kare ayrı bir `measure` çağrısıdır ve her kareden yalnız panelin kaydı basılır.

```dart
// main.dart
import 'sizing.dart';
import 'tween.dart';

Part buildPanel(int width) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Panel', width),
  ]));
}

void main() {
  final curves = {'dogrusal': linearWidth, 'ivmelenen': easedWidth};
  curves.forEach((name, curve) {
    for (var step = 0; step <= 4; step++) {
      final width = curve(80, 240, step, 4);
      final log = measure(buildPanel(width), 360);
      final panel = log.firstWhere((entry) => entry.path == '/Screen/Body/Panel');
      print('$name kare=$step istek=$width ${panel.sizing} genislik=${panel.width}');
    }
  });
}
```

```text
dogrusal kare=0 istek=80 secilen genislik=80
dogrusal kare=1 istek=120 secilen genislik=120
dogrusal kare=2 istek=160 secilen genislik=160
dogrusal kare=3 istek=200 secilen genislik=200
dogrusal kare=4 istek=240 secilen genislik=240
ivmelenen kare=0 istek=80 secilen genislik=80
ivmelenen kare=1 istek=90 secilen genislik=90
ivmelenen kare=2 istek=120 secilen genislik=120
ivmelenen kare=3 istek=170 secilen genislik=170
ivmelenen kare=4 istek=240 secilen genislik=240
```

Doğrusal eğri 80, 120, 160, 200 ve 240 üretir; ivmelenen eğri 80, 90, 120, 170 ve 240. İki eğri arada farklı genişlikler verir ve ikisinin de sonu 240'tır. Ama on satırın on tanesinde de boyutlanma `secilen`dir ve genişlik isteğe eşittir. Eğri isteğin değerini biçimlendirir; isteğin aralıkla ilişkisini değiştirmez.

**Kaç kez koştu** sorusu bir işin kaç kez çalıştığını sayar. Bir animasyon düzeni her karede yeniden koşturur ve koşum sayısı gerçekten artar; bu ders onu bir ölçü olarak almaz. Her karede ölçülen, o karedeki genişliğin sahibidir.

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

Kareleri sayaçla sayalım ve her karenin boyutlanmasını bir toplama işleyelim. Beş karenin hepsi aynı boyutlanmayı veriyorsa toplam tek bir satıra iner. Odak olarak son karedeki panelin satırını da basalım.

```dart
// main.dart
import 'sizing.dart';
import 'tween.dart';

Part buildPanel(int width) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Panel', width),
  ]));
}

void main() {
  final counter = FrameCounter();
  final tally = <String, int>{};
  String last = '';
  for (var step = 0; step <= 4; step++) {
    counter.tick();
    final log = measure(buildPanel(linearWidth(80, 240, step, 4)), 360);
    final panel = log.firstWhere((entry) => entry.path == '/Screen/Body/Panel');
    tally[panel.sizing] = (tally[panel.sizing] ?? 0) + 1;
    last = '${panel.path} ${panel.sizing} genislik=${panel.width} kisit=${panel.limit.bounds()}';
  }
  print('kareler=${counter.frames}');
  tally.forEach((sizing, count) => print('$sizing=$count'));
  print('son kare: $last');
}
```

```text
kareler=5
secilen=5
son kare: /Screen/Body/Panel secilen genislik=240 kisit=0..328
```

Sayaç 5 basar ve toplam tek bir satırdır: beş karenin beşi de `secilen`. Odak düğüm son karedeki `/Screen/Body/Panel`'dır. Genişliği 240, kısıtı `0..328`'dir ve panel kendi isteğini seçmiştir. Panel 80'den 240'a büyürken ağaç beş kez yeniden ölçüldü; ama genişliği her karede panel belirledi. Büyüyen bir genişlik, genişliğin sahibini değiştirmez.

**UY1.** Model yalnız genişliği ölçer. Karenin süresi, eğrinin gerçek zamandaki ilerleyişi, animasyonun başlatılıp iptal edilmesi ve panelin yüksekliği modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Beş karelik tamsayı ilerlemesi dersin varsayımıdır; gerçek bir animasyon kesirli değerler üretir.

Sonucun bir sınırı vardır ve bu derste ölçülmemiştir: kuralın tanımı gereği istek 328'i aşan bir kareden sonra panel kırpılan olur. Bitiş değerinin ekrana sığması ara değerin değil, aralığı bilen tarafın işidir.

#### Özet

- Bu dersin boyutlanması: seçilen. Odak düğüm son karedeki `/Screen/Body/Panel`'dır: 240 istek, `0..328` aralığının içinde kalır ve panel kendi isteğini seçer.
- Denetleyici ilerlemeyi sürer, eğri ilerlemeyi biçimlendirir ve ara değer genişliği üretir; örtük animasyonda denetleyiciyi çerçeve yönetir.
- Doğrusal ve ivmelenen eğriler ara karelerde farklı genişlikler (kare 2'de 160 ve 120) verdi; on ölçümün onunda da boyutlanma seçilen çıktı.
- Beş kare ayrı ayrı ölçüldü, sayaç 5 bastı ve beşi de seçilen oldu.
- Kural koşum sayısını değil, o karedeki genişliğin sahibini görür.

#### Sonraki Adım

Bu derste istek her karede değişti ve sahibi değişmedi. Sıradaki ders, `Platform Kanalları`, isteğin çerçevenin dışından, platformun kendi tarafından geldiği durumu ölçer: gömülü bir platform görünümünün istediği genişlik kaç ileti gelirse gelsin, yuvanın verdiği genişliğe nasıl çekildiğine bakar.

### Platform Kanalları

Source: https://academia.sh/tr/kurslar/flutter/platform-kanallari

Platform tarafından kaç ileti gelirse gelsin gömülü görünümün genişliğini çerçeve dayatır. Ders üç farklı istekle ve yinelenen iletilerle bunu ölçer.

Önceki ders, çerçevenin animasyon karelerini ve her karede seçilen paneli ölçtü. Bu ders ağacın dışına, yerel platforma uzanır: uygulama bazen platformun kendi ekran parçasını gömmek ya da platformdan bir bilgi almak ister. Bu konuşma bir kanaldan geçer. Kanaldan gelen bir ileti gömülü görünümün genişliğini de önerebilir. Kursun sorusu yine aynıdır: **bu bileşenin genişliğini kim belirliyor?** Ders yanıtı, görünümü saran yuvada arar.

#### Kanal, İleti ve Gömülü Görünüm

**Platform kanalı (platform channel)** uygulamanın Dart tarafı ile yerel platform kodu arasındaki adlandırılmış bir konuşma hattıdır. Dart tarafı bir yöntem adı ve bağımsız değişkenlerle çağrı gönderir, platform tarafı çağrıyı karşılar ve yanıt döner. Platform da kendiliğinden ileti gönderebilir. Kanalın iki ucu iki ayrı dildedir; veriler her iki yönde de basit türlerle, sayı, dize, liste ve eşlemle taşınır.

**Gömülü görünüm (platform view)** platformun kendi ekran parçasının Dart ağacına konmasıdır. Ağaçta tek bir düğümdür, ama içeriğini yerel platform çizer. Bu düğümün genişliği de ağacın kurallarına uyar: çerçeve onu bir yuvaya koyar ve yuva ona bir kısıt verir.

#### Çekirdek

Her ders aynı çekirdeği kullanır. `measure` ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa **dayatılan**, isteğin aralık içinde kaldığı durumda **seçilen**, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa **kırpılan**, istek yok ve üst sınır sonluysa **esnetilen**. Çıktıda dört ad ASCII yazılır: `dayatilan`, `secilen`, `kirpilan`, `esnetilen`. `ebeveyn:` satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Çerçeve Tarafı

Aşağıdaki iki parça kanalın iki ucunu gösterir. Parçalar ölçülmez; model çerçevenin ya da platformun kodunu çalıştırmaz.

```dart
// Platform kodu: Dart ucu
const channel = MethodChannel('app/embed');

Future<int?> askWidth() {
  return channel.invokeMethod<int>('preferredWidth');
}
```

```swift
// Platform kodu: yerel uc
let channel = FlutterMethodChannel(
  name: "app/embed",
  binaryMessenger: messenger
)
channel.setMethodCallHandler { call, result in
  if call.method == "preferredWidth" {
    result(180)
  } else {
    result(FlutterMethodNotImplemented)
  }
}
```

Dart ucu `preferredWidth` adlı yöntemi çağırır, yerel uç 180 döner. Bu 180 bir **istek**tir: platform görünümünün kaç birim genişlik istediğini söyler. Dersin sorusu, bu isteğin görünümün genişliğine ne yaptığıdır.

#### Kanaldan Yuvaya

Modelde kanal, iletileri bir liste olarak getiren bir işlevdir. Her ileti bir eşlemdir: bir `id` ve platformun istediği bir `width`. Yardımcı dosya iki grup ileti taşır, `ilk` ve `tekrar`, ve bir iletiden genişliği okuyan işlevi verir. Dosya çıktıya hiçbir şey basmaz.

```dart
// channel.dart — kanaldan gelen iletiler ve istek okuma
const _batches = <String, List<Map<String, Object>>>{
  'ilk': [
    {'id': 1, 'width': 180},
    {'id': 2, 'width': 400},
    {'id': 3, 'width': 96},
  ],
  'tekrar': [
    {'id': 1, 'width': 180},
    {'id': 1, 'width': 180},
    {'id': 2, 'width': 400},
    {'id': 3, 'width': 96},
    {'id': 3, 'width': 96},
  ],
};

Future<List<Map<String, Object>>> receiveMessages(String batch) async {
  return _batches[batch]!;
}

int widthOf(Map<String, Object> message) => message['width'] as int;
```

Ekranın ağacı bir iç boşluk, bir sütun, bir başlık ve gömülü görünümün yuvasıdır. Yuva sabit 240 genişliktedir: `fixed('Embed', 240, ...)`. Görünüm yuvanın tek çocuğudur ve iletiden gelen genişliği ister. İlk ölçüm `ilk` grubunun ilk iletisini kullanır.

```dart
// main.dart
import 'sizing.dart';
import 'channel.dart';

Part buildScreen(int requested) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    fixed('Embed', 240, leaf('PlatformView', requested)),
  ]));
}

Future<void> main() async {
  final messages = await receiveMessages('ilk');
  final log = measure(buildScreen(widthOf(messages.first)), 360);
  printSizes(log);
  countSizings(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Embed genislik=240 kisit=0..328
dayatilan: /Screen/Body/Embed/PlatformView genislik=240 kisit=240..240
dayatilan: 3
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 3
```

İlk ileti 180 ister, ama `/Screen/Body/Embed/PlatformView` kaydı **dayatılan**dır: genişliği 240, kısıtı `240..240`. Yuva görünüme sıkı bir kısıt verir; iletideki 180 yuvanın sıkı kısıtına çarpar ve genişlik 240'ta kalır. Yuvanın kendisi 328 genişliğindeki bir sütunun içinde 240 isteyen, aralığın içinde kalan bir düğümdür; onu seçen yuvadır, görünüm değil. Kanalın söylediği, görünümün boyutlanmasında bir şey değiştirmez.

#### Kanal Ne Derse Desin

Şimdi `ilk` grubundaki üç iletinin her birini ayrı ayrı ölçelim: 180, 400 ve 96. Üçü de aynı ağaca farklı bir istek verir.

```dart
// main.dart
import 'sizing.dart';
import 'channel.dart';

Part buildScreen(int requested) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    fixed('Embed', 240, leaf('PlatformView', requested)),
  ]));
}

Future<void> main() async {
  final messages = await receiveMessages('ilk');
  for (final message in messages) {
    final log = measure(buildScreen(widthOf(message)), 360);
    final view = log.firstWhere((entry) => entry.path == '/Screen/Body/Embed/PlatformView');
    print('ileti=${message['id']} istek=${widthOf(message)} ${view.sizing} genislik=${view.width} kisit=${view.limit.bounds()}');
  }
}
```

```text
ileti=1 istek=180 dayatilan genislik=240 kisit=240..240
ileti=2 istek=400 dayatilan genislik=240 kisit=240..240
ileti=3 istek=96 dayatilan genislik=240 kisit=240..240
```

Üç istek 180, 400 ve 96'dır ve üç satırda da boyutlanma `dayatilan`, genişlik 240'tır. 400 isteyen ileti yuvanın içinde görünümü büyütmez, 96 isteyen küçültmez. İleti platformdan geldiği için bir değer taşır, ama ağaçta bu değer yalnız bir istektir; sıkı kısıt altında istek, genişliğe etki eden bir şey değildir.

Bu, kanaldaki **gönderilen olay alıcıya kaç kopya ulaştı** sorusundan ayrı bir sorudur. O soru bir olayın kanaldan kaç kez geçtiğini sayar; bu ders bir iletinin sonucunda görünümün genişliğinin kimde olduğunu sorar. Kaç ileti gelirse gelsin, gömülü görünümün genişliğini çerçeve dayatır.

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

Kanal bir iletiyi birden çok kez de gönderebilir. `tekrar` grubunda beş ileti vardır: `1` iki kez, `2` bir kez, `3` iki kez. Aşağıdaki ölçüm beşini de ağaca verir ve toplam tabloyu tek bir yerde birleştirir. Ardından ekran genişliği 200'e iner.

```dart
// main.dart
import 'sizing.dart';
import 'channel.dart';

Part buildScreen(int requested) {
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    fixed('Embed', 240, leaf('PlatformView', requested)),
  ]));
}

Future<void> main() async {
  final messages = await receiveMessages('tekrar');
  final all = <Sizing>[];
  for (final message in messages) {
    all.addAll(measure(buildScreen(widthOf(message)), 360));
  }
  print('ileti sayisi=${messages.length}');
  countSizings(all);
  final narrow = measure(buildScreen(widthOf(messages.first)), 200);
  final view = narrow.firstWhere((entry) => entry.path == '/Screen/Body/Embed/PlatformView');
  print('ekran=200 ${view.sizing} genislik=${view.width} kisit=${view.limit.bounds()}');
}
```

```text
ileti sayisi=5
dayatilan: 15
secilen: 10
kirpilan: 0
esnetilen: 0
ebeveyn: 15
ekran=200 dayatilan genislik=168 kisit=168..168
```

Odak düğüm `/Screen/Body/Embed/PlatformView`'dur. Beş ileti ağacı beş kez ölçer ve her ölçüm üç `dayatilan` kayıt verir: `/Screen`, `/Screen/Body` ve görünüm. Toplam `dayatilan: 15` ve `secilen: 10` bu yüzden çıkar, yani 5 çarpı 3 ve 5 çarpı 2. Yinelenen iletiler kayıt sayısını artırır, ama hiçbir kaydın boyutlanmasını değiştirmez: görünüm beş ölçümün beşinde de dayatılandır. Kopyayı sayan şey sayıcıdır, genişliğin sahibini söyleyen şey kuraldır.

Ekran 200'e inince iç boşluktan sonra gövde 168 genişliğinde kalır. Yuva 240 ister, ama kısıt 0..168 olduğu için yuva 168'e iner; görünüm de yuvanın verdiği sıkı kısıtla 168 alır. Çıktıdaki son satır bunu gösterir: boyutlanma yine `dayatilan`, kısıt `168..168`. İleti 180 isterken de sonuç değişmez, çünkü istek sıkı kısıtı değiştirmez.

**UY1.** Model yalnız genişliği ölçer. Yerel platformun görünümü, kendi içinde çizdiği şey, görünümün yüksekliği, dokunma olayları ve kanal çağrılarının zamanlaması modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Yuvayı sabit 240 saymak dersin varsayımıdır; gerçek bir yuva, görünüme kısıtı farklı biçimde verebilir.

#### Özet

- Bu dersin boyutlanması: dayatılan. Odak düğüm `/Screen/Body/Embed/PlatformView`'dur ve yuvanın verdiği `240..240` kısıtıyla iletideki istek 180, 400 ya da 96 olsa da 240 alır.
- Platform kanalı, Dart ve yerel platform arasında adlandırılmış bir konuşma hattıdır; ilettiği genişlik ağaçta yalnız bir istektir.
- Üç farklı istek üç satırda da dayatılan çıktı; yinelenen iletiler boyutlanmayı değil yalnız kayıt sayısını artırdı.
- Gömülü görünümün genişliğinin sahibi, kanalı dinleyen taraf değil, görünümü yuvaya koyan çerçevedir.

#### Sonraki Adım

Bu derste kanaldan gelen istek sonuca girmedi ve görünümün genişliğini yuva dayattı. Sıradaki konu, `Test Türleri`, aynı ekranı iki test yüzeyinde ölçer: 400 ve 320 genişliğinde. 300 isteyen `/Screen/Body/Chart` düğümü 400'de seçilen, 320'de kırpılan olur ve ölçülen her düğüm düzene girmiştir.

## Kalite ve Yayın

Test türleri, geliştirici araçları, gereksiz yeniden çizimi azaltan başarım ayarı, paket yönetimi ve sürüm kısıtları, platform başına derleme ve yayın.

### Test Türleri

Source: https://academia.sh/tr/kurslar/flutter/test-turleri

Bir test, beklenen boyutlanmayı ölçülen kayıtla karşılaştırır. Ders aynı ekranı iki test yüzeyinde ölçer ve testin neyi göremediğini gösterir.

Önceki derslerde uygulamanın yapısı kuruldu: bileşenler ağaca dizildi, durum ve platform kanalı bağlandı. Elde çalışan bir ekran var, ama çalıştığını bir kişinin ona bakarak söylemesi yetmez. Bu ders, bir ekranın beklenen davranışını yazıya dökmenin ve her değişiklikte makineye denetletmenin yolunu anlatır. Konu test türleridir; kursun ekseni olan "bu bileşenin genişliğini kim belirliyor" sorusu da bu derste bir test beklentisine dönüşür.

#### Üç Test Türü

Testler neyi bir araya getirdiklerine göre ayrılır. **Birim testi** tek bir işlevi ya da sınıfı alır, çevresinden yalıtır ve girdiye karşılık çıktıyı sınar. Ağaç, ekran ve platform yoktur; test hızlıdır ve neden kaldığını söylemesi kolaydır. **Bileşen testi** tek bir bileşeni ya da küçük bir bileşen ağacını gerçek çerçeveyle kurar, ama gerçek bir aygıt açmaz. Test, ağacı kurar, bir dokunmayı taklit eder, ardından ağaçta bir bileşenin varlığını ya da değerini sorgular. **Tümleşim testi** uygulamanın tamamını, ya da büyük bir bölümünü, bir aygıtta ya da emülatörde çalıştırır ve kullanıcının izleyeceği yolu baştan sona dolaşır.

Üçü arasındaki değiş tokuş basittir. Birim testi çabuk koşar ama bileşenlerin birbirine nasıl oturduğunu görmez. Tümleşim testi her şeyi görür ama yavaştır, kurulumu ağırdır ve kaldığında hatanın nerede olduğunu söylemez. Bileşen testi ikisinin arasında durur: ağacı görür, platformu görmez. Bu kursun ekseni olan boyutlanma da tam bu orta katmanda yaşar. Bir bileşenin genişliği, ebeveyninin kısıtına ve kendi isteğine bağlıdır; bu iki girdi bir ağaç kurulduğunda ortaya çıkar, tek bir işlevi çağırmakla ya da uygulamayı baştan sona dolaşmakla çıkmaz.

#### Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder: dayatılan, seçilen, kırpılan, esnetilen. Aşağıdaki dosya bu dersin çekirdeğidir; çıktı üretmez, ölçülen bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

#### Birim Testi: Ağaç Yok

Aşağıdaki parça bir birim testidir. Bir sayaç işlevini sınar ve çerçeveden yalnızca bir test tanımı ile bir beklenti ister. Çerçeve kodu olduğu için bu parça koşmaz; ders ona çıktı iddia etmez.

```dart
// Platform kodu: birim testi
void main() {
  test('sayac bir artar', () {
    final counter = Counter();
    counter.increment();
    expect(counter.value, 1);
  });
}
```

Testin üç parçası vardır: kurulum, eylem, beklenti. `Counter` kurulur, `increment` çağrılır ve değerin bir olduğu beklenir. Hiçbir yerde genişlik, kısıt ya da ekran geçmez. Birim testi bu kursun sorusuna cevap veremez; çünkü soru, bileşenin bir ebeveynin içinde durmasını gerektirir.

#### Bileşen Testi: Ağaç Var, Platform Yok

Bileşen testi ağacı kurar. Çerçeve bunun için bir test ortamı sunar: bileşen sahte bir ekrana yerleştirilir, bir kare çizilir ve testin sorguları ağacı gezer.

```dart
// Platform kodu: bileşen testi
void main() {
  testWidgets('baslik gorunur', (tester) async {
    await tester.pumpWidget(const ExampleScreen());
    expect(find.text('Rapor'), findsOneWidget);
    expect(tester.getSize(find.byType(Chart)).width, 300);
  });
}
```

Son satır önemlidir. Test bir bileşenin genişliğini sorar ve 300 bekler. Bu beklenti, bileşenin kendi isteği olan 300 birimi kimsenin değiştirmeyeceğini varsayar. Testin çalıştığı ekran genişliği bu varsayımı taşır, ama test bunu yazmaz. Test yüzeyi, yani testin sahte ekranının genişliği, bileşen testinin gizli girdisidir.

Aşağıdaki bölümler bu girdiyi görünür kılar. Modelde test, ölçülen kayıt listesinden bir yolu bulup boyutlanma adını beklenenle karşılaştıran küçük bir işlevdir.

#### Test Edilen Ekran

Denetlenecek ekran iki bileşenli bir sütundur: 120 birim isteyen bir başlık ve genişliği ayarlanabilen bir grafik. Ekran, grafiğin istediği genişliği alan bir işlevle kurulur; böylece ağaç duruma bağlıdır ve her ölçüm kendi ağacını kurar. Ağaç kuran işlev model dosyasında durur.

```dart
// screen_model.dart — test edilen ekranin bilesen agaci
import 'sizing.dart';

Part buildScreen(int chartWant) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      leaf('Chart', chartWant),
    ]));
```

Beklentiyi karşılaştıran işlev de ayrı bir dosyada durur. Ölçülen kayıt listesinden yolu arar; yol varsa boyutlanma adını beklenenle karşılaştırır, yoksa bunu söyler. Beklenen değer `printSizes`'a verilmez; çıktıyı yalnızca bu işlev yazar.

```dart
// check_sizing.dart — beklenen boyutlanmayi olculen kayitla karsilastirir
import 'sizing.dart';

void checkSizing(List<Sizing> log, String path, String expected) {
  final found = log.where((entry) => entry.path == path).toList();
  if (found.isEmpty) {
    print('KALDI: $path kayit yok');
    return;
  }
  final actual = found.first.sizing;
  if (actual == expected) {
    print('GECTI: $path $expected');
  } else {
    print('KALDI: $path beklenen=$expected olculen=$actual');
  }
}
```

#### 400 Birimlik Yüzeyde Geçen Test

Önce ekran 400 birimlik bir test yüzeyine konur. Grafik 300 birim ister ve test grafiğin isteğini seçilen olarak bekler. Aşağıdaki blok tüm kaydı basar, ardından denetimi koşar.

```dart
// main.dart
import 'sizing.dart';
import 'screen_model.dart';
import 'check_sizing.dart';

void main() {
  final log = measure(buildScreen(300), 400);
  printSizes(log);
  checkSizing(log, '/Screen/Body/Chart', 'secilen');
}
```

```text
dayatilan: /Screen genislik=400 kisit=400..400
dayatilan: /Screen/Body genislik=368 kisit=368..368
secilen: /Screen/Body/Title genislik=120 kisit=0..368
secilen: /Screen/Body/Chart genislik=300 kisit=0..368
GECTI: /Screen/Body/Chart secilen
```

Kök sıkı ekran kısıtını alır, iç boşluk iki yandan 16'şar birim düşer ve gövde 368 birimlik sıkı kısıt görür. Sütun kısıtı gevşetir; grafiğin 300 birimlik isteği 0..368 aralığına sığar. Grafik genişliğini kendi seçmiştir, test geçer.

#### 320 Birimlik Yüzeyde Kalan Test

Aynı ekran, aynı beklenti, yalnızca test yüzeyi 320 birim. Yeni bir geliştirici aynı testi daha dar bir aygıt için de koşturmak ister; kodda hiçbir şey değişmez.

```dart
// main.dart
import 'sizing.dart';
import 'screen_model.dart';
import 'check_sizing.dart';

void main() {
  final log = measure(buildScreen(300), 320);
  printSizes(log);
  checkSizing(log, '/Screen/Body/Chart', 'secilen');
}
```

```text
dayatilan: /Screen genislik=320 kisit=320..320
dayatilan: /Screen/Body genislik=288 kisit=288..288
secilen: /Screen/Body/Title genislik=120 kisit=0..288
kirpilan: /Screen/Body/Chart genislik=288 kisit=0..288
KALDI: /Screen/Body/Chart beklenen=secilen olculen=kirpilan
```

Gövde artık 288 birimlik sıkı kısıt görür. Grafik 300 ister, ama izin verilen en çok 288'dir; çıkan genişlik 288 ve boyutlanma kırpılan. Beklenti "seçilen" idi, test kalır. Kalan test bir hata bulmuştur: ekran dar yüzeyde grafiğini isteğinden küçük gösteriyor. Hatanın nerede olduğu da çıktıdan okunur, çünkü `beklenen` ve `olculen` alanları yan yana durur.

Burada iki yol vardır ve hangisinin seçileceği bir tasarım kararıdır. Ekran dar yüzeyde grafiği küçültebilir; bu davranış istenen davranışsa beklenti değişir. Ya da ekran grafiği kaydırmalı bir alana alır; bu durumda ağaç değişir ve test yeniden geçer. Test hangisinin doğru olduğunu söylemez, yalnızca iki durumun farkını görünür kılar.

#### Beklentiyi Düzeltmek

Dar yüzeyde kırpılmak istenen davranışsa beklenti buna göre yazılır. Aşağıdaki blok iki beklentiyi birlikte sınar. Birincisi kırpılanı doğru bekler. İkincisi ağaçta hiç bulunmayan bir düğümü sorar.

```dart
// main.dart
import 'sizing.dart';
import 'screen_model.dart';
import 'check_sizing.dart';

void main() {
  final log = measure(buildScreen(300), 320);
  checkSizing(log, '/Screen/Body/Chart', 'kirpilan');
  checkSizing(log, '/Screen/Body/Legend', 'secilen');
}
```

```text
GECTI: /Screen/Body/Chart kirpilan
KALDI: /Screen/Body/Legend kayit yok
```

İkinci satır önemli bir kuralı gösterir: ağaca girmeyen bir düğüm için kayıt yoktur. Bir testin yazıp da hiçbir şeyi sınamadığı sessiz bir durum buradan doğar. Düğümün adını yanlış yazan bir test, "seçilen" yerine "kayıt yok" ile kalır; bu yüzden denetim işlevi bulunmayan yolu geçmiş saymaz, kalmış sayar.

#### Tümleşim Testi Neyi Ekler

Bileşen testinin test yüzeyi modeldeki ekran genişliğidir; bir sayıdır ve testin yazarı onu seçer. Tümleşim testi gerçek bir aygıtta koşar ve aygıtın kendi ekran genişliğini, yazı tipi ölçüsünü, güvenli alan payını kullanır. Aynı ekran iki farklı aygıtta iki farklı genişlikle çalışır; tümleşim testi bunlardan hangisinde koşuyorsa onu görür.

Bu, bileşen testinin yerini tümleşim testinin almadığı anlamına gelir. Bileşen testi yüzeyi bilerek seçer: 400 ve 320 gibi iki sayı, iki sınıfı temsil eder. Tümleşim testi yüzeyi aygıttan alır ve elinin altındaki aygıt neyse onu sınar. İkisini birlikte koşturmak, sayısı az ama anlamlı yüzeylerde ağacı, birkaç gerçek aygıtta ise uygulamanın bütününü denetler.

#### Yaygın Yanılgı

**Testler geçiyorsa ekran her koşulda doğrudur.** Böyle değildir. Bu dersin iki ölçümü bunu gösterir: 400 birimde geçen test, 320 birimde kalır. Test aynıdır, ekran aynıdır; değişen yüzeydir. Bir testin geçmesi, ekranın her koşulda doğru olduğunu göstermez; yalnızca testin seçtiği koşulda doğru olduğunu gösterir.

Bu soru M12/K04'teki soruyla karıştırılmamalı. Orada bir testin sorusu "bu parça bu istekte koştu mu" idi ve cevap kapsamaydı. Burada soru, koşan bir bileşenin genişliğini kimin belirlediğidir. Ölçülen her düğüm düzene girmiştir, yani koşmuştur; boyutlanma yalnız düzene giren düğümü sınıflar ve koşmuş olmak doğru boyutlanmış olmak demek değildir.

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

Son ölçüm testin neyi göremediğini gösterir. Test yüzeyi ekranın bileşen ağacına girdi olarak verilir; test bu girdiyi değiştiremez ve girdinin değişince ne olacağını da önceden bilemez. Aşağıdaki blok 320 birimlik yüzeyde yalnızca grafiğin kaydına bakar ve ardından tüm kaydın sayımını verir.

```dart
// main.dart
import 'sizing.dart';
import 'screen_model.dart';

void main() {
  final log = measure(buildScreen(300), 320);
  printSizes(log.where((entry) => entry.path == '/Screen/Body/Chart').toList());
  countSizings(log);
}
```

```text
kirpilan: /Screen/Body/Chart genislik=288 kisit=0..288
dayatilan: 2
secilen: 1
kirpilan: 1
esnetilen: 0
ebeveyn: 3
```

Odak düğüm `/Screen/Body/Chart`. 320 birimlik yüzeyde grafik kırpılan çıktı; dört kaydın üçünde genişliği bileşenin kendisi değil ebeveyn ya da aralık belirliyor. Başlık hâlâ seçilen, ama grafik artık isteğini yerine getiremiyor. Dersin boyutlanması bu yüzden kırpılandır: dar test yüzeyi, grafiğin isteğinin aralığa sığmadığı yerdir.

**KY1.** Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir test, ölçülen kayıt listesinden bir yolu bulup boyutlanma adını karşılaştırır. Gerçek bir bileşen testi bir bileşenin boyutunu aygıtın gerçek ölçüleriyle sorgular ve yazı tipi ölçüsü gibi modelde bulunmayan girdilerden etkilenir. Modelde ölçülen beklentiler gerçek bir testin sorgularıyla bire bir örtüşmez.

#### Özet

- Bu dersin boyutlanması: kırpılan. Dar test yüzeyinde `/Screen/Body/Chart` isteğinin altında kaldı ve beklenen "seçilen" bu yüzden tutmadı.
- Birim testi bir işlevi yalıtır, bileşen testi bir ağacı kurar, tümleşim testi uygulamayı bir aygıtta baştan sona dolaşır.
- Boyutlanma bir ağaç özelliğidir; bu yüzden birim testi onu göremez, bileşen testi test yüzeyini seçerek görür.
- `checkSizing` beklenen adı ölçülen kayıtla karşılaştırır ve `GECTI` ya da `KALDI` verir; ağaçta olmayan bir yol geçmiş sayılmaz.
- Aynı ekran 400 birimde geçer, 320 birimde kalır: test yüzeyi bileşen testinin gizli girdisidir.

#### Sonraki Adım

Bir test kaldığında hatanın nerede olduğunu söylemek bir şeydir, hatanın neden olduğunu görmek başka. Sıradaki ders, `Geliştirici Araçları`, ağacı çalışırken inceleyen araçları ele alır ve bir bileşenin aldığı kısıtı, genişliği ve çıktının hangi bileşenden geldiğini gösterir. Konunun üçüncü dersi `Başarım Ayarı` ise ağaçtaki gereksiz değişiklikleri azaltmayı anlatır.

### Geliştirici Araçları

Source: https://academia.sh/tr/kurslar/flutter/gelistirici-araclari

Bileşen inceleyicisi, düzen inceleme ve başarım görünümü bir ağacı çalışırken gösterir. Ders inceleyicinin gösterdiği kısıt ve genişliği ölçülen kayıtla eşler.

Önceki derste bir test kaldı ve hatanın hangi yolda olduğu söylendi. Neden böyle olduğunu görmek ise başka bir işin konusu: ağacı çalışırken açıp bakmak. Bu ders, çerçevenin geliştirme araçlarını boyutlanma ekseniyle okur. Bir araç ekranda bir bileşenin genişliğini gösterdiğinde, aslında kursun sorusunu cevaplıyor: bu genişliği kim belirledi.

#### Dört Araç

**Bileşen inceleyicisi** çalışan uygulamanın bileşen ağacını gösterir. Bir bileşen seçildiğinde yalnız onun özelliklerini değil, aldığı kısıtı ve ortaya çıkan boyutu da görürsünüz. Çerçevenin belgesi bu konuda şunu söyler: "The constraints flow down the tree, and the sizes flow back up." Yani kısıtlar ağaçta aşağı iner, boyutlar yukarı çıkar; düzen sorunlarında bakılacak iki alan `size` ve `constraints` alanlarıdır.

**Düzen inceleme**, inceleyicinin içinde esnek bileşenler için ayrı bir görünümdür. Satır, sütun ya da esnek çocuklarla ilgili bir bileşen seçildiğinde esnek çarpanı, esnek uyumu ve kısıtlar görselleştirilir; kısıt ihlalleri kırmızıyla, taşma hataları sarı şeritli bir desenle gösterilir. Bu görünümde değiştirdiğiniz değerler kaynak koda yazılmaz ve sıcak yeniden yüklemede geri alınır.

**Başarım görünümü** genişlik değil zaman gösterir: kareleri bir çizelgede dizer, yavaş kalan kareleri kırmızı işaretler ve bir karede hangi işin yapıldığını olay çizelgesinde açar. Bileşen yapılarının hangi anda kurulduğunu gösteren bir seçenek de vardır. Belge, ölçümün geliştirme kipinde değil başarım ölçüm kipinde alınmasını önerir, çünkü geliştirme kipindeki kare süreleri yayın sürümünün başarımını yansıtmaz. Bu dersin sayıları süre içermez; başarım görünümü bir sonraki dersin konusudur.

**Sıcak yeniden yükleme** değişen kaynak kodu çalışan uygulamaya yollar ve ağacı yeniden kurar. `main` ve `initState` yeniden çalışmaz; uygulamanın durumu korunur. Belgeye göre bu yalnız geliştirme kipinde çalışır. Durum korunduğu için kaynak koddaki bir değişiklik ekranda görünmeyebilir: örneğin başlangıç değerini değiştirmek, değer zaten kurulmuşsa bir etki yaratmaz. O zaman uygulamayı baştan başlatan sıcak yeniden başlatma gerekir.

#### Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde inceleyicinin bir bileşen için gösterdiği iki değer, `printSizes` çıktısındaki `genislik` ve `kisit` alanlarıdır. Model başarım görünümünü ya da sıcak yeniden yüklemeyi taşımaz; ikincisine ağacı yeniden ölçerek yaklaşılır.

#### İncelenen Ekran

Ekranın ağacı bir sütundur: 150 birim isteyen bir etiket ve bir `Center` içinde 80 birim isteyen bir `Chip`. Ağaç, çipin genişliğini alan bir işlevle kurulur.

```dart
// panel_model.dart — incelenen ekranin bilesen agaci
import 'sizing.dart';

Part buildPanel(int chipWidth) => pad('Screen', 16, column('Body', [
      leaf('Label', 150),
      center('Center', leaf('Chip', chipWidth)),
    ]));
```

Bu ağacın kaynak karşılığında `Center` yalnız çipi sarmalar. Geliştirici çipin 80 birim olduğunu bilir ve ona ayrılan alanın da yaklaşık o kadar olmasını bekler. Bu beklenti, inceleyiciye bakılana kadar sürer.

#### Beklenmedik Geniş Görünen Bileşen

Ekran 360 birim genişliğinde. Aşağıdaki blok kaydın tamamını basar; her satır, inceleyicinin bir bileşen için göstereceği değerlerdir.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(80), 360);
  printSizes(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Label genislik=150 kisit=0..328
esnetilen: /Screen/Body/Center genislik=328 kisit=0..328
secilen: /Screen/Body/Center/Chip genislik=80 kisit=0..328
```

`Chip` 80 birim ister ve 80 birim alır: seçilen. `Center` ise 328 birimdir, çipten çok daha geniş. Sınırlı bir kısıtta ortalayan bileşen isteğini bildirmez; kendisine verilen en geniş değere yayılır. Kursun dilinde bu, esnetilen boyutlanmadır. İnceleyicide `Center` seçildiğinde göreceğiniz `size` 328, `constraints` ise 0 ile 328 arasıdır. Geliştirici çipe bakıp `Center`'ın da dar olmasını bekliyordu; oysa genişliği çip değil, ebeveynin kısıtı belirledi.

#### Kısıt Kardeşe de Aynen İner

M11/K05'te bir işlemin kaynağa ne bıraktığı sorulurdu: kaynak bir kez tüketildi mi, ardından hâli nedir. Düzen kısıtı için bu soru geçersizdir, çünkü düzen kısıtı tüketilmez. Sütun gövdeye gelen kısıtı gevşetir ve her çocuğa aynı gevşek kısıtı verir; etiket aldığında `Center`'ın payı eksilmez. Aşağıdaki blok, `0..328` kısıtını alan tüm bileşenleri listeler.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(80), 360);
  printSizes(log.where((entry) => entry.limit.bounds() == '0..328').toList());
}
```

```text
secilen: /Screen/Body/Label genislik=150 kisit=0..328
esnetilen: /Screen/Body/Center genislik=328 kisit=0..328
secilen: /Screen/Body/Center/Chip genislik=80 kisit=0..328
```

`Label` ve `Center` aynı kısıtı alır. `Chip` de aynı kısıtı alır, çünkü ortalayan bileşen kısıtı gevşetir ama üst sınırı korur. İnceleyicide yan yana duran iki kardeşin kısıt alanı bu yüzden aynı görünür. Kısıtı daraltan tek şey iç boşluktur ve o ağaçta yalnız `Screen` düzeyindedir.

#### Taşma ve Düzen İnceleme

Çipin isteği kısıtı aşarsa ne olur? Aşağıdaki blok çipi 400 birim yapar ve `Center` altındaki kayıtlara bakar.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(400), 360);
  printSizes(log.where((entry) => entry.path.startsWith('/Screen/Body/Center')).toList());
}
```

```text
esnetilen: /Screen/Body/Center genislik=328 kisit=0..328
kirpilan: /Screen/Body/Center/Chip genislik=328 kisit=0..328
```

Çip 400 ister, izin verilen en çok 328'dir. Genişliği 328'e çekilir ve kırpılan olur. Gerçek çerçevede aynı durum bir taşma hatası olarak görünür: düzen incelemenin sarı şeritli deseni tam burada işe yarar. Modelde taşma bir görüntü değil, bir sınıftır; kayıt yalnız isteğin aralık dışında kaldığını söyler.

#### Sıcak Yeniden Yükleme Ağacı Yeniden Ölçer

Sıcak yeniden yükleme ağacı yeniden kurar. Modelde bunun karşılığı, yeni durumla aynı ağaç işlevini çağırıp ağacı yeniden ölçmektir. Aşağıdaki blok çip genişliğini 80'den 400'e çevirir ve iki ölçümün sayımını yan yana basar.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  for (final chip in [80, 400]) {
    print('chip=$chip');
    countSizings(measure(buildPanel(chip), 360));
  }
}
```

```text
chip=80
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 1
ebeveyn: 3
chip=400
dayatilan: 2
secilen: 1
kirpilan: 1
esnetilen: 1
ebeveyn: 4
```

Çip değeri değişince bir bileşen seçilenden kırpılana geçer; ebeveyn sayısı üçten dörde çıkar. Gerçek sıcak yeniden yüklemede uygulamanın durumu korunur; modelde ise yeniden ölçme her seferinde sıfırdan bir ağaç kurar. Modelin korunan durumu yoktur.

#### Aracın Gösterdiğini Okumak

Bir inceleyiciye bakarken izlenecek sıra basittir. Önce bileşeni seç. Sonra aldığı kısıta bak: sıkıysa boyutu ebeveyn dayatmıştır. Sınırlı ve gevşekse bileşenin kendi isteğine bak: istek aralığa sığıyorsa seçilen, sığmıyorsa kırpılan. İstek yoksa ve üst sınır sonluysa genişlik üst sınırdır: esnetilen. Bu üç soru dört boyutlanmanın hepsini ayırır.

Aşağıdaki iki parça, aynı sorunun çerçeve kodundaki karşılığıdır. İlki ekranda her bileşenin sınırlarını çizen bir bayrağı açar; ikincisi ortalanmış bir sarmalayıcıyı gösterir.

```dart
// Platform kodu: duzen sinirlarini cizmek
void main() {
  debugPaintSizeEnabled = true;
  runApp(const ExampleScreen());
}
```

Bu bayrak her bileşenin sınırını çizer ve yalnız geliştirme kipinde etkilidir.

```dart
// Platform kodu: ortalanmis sarmalayici
Column(
  children: [
    const Text('Etiket'),
    Center(child: Chip(label: Text('Etiket'))),
  ],
)
```

Bu iki satırlık düzende `Center` kendisine verilen genişliğe yayılır. İnceleyici bu yayılmayı gösterir; modelde de aynı yayılma `Center` düğümü için ölçülmüştür.

#### Yaygın Yanılgı

**İnceleyicide geniş görünen bileşenin içeriği de geniştir.** Böyle değildir. `Center` 328 birimdir ama içindeki çip 80 birimdir. Bileşenin sınırı ile içeriği ayrı şeylerdir; inceleyici sınırı gösterir, içeriğin kendi isteğini ayrıca okumak gerekir.

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

Son ölçüm, kuralın inceleyici gibi yalnız o anki kısıtı gördüğünü gösterir. Ekran 360 birimden 240 birime inince `Center`'ın genişliği çipe değil ekrana bağlı kalır.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  for (final screen in [360, 240]) {
    final log = measure(buildPanel(80), screen);
    printSizes(log.where((entry) => entry.path == '/Screen/Body/Center').toList());
  }
}
```

```text
esnetilen: /Screen/Body/Center genislik=328 kisit=0..328
esnetilen: /Screen/Body/Center genislik=208 kisit=0..208
```

Odak düğüm `/Screen/Body/Center`. İki ekranda da esnetilen çıkar; genişliği 328'den 208'e iner, çip ise iki ölçümde de 80 birimdir. Dersin boyutlanması bu yüzden esnetilendir: ortalayan bileşenin genişliğini ekran belirler.

**KY1.** Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde inceleyicinin değeri bir kayıt satırıdır. Gerçek inceleyici bir bileşenin yalnız kısıt ve boyutunu değil, çizim nesnesinin özelliklerini ve esnek çarpanını da gösterir; bunlar modelde yoktur. Başarım görünümünün kare çizelgesi ve sıcak yeniden yüklemenin korunan durumu da modelde ölçülmez.

#### Özet

- Bu dersin boyutlanması: esnetilen. `Center` sınırlı kısıtta isteğini bildirmedi ve 328 birime yayıldı; içindeki çip 80 birimde seçilen kaldı.
- Bileşen inceleyicisi kısıt ve boyutu, düzen inceleme esnek bileşenleri, başarım görünümü kareleri gösterir.
- İnceleyicinin gösterdiği kısıt ve genişlik, modelde `printSizes` satırının `kisit` ve `genislik` alanlarıdır.
- Düzen kısıtı tüketilmez: aynı gevşek kısıt `Label`, `Center` ve `Chip` için de `0..328` olarak indi.
- Sıcak yeniden yükleme ağacı yeniden kurar ve durumu korur; modelde karşılığı ağacı yeni durumla yeniden ölçmektir.

#### Sonraki Adım

İnceleyici bir sorunu gösterir, ama çoğu zaman sorun yanlış genişlik değil gereksiz iştir. Sıradaki ders, `Başarım Ayarı`, ağaçta gereksiz yeniden kurmayı azaltan yöntemleri ve bir bileşenin değişikliğinin yukarıya sızmadığı yeniden düzen sınırını ele alır.

### Başarım Ayarı

Source: https://academia.sh/tr/kurslar/flutter/basarim-ayari

Gereksiz yeniden kurma ve yeniden düzen, başarım sorununun en sık kaynağıdır. Ders, bir değişikliğin ağaçta ne kadar yükseğe çıktığını ölçülen kayıtla gösterir.

Önceki derste inceleyici bir sorunu gösterdi. Çoğu zaman sorun yanlış bir genişlik değil, gereksiz bir iştir: bir bileşenin değişmeyen alt ağacı her değişiklikte yeniden kurulur ya da yeniden ölçülür. Bu ders iki soruya bakar. Bir bileşenin değişikliği ağaçta ne kadar yükseğe çıkıyor? Ağaçta ne kadar iş yapılıyor?

Başarımın kendisi bir sayıdır ve bu ders sayı yazmaz; süre ölçmez. Yazılan şey, değişikliğin kaç düğüme dokunduğudur. Süre ve kare bütçesi başka bir işin konusudur.

#### Yeniden Düzen Sınırı

Birinci soru, değişikliğin ağaçta ne kadar yükseğe çıktığıdır. Bir bileşenin isteği değişirse ve ebeveyni çocuğun boyutunu okuyorsa, ebeveynin de yeniden düzenlenmesi gerekir; onun ebeveyni de çocuğunun boyutunu okuyorsa değişiklik yukarı doğru sürer. Çerçevenin belgesine göre ebeveyn çocuğun boyutunu okumuyorsa ya da çocuğa verilen kısıt sıkıysa, değişiklik orada durur: çocuğun yalnızca bir boyutu olabilir ve ebeveyn bunu bilir. Bu noktaya yeniden düzen sınırı denir.

Bu dersin ölçülebilir iddiası şudur: sıkı kısıt veren bileşen, altındaki değişikliğin yukarı çıkmasını durdurur. Modelde bu iddia, bir bileşenin isteği değişince hangi düğümlerin genişliğinin değiştiğini sayarak denenir.

#### Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir değişiklik, aynı ağaç işlevinin başka bir istekle yeniden çağrılması ve iki kaydın karşılaştırılmasıdır. Model çerçevenin yeniden kurma işini ya da zamanını taşımaz.

#### Sayılan Ekran

İki ağaç aynı sayacı, yani isteği değişecek yaprağı taşır. Birincisinde sayaç, 200 birimlik sabit bir panelin içindedir; panel çocuğuna sıkı kısıt verir. İkincisinde panel yalnızca bir iç boşluktur ve sayacın isteği panelin genişliğine geçer.

```dart
// counter_model.dart — sayacin iki ortami
import 'sizing.dart';

Part buildBounded(int want) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      fixed('Panel', 200, leaf('Counter', want)),
    ]));

Part buildLoose(int want) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      pad('Panel', 8, leaf('Counter', want)),
    ]));
```

Değişikliği sayan yardımcı, iki kaydı yola göre eşler. Yeni olan ya da genişliği değişen her düğümü sayar.

```dart
// change_count.dart — iki olcum arasinda genisligi degisen dugumleri sayar
import 'sizing.dart';

int countChanged(List<Sizing> before, List<Sizing> after) {
  var changed = 0;
  for (final entry in after) {
    final match = before.where((old) => old.path == entry.path).toList();
    if (match.isEmpty || match.first.width != entry.width) {
      changed++;
    }
  }
  return changed;
}
```

#### Sıkı Kısıtlı Panel

İlk ölçüm sınırlı ortamın tüm kaydını basar. Ekran 360 birimdir ve sayaç 100 birim ister.

```dart
// main.dart
import 'sizing.dart';
import 'counter_model.dart';

void main() {
  final log = measure(buildBounded(100), 360);
  printSizes(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Panel genislik=200 kisit=0..328
dayatilan: /Screen/Body/Panel/Counter genislik=200 kisit=200..200
```

Panel 200 birim ister ve 200 alır: seçilen. Sayacın istediği ise bir önemi taşımaz, çünkü panel ona 200 ile 200 arasında sıkı bir kısıt verir. Sayacın genişliğini sayacın isteği değil panel belirler: dayatılan.

#### İstek Değişince

Şimdi sayacın isteği 100'den 300'e çıkar. Sıkı kısıtlı ortamda iki kaydı karşılaştırırız.

```dart
// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final before = measure(buildBounded(100), 360);
  final after = measure(buildBounded(300), 360);
  printSizes(after.where((entry) => entry.path.endsWith('Counter')).toList());
  print('degisen: ${countChanged(before, after)}');
}
```

```text
dayatilan: /Screen/Body/Panel/Counter genislik=200 kisit=200..200
degisen: 0
```

Sayaç iki ölçümde de 200 birimdir ve hiçbir düğümün genişliği değişmemiştir. Değişiklik panelde durdu. Gerçek çerçevede de aynı fikir geçerlidir: sıkı kısıt veren bileşen, altındaki değişikliği yukarı taşımaz. Hangi düğümlerin gerçekten yeniden düzenleneceği ayrı bir iştir ve bu derste ölçülmez.

#### Sıkı Kısıt Vermeyen Panel

Aynı değişikliği iç boşluktan oluşan panelde deneriz. Burada panel çocuğuna sıkı kısıt vermez.

```dart
// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final before = measure(buildLoose(100), 360);
  final after = measure(buildLoose(300), 360);
  printSizes(after.where((entry) => entry.path.contains('Panel')).toList());
  print('degisen: ${countChanged(before, after)}');
}
```

```text
secilen: /Screen/Body/Panel genislik=316 kisit=0..328
secilen: /Screen/Body/Panel/Counter genislik=300 kisit=0..312
degisen: 2
```

Sayacın 300 birimlik isteği 0 ile 312 arasına sığar ve seçilen kalır. Panel çocuğunun genişliğini iki yandan 8'er birim iç boşlukla sarar; sayaç değişince panelin genişliği de 116'dan 316'ya çıkar. İki düğüm değişti: sayaç ve panel. Sütun gövdesi sıkı kısıt aldığı için gövdeye ve ekrana kadar çıkmadı. Değişiklik sayaçtan bir basamak yukarı, panele çıktı ve gövdedeki sıkı kısıtta durdu.

#### Değişikliğin Yolunu Okumak

İki ölçüm aynı değişikliği farklı yollara sokar. Sıkı kısıtlı panelde sayacın yolu panelde biter: ebeveyn çocuğun isteğini hiç sormaz, çünkü cevabı önceden dayatmıştır. Gevşek panelde ise ebeveyn çocuğun boyutuna bağlıdır; panelin genişliği sayacın genişliğinden türer ve değişiklik bir basamak yukarı çıkar. Gövde bu zinciri keser, çünkü gövdenin genişliğini ekran dayatır.

Bu okuma bir başarım sorununu ararken işe yarar. Bir bileşen sık değişiyorsa önce ona ne kadar yukarıdan bağlı olduğuna bakılır. Yukarıdaki her ebeveyn çocuğun boyutunu okuyorsa, küçük bir değişiklik büyük bir alt ağacı yeniden düzenletebilir. Araya sıkı kısıt veren bir bileşen koymak bu zinciri kısaltır; bunun bedeli, çocuğun genişliğini artık çocuğun değil o bileşenin belirlemesidir. Sayaç 300 birim istese de 200 birimde kalır. Yani başarım kazancı ile boyutlanma özgürlüğü arasında bir alışveriş vardır ve bu alışverişi görmenin yolu, hangi düğümün dayatılan olduğuna bakmaktır.

#### Gereksiz Yeniden Kurma ve Sabit Kurucu

Çerçeve bir bileşen değiştiğinde onun altındaki ağacı yeniden kurar. Alt ağaç aynı kalıyorsa bu iş boşunadır. Çerçevenin belgesi, değişmez sabit kurucuyla kurulan bir bileşen için yeniden kurma işinin çoğunun atlanabileceğini söyler. Fikir basittir: değişmeyen parça her seferinde yeni bir nesne olarak üretilmez, aynı sabit nesne kullanılır.

Aşağıdaki parça iki biçimi yan yana gösterir; ölçülen modelin dışında, çerçeve kodudur.

```dart
// Platform kodu: sabit kurucu ve her seferinde yeni nesne
Column(
  children: [
    const Text('Baslik'),
    Text('Sayac: $counter'),
  ],
)
```

İlk çocuk hiç değişmez ve sabit kurucuyla kurulur. İkincisi sayaç değiştikçe değişir, bu yüzden sabit olamaz. Sabit kurucu yeniden kurma işini azaltır; boyutu, yani genişliği değiştirmez. Genişliği kimin belirlediği sorusu bu parçada aynı kalır.

#### Başarım Sorunu ile Bütçe Aşımı

M31/K06'da başarım, bir bütçenin aşılıp aşılmadığıyla sorulur; burada soru genişliği kimin belirlediğidir ve bütçe yoktur. Kırpılan bir bileşen bütçeyi aşmış sayılmaz; yalnızca isteği izin verilen aralığa sığmamıştır. Bu derste hiçbir süre yazılmadı ve yazılmayacak; yazılan şey bir değişikliğin kaç düğüme dokunduğudur.

#### Yaygın Yanılgı

**Sabit kurucu bir bileşenin genişliğini değiştirir ya da korur.** İkisi de yanlıştır. Sabit kurucu yeniden kurma işini azaltır; genişliği kimin belirlediği sorusu aynı kalır. Sayaç sabit kurucuyla da kurulsa, `buildBounded` içinde 200 dayatılan, `buildLoose` içinde 300 seçilen olurdu.

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

Son ölçüm, sınır kuralını sıkı kısıt ile gevşek kısıt arasındaki farka bağlar. Aynı değişiklik iki ortamda koşar ve yalnız değişen düğüm sayısı basılır.

```dart
// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final bounded = countChanged(measure(buildBounded(100), 360), measure(buildBounded(300), 360));
  final loose = countChanged(measure(buildLoose(100), 360), measure(buildLoose(300), 360));
  print('sinirli: $bounded');
  print('sinirsiz: $loose');
}
```

```text
sinirli: 0
sinirsiz: 2
```

Odak düğüm `/Screen/Body/Panel/Counter`. Sıkı kısıtlı ortamda dayatılandır, değişen düğüm sayısı sıfırdır; gevşek ortamda seçilendir ve iki düğüm değişir. Dersin boyutlanması bu yüzden dayatılandır: sayacın genişliğini, değişen isteği değil, panelin sıkı kısıtı belirledi.

**KY1.** Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir değişiklik iki kaydın karşılaştırmasıdır ve süre taşımaz. Çerçevenin yeniden kurma işi, nesne ömrü ve yeniden düzen sırası modelde yoktur. Yeniden düzen sınırının tam koşulları çerçevenin belgesindedir; model yalnız sıkı kısıtın etkisini örnekler.

#### Özet

- Bu dersin boyutlanması: dayatılan. Sayacın genişliğini, değişen isteği değil, panelin sıkı kısıtı belirledi.
- Sabit kurucu yeniden kurma işini azaltır; boyutu belirleyen kuralı değiştirmez.
- Sıkı kısıt veren bileşen, altındaki değişikliğin yukarı çıkmasını durdurur; modelde değişen düğüm sayısı 0 oldu.
- Gevşek kısıtta aynı değişiklik iki düğümü değiştirdi.
- Kırpılan boyutlanma bir bütçe aşımı değildir; bu dersin kayıtları süre taşımaz.

#### Sonraki Adım

Başarım ayarı uygulamanın kendi ağacını hedefler. Ama uygulama çoğu zaman yalnız kendi kodundan oluşmaz: başkalarının yazdığı paketler de ağaca girer. Sıradaki ders, `Paket Yönetimi`, bir paketin sürümü değişince ağaçta ne olduğunu ele alır.

### Paket Yönetimi

Source: https://academia.sh/tr/kurslar/flutter/paket-yonetimi

Bir uygulama başkalarının yazdığı paketlerle büyür. Ders paket deposunu, sürüm kısıtını ve kilit dosyasını anlatır; bir paketin yeni sürümünün bileşen genişliğini nasıl etkilediğini ölçer.

Önceki ders uygulamanın kendi ağacını hedefledi. Ama bir uygulama yalnız kendi kodundan oluşmaz: derecelendirme çubuğu, görsel önbelleği ya da bir tarih seçici başkalarının yazdığı paketlerden gelir. Bir paketin yeni sürümü çıktığında uygulamada ne değişir? Bu ders önce paket düzeninin dört parçasını sıralar, sonra bir sürüm yükseltmesinin bir bileşenin genişliğine nasıl yansıdığını ölçer.

#### Dört Parça

**Paket deposu**, paketlerin sürümleriyle birlikte yayımlandığı ve araçların onları indirdiği ortak yerdir. **Bağımlılık**, uygulamanın bir pakete ihtiyaç duyduğunu söyleyen bildirimdir. **Sürüm kısıtı**, bağımlılığın hangi sürümlerle kabul edileceğini bir aralık olarak yazar. **Kilit dosyası** ise çözümlemenin sonucunu saklar: araç bir kez hangi sürümü seçtiyse onu kaydeder ve sonraki kurulumlar aynı sürümü getirir.

Çerçevenin belgesine göre bir uygulamanın bildirimi `pubspec.yaml` dosyasıdır ve çözülmüş sürümler `pubspec.lock` dosyasında durur. Belge, uygulamalarda kilit dosyasının sürüm denetimine eklenmesini önerir; böylece her geliştirici ve her derleme aynı sürümlerle çalışır.

#### Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir paket sürümü, bir bileşenin varsayılan genişliğini veren bir sayıdır. Model paket çözümlemesini, sürüm aralıklarını ve indirmeyi taşımaz.

#### Bildirim

Aşağıdaki parça bildirimdeki bağımlılığı gösterir. Paket adı kurguseldir.

```yaml
# Platform kodu: paket bildirimi
dependencies:
  ornek_paket: ^1.3.0
```

`^1.3.0`, sürüm 1.3.0 ve onunla uyumlu sonraki sürümleri kabul eder. Bu bir sürüm kısıtıdır. Sürüm kısıtı ile düzen kısıtı iki ayrı şeydir: birincisi hangi paket sürümünün kurulacağını, ikincisi bir bileşenin hangi genişlik aralığında ölçüleceğini söyler ve aralarında bir bağ yoktur.

#### Paketin İki Sürümü

Kurgusal paketin derecelendirme bileşeni sürüm 1'de 96 birim, sürüm 2'de 112 birim varsayılan genişlikle gelir. Uygulama bileşeni bir başlığın altına koyar ve genişliği kendisi vermez. Ağaç, sürümü alan bir işlevle kurulur.

```dart
// package_model.dart — paketin iki surumunun verdigi varsayilan genislik
import 'sizing.dart';

int ratingWidth(int version) => version == 1 ? 96 : 112;

Part buildRating(int version) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      leaf('Rating', ratingWidth(version)),
    ]));
```

#### Eski Sürüm

İlk ölçüm sürüm 1 için tüm kaydı basar. Ekran 360 birimdir.

```dart
// main.dart
import 'sizing.dart';
import 'package_model.dart';

void main() {
  final log = measure(buildRating(1), 360);
  printSizes(log);
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Rating genislik=96 kisit=0..328
```

Başlık 120, derecelendirme 96 birim ister ve ikisi de aldığı 0 ile 328 arasındaki aralığa sığar. İkisinin genişliğini de kendileri seçmiştir.

#### Yeni Sürüm

Şimdi aynı ağaç sürüm 2 ile kurulur. Uygulamanın kodunda hiçbir şey değişmemiştir; yalnız kilit dosyası yeni sürümü gösterir. Aşağıdaki blok iki sürümü yan yana ölçer ve yalnız derecelendirmenin satırını basar.

```dart
// main.dart
import 'sizing.dart';
import 'package_model.dart';

void main() {
  for (final version in [1, 2]) {
    print('surum=$version');
    final log = measure(buildRating(version), 360);
    printSizes(log.where((entry) => entry.path == '/Screen/Body/Rating').toList());
  }
}
```

```text
surum=1
secilen: /Screen/Body/Rating genislik=96 kisit=0..328
surum=2
secilen: /Screen/Body/Rating genislik=112 kisit=0..328
```

Yeni sürümde derecelendirme 112 birimdir ve yine seçilendir. Aldığı kısıt aynıdır: `0..328`. Değişen tek şey bileşenin kendi isteğidir. Sürüm yükseltmesi, ağacın hiçbir düğümünün kısıtını değiştirmedi.

#### Bu Değişiklik Nereye Ulaşıyor

M12/K02'de bir kod değişikliğinin nereye yayıldığı sorulurdu: değişen bir parçaya kimler bağlı, etki nereye ulaşıyor. Boyutlanmada yayılım sorusu yoktur. Paketin sürümü değişince düzen kuralı bunu bilmez; kural yalnız yeni isteği görür, o isteğin hangi sürümden geldiğini görmez. Bunu sayımla da doğrulayabiliriz.

```dart
// main.dart
import 'sizing.dart';
import 'package_model.dart';

void main() {
  for (final version in [1, 2]) {
    print('surum=$version');
    countSizings(measure(buildRating(version), 360));
  }
}
```

```text
surum=1
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 2
surum=2
dayatilan: 2
secilen: 2
kirpilan: 0
esnetilen: 0
ebeveyn: 2
```

İki sürümün sayımı aynıdır. 360 birimlik ekranda sürüm yükseltmesi hiçbir düğümün sınıfını değiştirmedi.

#### Bildirimi Kilitlemek

Kilit dosyası, sürüm kısıtının izin verdiği aralıktan tek bir sürüm seçip saklar. Kilit dosyası yoksa ya da sürüm denetimine eklenmediyse, aynı `^1.3.0` kısıtı farklı zamanlarda farklı sürümler getirebilir. Derecelendirme örneğinde bu, aynı kodun bir derlemede 96, ötekinde 112 birimlik bir bileşen göstermesi demektir. Yani kilit dosyası yalnız bir derleme kolaylığı değil, ekranın aynı görünmesinin de koşuludur.

Bu, sürüm kısıtı ile düzen kısıtının ayrı olduğu kuralıyla çelişmez. Kilit dosyası hangi sürümün kurulacağını sabitler; sürüm kurulduktan sonra bileşenin isteği artık bir sayıdır ve ağaçtaki yerinde ölçülür.

#### Yaygın Yanılgı

**Paketi yükseltmek ekrandaki düzeni bozar ya da düzeltir.** Yükseltme düzen kuralını değiştirmez; yalnız bileşenin isteğini değiştirir. Ekranın ne olacağını sonra yine kısıt belirler. İstek aralığa sığıyorsa sonuç bir şey fark ettirmez, sığmıyorsa kırpılır. Sonraki bölümdeki dar ekran ölçümü bu ikinciyi gösterir.

#### Dar Ekranda

Ekran 130 birim genişliğine iner. İç boşluk iki yandan 16'şar birim düşer; gövdeye 98 birimlik bir aralık kalır. Sürüm 1'in 96 birimlik isteği aralığa sığar, sürüm 2'nin 112 birimlik isteği sığmaz.

```dart
// main.dart
import 'sizing.dart';
import 'package_model.dart';

void main() {
  for (final version in [1, 2]) {
    print('surum=$version');
    final log = measure(buildRating(version), 130);
    printSizes(log.where((entry) => entry.path == '/Screen/Body/Rating').toList());
  }
}
```

```text
surum=1
secilen: /Screen/Body/Rating genislik=96 kisit=0..98
surum=2
kirpilan: /Screen/Body/Rating genislik=98 kisit=0..98
```

Aynı yeni sürüm, 130 birimlik ekranda kırpılan olur. Ağaç aynı, kural aynı; yalnız isteğin aralığa sığıp sığmadığı değişti. Sürüm yükseltmesinin uygulama için riski buradadır: istek büyüyünce dar bir ekranda kırpılma görülebilir.

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

Son ölçüm iki sürümü yalnız sınıf adıyla karşılaştırır. Ekran 360 birimdir.

```dart
// main.dart
import 'sizing.dart';
import 'package_model.dart';

void main() {
  for (final version in [1, 2]) {
    final log = measure(buildRating(version), 360);
    final entry = log.firstWhere((item) => item.path == '/Screen/Body/Rating');
    print('surum=$version ${entry.sizing}');
  }
}
```

```text
surum=1 secilen
surum=2 secilen
```

Odak düğüm yeni sürümdeki `/Screen/Body/Rating`. İki sürümde de seçilendir: kural yalnız yeni isteği görür, hangi sürümden geldiğini görmez. Dersin boyutlanması bu yüzden seçilendir.

**KY1.** Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde paket sürümü bir tamsayı işlevidir. Gerçek paket çözümlemesi, sürüm aralıklarının kesişimi, kilit dosyasının üretilmesi ve paket deposundan indirme modelde yoktur. Bir paketin gerçek varsayılan genişliği ise o paketin kendi belgesindedir.

#### Özet

- Bu dersin boyutlanması: seçilen. Yeni sürümdeki derecelendirme, 360 birimlik ekranda 112 birim ister ve 0..328 aralığında kendi genişliğini seçer.
- Paket deposu paketleri yayımlar, bağımlılık bildirir, sürüm kısıtı aralık yazar, kilit dosyası seçilen sürümü saklar.
- Sürüm kısıtı hangi paket sürümünün kurulacağını, düzen kısıtı bir bileşenin hangi genişlik aralığında ölçüleceğini söyler; ikisi ayrıdır.
- Sürüm yükseltmesi düğümlerin kısıtını değiştirmedi; yalnız bileşenin isteğini 96'dan 112'ye çıkardı.
- Kural yalnız yeni isteği görür: 360 birimde iki sürüm de seçilen, 130 birimde yeni sürüm kırpılan çıkar.

#### Sonraki Adım

Kalite, test, araç, başarım ve paketler yerine oturdu. Sıradaki ve konunun son dersi, `Derleme ve Yayın`, aynı uygulamanın iki hedefe, telefona ve tablete, birlikte yayımlandığında aynı bir görselin iki hedefte de kırpılıp kırpılmadığına bakar.

### Derleme ve Yayın

Source: https://academia.sh/tr/kurslar/flutter/derleme-ve-yayin

Bir uygulama yazıldıktan sonra kullanıcıya ulaşana dek derlenir, imzalanır ve bir kanaldan dağıtılır. Ders bu yolu platform başına sıralar; aynı sürümü telefon ve tablet için ölçer ve on dokuz dersin boyutlanmasını tek tabloda toplar.

Önceki ders paketleri ele aldı. Geriye uygulamayı kullanıcıya ulaştırmak kalıyor: derlemek, imzalamak ve bir kanaldan dağıtmak. Bu ders önce bu yolun parçalarını sıralar, sonra aynı sürümü telefon ve tablet için ölçer ve aynı görselin iki hedefte de kırpılıp kırpılmadığına bakar. Ders kursun son dersidir; sonunda on dokuz dersin boyutlanması tek tabloda toplanır.

#### Derleme Kipleri

Çerçevenin belgesine göre üç derleme kipi vardır. **Hata ayıklama** kipi geliştirme içindir: sıcak yeniden yükleme yalnız bu kipte çalışır ve doğrulama ifadeleri açıktır. **Profil** kipi başarım çözümlemesi içindir; belge bu kipin emülatörde ve simülatörde kapalı olduğunu söyler, çünkü oradaki davranış gerçek başarımı temsil etmez. **Sürüm** kipi dağıtım içindir: doğrulama ifadeleri kapalıdır, hata ayıklama bilgisi çıkarılır ve derleme hızlı açılış ile küçük paket için en iyilenir.

#### Platform Başına Çıktı

Her platform kendi paket biçimini ister. Android belgesi, uygulama mağazasının uygulama paketi biçimini tercih ettiğini söyler; paketi desteklemeyen mağazalar için işlemci mimarisi başına bölünmüş kurulum dosyaları önerilir. iOS derlemesi bir arşiv ve mağazaya yüklenecek bir paket üretir. Sürüm numarası iki platformda da tek yerde bildirilir: noktalı üç sayı sürümü, artı işaretinden sonraki sayı derleme numarasını verir.

```yaml
# Platform kodu: sürüm numarası
version: 1.4.0+12
```

`1.4.0` kullanıcıya görünen sürümdür, `12` derleme numarasıdır. iOS belgesi, mağazaya yapılan her yüklemenin benzersiz bir derleme numarası istediğini söyler.

#### İmzalama

İmzalama, paketin kimden geldiğini ve yolda değişmediğini kanıtlar. Android belgesi iki anahtarı ayırır: geliştiricinin yüklerken kullandığı **yükleme anahtarı** ve kullanıcıya ulaşan paketi imzalayan **uygulama imzalama anahtarı**. Yükleme anahtarının saklandığı dosya ve ona anahtar bilgisini veren yapılandırma dosyası gizli tutulur; belge ikisinin de herkese açık sürüm denetimine eklenmemesini ister. iOS tarafında imzalama ve hazırlık profilini geliştirme ortamı yönetebilir; belgeye göre bu ayar varsayılan olarak açıktır ve geliştirici kayıtlı hesabına bağlı bir takım seçer.

#### Sürüm Kanalı

İmzalı paket doğrudan herkese gitmek zorunda değildir. İki mağazanın yayın konsolu da paketi önce küçük bir gruba açan bir iç sınama kanalı sunar. Grup sürümü beğenirse paket inceleme için gönderilir ve onaydan sonra yayımlanır. İnceleme süresi bu derste yazılmaz.

Kanal, aynı derlemenin **kime** ulaştığını belirler. Hangi genişlikte çizildiğini belirlemez; onu hedefin ekranı ve ağaçtaki kısıtlar belirler.

#### Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

```dart
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
```

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde sürüm, iki ekran genişliğinde ölçülen bir ağaçtır.

#### İki Hedef

Sürüm iki hedef için ölçülür: 360 birimlik telefon ve 768 birimlik tablet. Ağaçta 900 birim isteyen büyük bir `Hero` görseli vardır. İşlev derleme kipini alır ve yalnız geçerli olup olmadığına bakar; kip ağacı değiştirmez.

```dart
// release_model.dart — surum hedefine gore ayni agaci kurar
import 'sizing.dart';

const modes = ['debug', 'profile', 'release'];
const targetOrder = ['telefon', 'tablet'];
const targets = {'telefon': 360, 'tablet': 768};

Part buildRelease(String mode) {
  if (!modes.contains(mode)) throw ArgumentError('bilinmeyen kip');
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', 500),
    leaf('Hero', 900),
    row('Toolbar', [
      leaf('Back', 48),
      flex('Search', 1, fill('Field')),
      leaf('Menu', 48),
    ]),
  ]));
}
```

İlk ölçüm telefonu tüm kayıtla basar.

```dart
// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  printSizes(measure(buildRelease('release'), targets['telefon']!));
}
```

```text
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
kirpilan: /Screen/Body/Banner genislik=328 kisit=0..328
kirpilan: /Screen/Body/Hero genislik=328 kisit=0..328
esnetilen: /Screen/Body/Toolbar genislik=328 kisit=0..328
secilen: /Screen/Body/Toolbar/Back genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Body/Toolbar/Search genislik=232 kisit=232..232
dayatilan: /Screen/Body/Toolbar/Search/Field genislik=232 kisit=232..232
secilen: /Screen/Body/Toolbar/Menu genislik=48 kisit=0..sonsuz
```

Ekrandan iç boşluk düşünce gövdeye 328..328 iner. 120 isteyen `Title` aralığa sığar, seçilen olur. 500 isteyen `Banner` ile 900 isteyen `Hero` 328'e çekilir, kırpılan olur. Araç çubuğu istek bildirmez ve yayılır, esnetilen olur.

Şimdi aynı ağaç tablet genişliğinde ölçülür.

```dart
// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  printSizes(measure(buildRelease('release'), targets['tablet']!));
}
```

```text
dayatilan: /Screen genislik=768 kisit=768..768
dayatilan: /Screen/Body genislik=736 kisit=736..736
secilen: /Screen/Body/Title genislik=120 kisit=0..736
secilen: /Screen/Body/Banner genislik=500 kisit=0..736
kirpilan: /Screen/Body/Hero genislik=736 kisit=0..736
esnetilen: /Screen/Body/Toolbar genislik=736 kisit=0..736
secilen: /Screen/Body/Toolbar/Back genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Body/Toolbar/Search genislik=640 kisit=640..640
dayatilan: /Screen/Body/Toolbar/Search/Field genislik=640 kisit=640..640
secilen: /Screen/Body/Toolbar/Menu genislik=48 kisit=0..sonsuz
```

Gövde 736 olur. `Banner` bu kez 500 ile aralığa sığar, seçilen olur. `Hero` yine aralığı aşar ve 736'ya çekilir. Ağaç da kural da aynıdır; değişen yalnız kısıttır.

#### İki Hedefin Sayımı

Sayım iki hedefte ayrı basılır.

```dart
// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  for (final name in targetOrder) {
    print('hedef=$name');
    countSizings(measure(buildRelease('release'), targets[name]!));
  }
}
```

```text
hedef=telefon
dayatilan: 4
secilen: 3
kirpilan: 2
esnetilen: 1
ebeveyn: 7
hedef=tablet
dayatilan: 4
secilen: 4
kirpilan: 1
esnetilen: 1
ebeveyn: 6
```

Telefonda kırpılan 2 düğüm vardır, tablette 1; ebeveyn 7'den 6'ya iner. Fark `Banner`'dan gelir. `Hero`'nun sınıfı iki hedefte de değişmez.

#### Derleme Kipi

Üç kip için aynı ağaç iki hedefte ölçülür; her kayıt hata ayıklama kipininkiyle karşılaştırılır.

```dart
// main.dart
import 'sizing.dart';
import 'release_model.dart';

List<String> lines(List<Sizing> log) =>
    [for (final entry in log) '${entry.sizing} ${entry.path} ${entry.width}'];

void main() {
  for (final name in targetOrder) {
    final base = lines(measure(buildRelease('debug'), targets[name]!));
    for (final mode in modes) {
      final same = lines(measure(buildRelease(mode), targets[name]!));
      print('hedef=$name kip=$mode kayit=${same.length} ayni=${same.join('|') == base.join('|')}');
    }
  }
}
```

```text
hedef=telefon kip=debug kayit=10 ayni=true
hedef=telefon kip=profile kayit=10 ayni=true
hedef=telefon kip=release kayit=10 ayni=true
hedef=tablet kip=debug kayit=10 ayni=true
hedef=tablet kip=profile kayit=10 ayni=true
hedef=tablet kip=release kayit=10 ayni=true
```

Her kipte ve her hedefte kayıt, hata ayıklama kipininkiyle aynıdır.

#### Yaygın Yanılgı

**Sürüm kipinde derlemek bileşenlerin genişliğini değiştirir.** Modelde değiştirmez: kuralın girdisi kısıt ile istektir, kip değil. Sınıfı değiştiren, hedefin ekran genişliğidir.

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

Son ölçüm yalnız `Hero` düğümünü iki hedefte basar.

```dart
// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  for (final name in targetOrder) {
    final log = measure(buildRelease('release'), targets[name]!);
    final hero = log.firstWhere((entry) => entry.path == '/Screen/Body/Hero');
    print('hedef=$name ${hero.sizing} genislik=${hero.width} kisit=${hero.limit.bounds()}');
  }
}
```

```text
hedef=telefon kirpilan genislik=328 kisit=0..328
hedef=tablet kirpilan genislik=736 kisit=0..736
```

Odak düğüm `/Screen/Body/Hero`. 900 birimlik istek telefonda `0..328`, tablette `0..736` aralığını aşar. İki hedefte de genişliği görselin isteği değil, aralığın üst sınırı belirler. Dersin boyutlanması bu yüzden kırpılandır.

**KY1.** Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde derleme kipi yalnız bir addır. Gerçek derleme, imzalama, paket biçimleri ve mağaza yüklemesi modelde yoktur; ayrıntısı çerçevenin ve mağazaların belgelerindedir.

#### Kapanış Tablosu

Tablo kursun on dokuz dersini sırayla toplar; boyutlanmalar kaynak derslerin özetlerinden alınmıştır.

| Ders | Ölçülen düzen | Boyutlanma | Sınırlayıcı ölçüm |
|---|---|---|---|
| Dart Temelleri | Başlık, afiş ve araç çubuğu | kırpılan | `Banner` 400 ister, 360 ekranda 328 alır |
| Eşzamansız Dart | Değer gelmeden ve geldikten sonra kurulan ağaç | seçilen | `Avatar` 200 gelince isteğini seçer |
| Bileşen Kavramı | İç boşluğun içinde kart | dayatılan | `Card` 200 ister, 328..328 alır |
| Durumsuz ve Durumlu Bileşenler | Kapalı ve açık arama kutusu | esnetilen | Açık `Search` 480'de 448, 320'de 288 olur |
| Üç Ağaç Modeli | Ortalanmış logo | seçilen | `Logo` iki ekranda da 96'yı seçer |
| Düzen Bileşenleri | Sıkı ve gevşek esnek çocuk | esnetilen | Gevşek çocuğun metni 360'ta 280, 480'de 400, 320'de 240 olur |
| Platform Bileşen Aileleri | Sabit yuvada eylem düğmesi | dayatılan | `Button` iki ailede de 120 alır |
| Durum Yönetimi Yaklaşımları | Başlıkta sayaç rozeti | seçilen | `Badge` 30'dan 66'ya büyür, seçilen kalır |
| Gezinme ve Yönlendirme | Ekran genişliğinde gezinme kabı | dayatılan | `Orders` 200 ister, 360 alır |
| Ağ ve Dizileştirme | Sunucudan gelen afiş | kırpılan | 1200 isteyen `Banner` 328'e çekilir |
| Yerel Depolama | Tercihle kurulan kart | esnetilen | `full` tercihinde `Card` 328 alır |
| Varlıklar ve Yazı Tipleri | Ölçekli düğme | kırpılan | Ölçek 2'de `Button` 344 ister, 328 alır |
| Animasyonlar | Beş karede ilerleyen panel | seçilen | Son karede `Panel` 240'ı seçer |
| Platform Kanalları | Gömülü platform görünümünün yuvası | dayatılan | `PlatformView` iletideki isteğe bakmadan 240 alır |
| Test Türleri | 400 ve 320 birimlik test yüzeyi | kırpılan | `Chart` 320'de 300 ister, 288 alır |
| Geliştirici Araçları | `Center` içindeki `Chip` | esnetilen | `Center` 328'e yayılır, `Chip` 80'i seçer |
| Başarım Ayarı | Sabit panelde sayaç | dayatılan | `Counter` 200 alır, değişen düğüm 0 |
| Paket Yönetimi | İki sürümlü derecelendirme | seçilen | `Rating` yeni sürümde 112'yi seçer |
| Derleme ve Yayın | Telefon ve tablet için aynı sürüm | kırpılan | `Hero` 900 ister, 328 ve 736 alır |

Dersler arasında dağılım şöyledir: dayatılan 5, seçilen 5, kırpılan 5, esnetilen 4. Toplam on dokuz derstir.

Bu sayım kursun açılışındaki sayımla aynı şey değildir. Açılışta çekirdeğin tek bir düzeni dokuz kayıtla basılmıştı: dayatılan 4, seçilen 3, kırpılan 1, esnetilen 1 ve ebeveyn 6. Çekirdek tek düzenin dokuz kaydını, tablo on dokuz dersi sayar.

#### Özet

- Bu dersin boyutlanması: kırpılan. Odak düğüm `/Screen/Body/Hero`'dur: 900 birim isteyen görsel telefonda 328'e, tablette 736'ya çekilir.
- Hata ayıklama kipi geliştirme, profil kipi başarım çözümlemesi, sürüm kipi dağıtım içindir; platform başına paket, imzalama ve sürüm kanalı sürümü kullanıcıya ulaştırır.
- Aynı sürüm iki hedefte ölçüldü: telefonda kırpılan 2, tablette 1. `Banner` telefonda kırpılırken tablette seçilen oldu, `Hero` iki hedefte de kırpılan kaldı.
- Üç derleme kipinde de kayıt hata ayıklama kipininkiyle aynı çıktı.
- Kapanış tablosunda on dokuz dersin dağılımı dayatılan 5, seçilen 5, kırpılan 5, esnetilen 4'tür.

#### Sonraki Adım

Bu ders kursun son dersidir. On dokuz ders boyunca tek soru sorduk: bu bileşenin genişliğini kim belirliyor. Sıradaki konu alanı çapraz platform üretim pratiğidir.

