• 24.09.2026 21:12:39
  • Admin Admin

Flutter state management katmanında hızlı WebSocket ve sensör akışlarını sınırlı bellekle işleyin. Kare başına birleştirme, kayıp politikası, sıra numarası kontrolü ve DevTools ile p95 kare süresi ölçümünü uygulayın.

Flutter State Management'de Backpressure ve Akış Taşmasını Önleme

Flutter state management katmanında akış bütçesini tanımlamak

Bir flutter mobil uygulama geliştirme projesinde sunucu saniyede 300 fiyat güncellemesi gönderirken ekranda 60 Hz'den fazla görünür değişiklik üretemezsiniz. Bu nedenle önce olay türlerini ayırın: fiyat gibi son durum taşıyan olaylarda 'latest wins', sipariş gerçekleşmesi gibi yan etkili olaylarda sıralı ve kayıpsız teslimat kullanın. Her akış için ölçülebilir bir sözleşme yazın: örneğin fiyat akışı en fazla 500 olay/saniye kabul eder, UI'ye en fazla kare başına bir bildirim gider, kuyruk üst sınırı 1000 olaydır ve sınır aşımında en eski fiyat olayı atılır.

Dart'ta StreamSubscription.pause() bir tüketiciyi durdurur, fakat bunu ağ seviyesinde gerçek backpressure sanmak yaygın bir hatadır. Özellikle broadcast Stream kaynaklarında pause edilen aboneliğin tamponu kaynak veya abonelik tarafında büyüyebilir. WebSocket sunucusu uygulama seviyesinde bir kredi protokolü kullanmıyorsa, istemcinin pause çağrısı uzak üreticinin gönderim hızını düşürmez. Dart DevTools Memory görünümünde bu hatayı, pause sonrası büyümeye devam eden _PendingEvents veya uygulamanızın kuyruk nesneleri ile doğrulayabilirsiniz.

  • Son durum: sembol, cihaz veya kullanıcı anahtarı başına son olayı saklayın.
  • Yan etkili komut: sıra numarası ile kalıcı kuyruğa yazın ve ACK gelmeden silmeyin.
  • Analitik: örnekleme oranını açıkça belirleyin; örneğin her 20 olaydan birini gönderin.
  • Taşma: drop-oldest, drop-newest veya bağlantıyı yeniden senkronize etme kararını olay tipine göre kodlayın.

Cross platform mobil uygulama geliştirme için kare bazlı birleştirme

Provider, Riverpod veya kendi ChangeNotifier katmanınızda her ağ olayında notifyListeners() çağırmak, aynı widget ağacını tek bir vsync aralığında yüzlerce kez kirli hale getirir. Aşağıdaki ChangeNotifier, aynı sembole gelen güncellemeleri Map içinde birleştirir ve SchedulerBinding ile en fazla bir sonraki karede yayınlar. Bu yaklaşım Android ve iOS zamanlayıcı çözünürlüğüne bağlı bir Timer yerine Flutter'ın frame scheduler'ına bağlandığı için cross platform mobil uygulama geliştirme senaryolarında aynı davranışı verir.

import 'dart:collection';
import 'package:flutter/scheduler.dart';
import 'package:flutter/foundation.dart';

class Quote {
  const Quote(this.symbol, this.price, this.sequence);
  final String symbol;
  final double price;
  final int sequence;
}

class QuoteStore extends ChangeNotifier {
  final Map<String, Quote> _pending = {};
  Map<String, Quote> _visible = const {};
  bool _frameScheduled = false;

  UnmodifiableMapView<String, Quote> get visible =>
      UnmodifiableMapView(_visible);

  void ingest(Quote quote) {
    _pending[quote.symbol] = quote; // Aynı anahtar icin latest wins.
    if (_frameScheduled) return;

    _frameScheduled = true;
    SchedulerBinding.instance.scheduleFrameCallback((_) {
      _frameScheduled = false;
      if (_pending.isEmpty) return;

      _visible = Map.unmodifiable({..._visible, ..._pending});
      _pending.clear();
      notifyListeners();
    });
  }
}

