• 7.09.2026 21:31:02
  • Admin Admin

Flutter state management katmanında gereksiz widget build'lerini ölçüp azaltın. Riverpod select, immutable durum güncellemeleri ve DevTools kare zamanlarıyla ölçülebilir bir build bütçesi oluşturun.

Flutter State Management'de Seçici Dinleme ile Build Bütçesi

Flutter state management için önce build bütçesini ölçün

Flutter mobil uygulama geliştirme sürecinde state değiştiğinde kaç widget'ın yeniden build edildiğini tahmin ederek değil, profile modunda ölçerek karar verin. Aynı cihazda, aynı veriyle ve en az 30 tekrar eden scroll veya sepete ekleme etkileşimiyle önce-sonra karşılaştırması yapın. flutter run --profile ile uygulamayı açın, DevTools Performance ekranında UI thread zaman çizelgesini inceleyin ve Flutter Inspector'daki Track Widget Rebuilds seçeneğiyle hangi widget'ların dirty olarak işaretlendiğini görün. Hedef, toplam kare süresini tek başına düşürmek değil; state değişiminden sonra p95 buildDuration değerini cihazınızın kare bütçesinin altında tutmaktır. 60 Hz ekranda bu bütçe yaklaşık 16.6 ms, 120 Hz ekranda yaklaşık 8.3 ms'dir; raster süresi de aynı toplam bütçeyi paylaşır.

Ölçümü sayısallaştırmak için FrameTiming kayıtlarını profile koşumunda toplayın. Bu kodu uygulama başlangıcında kurup yalnızca kontrollü benchmark akışında çalıştırın; her kareyi production analitik sistemine göndermek hem I/O üretir hem de sonucu bozar. Önce geniş kapsamlı ref.watch kullanan ekranın p95 buildDuration değerini kaydedin, ardından selector değişikliğinden sonra aynı senaryoyu yeniden çalıştırın. Örneğin bir ticari sepet ekranında 8.4 ms p95 build süresinin 1.7 ms seviyesine inmesi ancak aynı cihaz, aynı API yanıtı ve aynı etkileşim akışı altında anlamlı bir karşılaştırmadır.

class FrameProbe {
  final List<int> buildMicros = [];

  FrameProbe() {
    WidgetsBinding.instance.addTimingsCallback(_onTimings);
  }

  void _onTimings(List<FrameTiming> timings) {
    for (final timing in timings) {
      buildMicros.add(timing.buildDuration.inMicroseconds);
    }
  }

  void dispose() {
    WidgetsBinding.instance.removeTimingsCallback(_onTimings);
  }
}

Debug koşumunda daha gürültülü fakat hızlı bir ilk teşhis için debugPrintRebuildDirtyWidgets bayrağını kullanın. Bu bayrağı assert bloğuna koymak önemlidir: assert ifadeleri release derlemede kaldırılır; aksi halde build günlükleri gerçek kullanıcı akışında log I/O ve gereksiz gürültü oluşturur. Sık yapılan hata, yalnızca rebuild sayısına bakmaktır. Çok ucuz 40 const widget'ın build edilmesi, JSON parse eden veya büyük bir listeyi sıralayan tek bir build fonksiyonundan daha düşük maliyetli olabilir.

void main() {
  assert(() {
    debugPrintRebuildDirtyWidgets = true;
    debugProfileBuildsEnabled = true;
    return true;
  }());

  runApp(const ProviderScope(child: App()));
}

Riverpod select ile state değişimini alan düzeyinde daraltın

Riverpod kullanan bir ekranda ref.watch(cartProvider) ile CartState'in tamamını dinlerseniz, quantity değişimi, kupon durumu veya teslimat seçeneği değiştiğinde o Consumer builder yeniden çalışır. Bunun yerine widget'ın gerçekten çizdiği skaler değeri select ile izleyin. Aşağıdaki CartLine, başka bir SKU güncellendiğinde yeni bir Map üretilse bile seçicinin sonucu aynı int kaldığı için yeniden build edilmez. CartSummary ise tek tek alanlar yerine Dart record kullanır; record yapısal eşitlik uyguladığı için itemCount veya totalCents değişmedikçe build tetiklenmez.

class CartLine extends ConsumerWidget {
  const CartLine({super.key, required this.sku});
  final String sku;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final quantity = ref.watch(
      cartProvider.select((state) => state.quantities[sku] ?? 0),
    );
    return Text('$sku: $quantity');
  }
}

