İçeriğe geç
academia.sh

Ders 10 / 19

Ağ ve Dizileştirme

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.

Yayın Güncelleme

Bu derste

  • İstek katmanını, JSON çözmeyi ve model dönüşümünü birbirinden ayırır
  • Sunucudan gelen bir genişlik isteğinin kısıt aralığının dışında kaldığını ölçer
  • İsteğin kökeni değişse de boyutlanmanın kırpılan kaldığını gösterir
  • Aralığın içine düşen bir isteğin kırpılmayıp seçilen kaldığını gösterir
İçindekiler

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

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

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

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

// 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);
}
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çilendir. 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.

// 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()}');
  }
}
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.

// 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()}');
  }
}
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 kirpilandı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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat