---
title: 'Yerel Depolama'
source: 'https://academia.sh/tr/kurslar/flutter/yerel-depolama'
course: Flutter
language: tr
updated: '2026-10-21T05:02:52+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tercih, anahtar–değer deposu ve gömülü veritabanı kavramlarını birbirinden ayırır'
    - 'Kaydedilmiş bir tercihin kartı esnetilen yaptığını ölçer'
    - 'Kayıtlı tercih yokken kartın seçilen kaldığını gösterir'
    - 'Ekranın okuduğu tercih bayatladığında kaydın değişmediğini gösterir'
---

# 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.