class CartSummary extends ConsumerWidget {
  const CartSummary({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final summary = ref.watch(
      cartProvider.select((state) => (state.itemCount, state.totalCents)),
    );
    return Text('${summary.$1} ürün - ${summary.$2} kuruş');
  }
}

select mekanizmasının kritik ayrıntısı karşılaştırmadır: seçicinin önceki ve sonraki sonucu == ile eşit kabul edilirse Consumer yeniden build edilmez. Bu nedenle select içinde her seferinde yeni List veya Map döndürmek çoğu zaman hatadır; Dart'ta List ve Map varsayılan olarak içerik yerine kimlik eşitliği kullanır. Örneğin state.items.where(...).toList() sonucu her state güncellemesinde yeni liste üretir ve içerik aynı olsa dahi dinleyiciyi uyandırır. Seçici sonucu olarak int, String, enum, record veya doğru yazılmış immutable value object döndürün; büyük türetilmiş koleksiyon gerekiyorsa onu provider seviyesinde memoize edin ve paket bağımlılığı olarak riverpod'un Provider yapısını kullanın.

Durum nesnesinin gerçekten immutable olması selector davranışını öngörülebilir kılar. Aşağıdaki güncelleme yalnızca değişen SKU için yeni Map oluşturur ve eski state içindeki Map'i değiştirmez. Yaygın bir production hatası, state içinde mutable bir Map tutup map[sku]++ yapmaktır: provider yeni state yayını yapmasa bile ekranda paylaşımlı veri değişebilir, testler geçerken UI rastgele bayat kalabilir.

@immutable
class CartState {
  const CartState({required this.quantities, required this.totalCents});
  final Map<String, int> quantities;
  final int totalCents;

  int get itemCount => quantities.values.fold(0, (a, b) => a + b);
}

class CartNotifier extends Notifier<CartState> {
  @override
  CartState build() => const CartState(quantities: {}, totalCents: 0);

  void increment(String sku, int unitPriceCents) {
    final next = {...state.quantities, sku: (state.quantities[sku] ?? 0) + 1};
    state = CartState(
      quantities: Map.unmodifiable(next),
      totalCents: state.totalCents + unitPriceCents,
    );
  }
}

Cross platform mobil uygulama geliştirmede rebuild sınırını widget ağacında kurun

Cross platform mobil uygulama geliştirme yapan ekiplerde aynı ekranın Android ve iOS cihazlarda farklı CPU sınırlarına çarpması, geniş Consumer alanlarını daha görünür hale getirir. Consumer'ı sayfanın köküne koyup tüm Column'u state ile üretmek yerine, state'e bağımlı en küçük görsel parçaya taşıyın. Örneğin CheckoutPage içinde adres formunu const ShippingAddressForm olarak sabit tutun, sepet toplamını ayrı bir ConsumerWidget olan CartSummary içinde okuyun. Flutter, aynı const widget örneğini gördüğünde ilgili alt ağacın update maliyetini atlayabilir; buna karşılık root Consumer her değişimde yeni closure ve widget yapılandırmaları üretir.

Dinamik satırlarda anahtar kullanın. ListView.builder içindeki CartLine'a ValueKey(sku) verilmezse sıralama veya silme sonrasında Element eşlemesi indeks üzerinden kayabilir; TextField, AnimationController veya satıra bağlı local state yanlış ürüne taşınabilir. Bu hata state management problemi gibi görünür ama aslında widget kimliği problemidir. DevTools Inspector'da bir ürünü sıraladıktan sonra selected widget'ın element kimliğini izleyerek doğrulayın.

ListView.builder(
  itemCount: skus.length,
  itemBuilder: (context, index) {
    final sku = skus[index];
    return CartLine(
      key: ValueKey(sku),
      sku: sku,
    );
  },
)

Birden fazla state alanını aynı anda okuyan widget için iki ayrı watch yerine tek record selector tercih edilebilir. İki bağımsız watch çoğu durumda Riverpod tarafından doğru çalışır, ancak bağımlılığı tek yerde belgelemek ve ara değerlerin widget build'i sırasında tutarlı bir snapshot olarak okunmasını kolaylaştırmak için select((s) => (s.currency, s.totalCents)) daha nettir. Buna karşılık API isteği, controller oluşturma veya Navigator çağrısını build içinde yapmayın. Selector daha az build üretse bile build'in yan etkisiz olma sözleşmesini değiştirmez; tek bir state değişimi framework tarafından birden fazla build denemesine yol açabilir.

Flutter eğitimi projelerinde regresyonu profile senaryosuyla kilitleyin

Bir flutter eğitimi veya dart programlama eğitimi örneğinde yalnızca UI testinin yeşil olması, build topolojisinin korunacağını garanti etmez. En az bir gerçekçi integration_test akışı oluşturun: 100 satırlık sepeti açın, aynı SKU'yu 20 kez artırın, farklı bir SKU'yu 20 kez artırın ve FrameProbe verisini dosyaya yazın. Bu akışı fiziksel veya sabit kapasiteli bir CI emülatöründe profile modunda çalıştırın. Klasik Flutter Driver altyapısı kullanan depolarda aşağıdaki komut çalışır; daha yeni test altyapısında eşdeğer profile entegrasyon koşumunu CI aracınıza bağlayın.

flutter drive   --driver=integration_test_driver/integration_test.dart   --target=integration_test/cart_scroll_benchmark.dart   --profile   -d emulator-5554

CI eşiğini ortalama yerine p95 ile koyun. Ortalama, ilk shader derlemesi, GC veya arka plandaki sistem aktivitesi yüzünden oluşan tekil sıçramaları gizleyebilir; p95 ise kullanıcıların sık gördüğü yavaş kare kümesini yakalar. Aynı benchmark sonucunda buildDuration p95, rasterDuration p95 ve 16.6 ms üstündeki kare sayısını saklayın. Kod incelemesinde örneğin build p95 2.1 ms'den 5.8 ms'ye çıktıysa, değişiklikte yeni bir geniş watch, pahalı formatlama veya liste kopyası olup olmadığını DevTools CPU Profiler ile açın.

Flutter kursu materyallerinde öğretilmesi gereken incelik şudur: selector her zaman doğru optimizasyon değildir. Seçici fonksiyonun kendisi O(n) filtreleme, locale formatlama veya kriptografik hash yapıyorsa bu maliyet her provider güncellemesinde ödenir. Bu tür türetilmiş veriyi ayrı bir Provider'da hesaplayın, giriş bağımlılıklarını daraltın ve sonucu immutable bir değer olarak yayınlayın. Böylece hem provider grafiği hem widget grafiği ayrı ayrı ölçülebilir hale gelir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management içinde ref.watch yerine select ne zaman kullanılmalı?

Widget yalnızca state'in bir alanını çiziyorsa select kullanın. Örneğin sepet satırı için state.quantities[sku] ?? 0 seçin. Seçiciden List veya Map döndürmeyin; kimlik eşitliği nedeniyle içerik aynı olsa bile rebuild oluşabilir. DevTools Track Widget Rebuilds ile değişiklik öncesi ve sonrası dirty widget sayısını karşılaştırın.

Flutter mobil uygulama geliştirme projesinde gereksiz rebuild nasıl ölçülür?

Uygulamayı flutter run --profile ile çalıştırın, DevTools Performance ekranında FrameTiming verisini inceleyin ve debug koşumunda debugPrintRebuildDirtyWidgets bayrağını açın. Aynı cihazda en az 30 tekrar eden etkileşim çalıştırıp p95 buildDuration, p95 rasterDuration ve bütçeyi aşan kare sayısını önce-sonra kaydedin.

Flutter eğitimi ve dart programlama eğitimi için immutable state neden gerekli?

Immutable state, provider'ın her değişimi açık bir yeni değer olarak yayınlamasını sağlar. Mutable Map üzerinde yerinde değişiklik yaparsanız selector sonucu veya widget ağacı beklenen bildirim olmadan bayat kalabilir. Map.unmodifiable ile dış mutasyonu engelleyin, güncellemede {...state.quantities, sku: nextValue} gibi kopya üretin.

Cross platform mobil uygulama geliştirmede Android ve iOS için ayrı rebuild optimizasyonu gerekir mi?

Önce ortak Dart widget ağacını optimize edin; geniş Consumer alanlarını daraltmak iki platformda da UI thread işini azaltır. Ardından aynı profile benchmark'ı hedef Android ve iOS cihazlarda ayrı çalıştırın. Farklı ekran yenileme hızları nedeniyle 60 Hz için 16.6 ms, 120 Hz için yaklaşık 8.3 ms kare bütçesiyle ayrı p95 eşikleri değerlendirin.

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