Buradaki incelik, _visible Map'ini yerinde değiştirmemektir. Yerinde mutasyon yapan bir Map ile seçici dinleme kullanan widget'lar eski ve yeni referansı eşit görüp güncellemeyi kaçırabilir. Provider kullanıyorsanız tüm ekranı watch etmek yerine sadece gereken alanı seçin:

final price = context.select<QuoteStore, double?>(
  (store) => store.visible['AAPL']?.price,
);
Bu seçim, AAPL dışındaki semboller değiştiğinde ilgili widget'ın build edilmesini engeller; ancak selector sonucunda yeni bir List veya Map üretirseniz referans eşitliği nedeniyle tersine her olayda build tetiklenebilir.

Flutter mobil uygulama geliştirme akışlarında sınırlı kuyruk ve kayıp politikası

Kare birleştirme UI bildirim sayısını düşürür, ancak asenkron bir disk yazıcısı veya şifre çözme adımı üreticiden yavaşsa bellek yine büyür. Aşağıdaki BoundedDrain sınıfı tüketici yavaşken yerel kuyruğu sabit tutar. Telemetri gibi son verinin daha değerli olduğu bir kanalda drop-oldest seçilmiştir; ödeme veya sohbet mesajı için aynı sınıfı kullanıp olay atmak yerine bağlantıyı durdurmalı ve sunucudan eksik aralığı yeniden istemelisiniz.

import 'dart:collection';

class BoundedDrain<T> {
  BoundedDrain({required this.maxSize, required this.consume});

  final int maxSize;
  final Future<void> Function(T event) consume;
  final Queue<T> _queue = ListQueue();
  bool _draining = false;
  int dropped = 0;

  void add(T event) {
    if (_queue.length == maxSize) {
      _queue.removeFirst();
      dropped++;
    }
    _queue.addLast(event);
    _drain();
  }

  Future<void> _drain() async {
    if (_draining) return;
    _draining = true;
    try {
      while (_queue.isNotEmpty) {
        await consume(_queue.removeFirst());
      }
    } finally {
      _draining = false;
      if (_queue.isNotEmpty) _drain();
    }
  }
}

Kuyruk doluluğunu yalnızca loglamak yeterli değildir. OpenTelemetry veya Sentry metric'leri ile queue_depth, dropped_total ve event_lag_ms değerlerini gönderin; event_lag_ms için DateTime.now().difference(event.createdAt) kullanın. Alarm eşiğini örneğin 60 saniyelik p95 queue_depth değerinin maxSize'in yüzde 70'ine ulaşması olarak belirleyin. Bu eşik, ani tekil sıçramalar yerine sürekli tüketim açığını yakalar.

Dart programlama eğitimi kapsamında sıralama, tekrar ve yeniden bağlanma

Dart programlama eğitimi içeriklerinde sık atlanan sorun, TCP bağlantısının paket sırasını korumasının yeniden bağlanmalar arasında uygulama olayı sırasını korumadığıdır. Sunucu her varlık için monoton sequence üretmeli; istemci de son uygulanan sequence değerini saklayıp eski veya yinelenen olayları reddetmelidir. Aksi halde bağlantı kesildiğinde hem eski soketten tamponlanmış hem de yeni soketten yeniden oynatılmış fiyat uygulanabilir.

class SequencedReducer {
  final Map<String, int> _lastSequence = {};

  bool shouldApply(Quote quote) {
    final previous = _lastSequence[quote.symbol] ?? -1;
    if (quote.sequence <= previous) return false;

    // previous + 1 degilse veri kaybi vardir.
    if (previous != -1 && quote.sequence != previous + 1) {
      requestSnapshot(quote.symbol, previous + 1, quote.sequence - 1);
      return false;
    }
    _lastSequence[quote.symbol] = quote.sequence;
    return true;
  }

  void requestSnapshot(String symbol, int from, int to) {
    // REST veya WebSocket RPC ile eksik araligi isteyin.
  }
}

Burada gap gördüğünüz anda yeni olayı doğrudan uygulamak cazip gelir, fakat toplam bakiye veya order book gibi türetilmiş durumlarda ara olaylar olmadan doğru sonuç üretilemez. Snapshot isteği devam ederken aynı varlık için gelen yeni olayları ayrı, sınırlı bir kuyrukta tutun. Snapshot'ın sequence değeri S ise yalnızca S'den büyük bekleyen olayları sıralı uygulayın. Bu kural, snapshot yanıtı ile canlı olayların yarıştığı edge case'i kapatır.

Flutter eğitimi projelerinde DevTools ile önce-sonra kare ölçümü

Bir flutter kursu laboratuvarında değişikliği doğrulamak için debug mod yerine profile mod kullanın:

flutter run --profile
flutter pub global run devtools
DevTools Performance ekranında aynı test senaryosunu iki kez kaydedin: 60 saniye boyunca saniyede 300 Quote üretin, 100 görünür sembol gösterin ve ekranı sabit tutun. İlk kayıtta her olayda notifyListeners(), ikinci kayıtta kare bazlı birleştirme kullanın. Karşılaştırmada build sayısı, UI thread p95 buildDuration, rasterDuration ve GC olaylarını ayrı raporlayın; yalnızca ortalama kare süresi kısa sıçramaları gizler.

Uygulamanın kendi ölçümünü de ekleyin; FrameTiming callback'i release'e yakın profile koşullarında p95 hesabı için ham veri üretir. Callback'i uygulama başlangıcında bir kez kaydedin, her build içinde kaydetmeyin.

final List<int> buildMicros = [];

void installFrameProbe() {
  WidgetsBinding.instance.addTimingsCallback((timings) {
    for (final timing in timings) {
      buildMicros.add(timing.buildDuration.inMicroseconds);
    }
    if (buildMicros.length > 600) {
      buildMicros.removeRange(0, buildMicros.length - 600);
    }
  });
}

int p95BuildMicros() {
  final sorted = [...buildMicros]..sort();
  if (sorted.isEmpty) return 0;
  return sorted[((sorted.length - 1) * 0.95).round()];
}
Hedefi cihaz sınıfına göre koyun: 60 Hz cihazda build ve raster işinin toplamı düzenli olarak yaklaşık 16.7 ms bütçesini aşarsa kaçırılmış kare oluşur. 120 Hz cihazda bütçe yaklaşık 8.3 ms'dir; bu yüzden aynı kodun farklı yenileme hızlarında ayrı ölçülmesi gerekir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management içinde StreamSubscription.pause() backpressure sağlar mı?

Hayır, otomatik olarak sağlamaz. pause() olay teslimini abonelikte durdurur; kaynağın, özellikle broadcast Stream veya uzak WebSocket üreticisinin üretimi azaltacağının garantisi yoktur. Bellek sınırı için BoundedDrain gibi yerel kuyruk limiti koyun, uzak üretici için ise kredi, ACK veya snapshot tabanlı uygulama protokolü tanımlayın.

Flutter mobil uygulama geliştirme sırasında çok hızlı WebSocket verisi UI'ye nasıl aktarılmalı?

Son durum verisini sembol veya varlık kimliğiyle Map içinde birleştirin ve SchedulerBinding.instance.scheduleFrameCallback ile kare başına tek notifyListeners() üretin. Yan etkili olayları bu yoldan geçirmeyin; sequence, ACK ve kalıcı kuyruk kullanın. Sonucu flutter run --profile ve DevTools Performance kaydıyla p95 buildDuration üzerinden karşılaştırın.

Cross platform mobil uygulama geliştirme için Timer mı frame callback mi kullanılmalı?

UI güncellemesini ekran yenilemesiyle hizalamak için frame callback kullanın. Sabit 16 ms Timer, cihazın 120 Hz ekranında iki frame kaçırabilir, arka plan kısıtlamalarında gecikebilir ve frame dışında gereksiz bildirim üretebilir. Timer yalnızca iş kuralınız gerçek zamanlı periyot gerektiriyorsa kullanılmalı; UI yayını yine frame sınırında birleştirilmelidir.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt