• 30.10.2026 03:13:02
  • İbrahim Gökyar

Bu makale, Flutter mobil uygulama geliştirme sürecinde granular state management ile gereksiz rebuild ve paint maliyetlerini nasıl ölçüp azalttığınızı gösterir; flutter eğitimi veya dart programlama eğitimi kapsamında uygulanabilir, ölçülebilir taktikler içerir.

Flutter state management: Granüler rebuild stratejileri ve profil rehberi

Neden granular state management - mekanizma ve ölçüm adımları

Flutter'da bir widget tree yeniden oluşturulduğunda widget nesneleri yeniden inşa edilir; Element ve RenderObject katmanları ise tip ve key eşleşirse yeniden kullanılabilir. Bunun sonucu: build() fonksiyonu sıklıkla çağrılır ama asıl maliyet build sırasında yapılan alokasyonlar ve paint/raster aşamasındaki GPU upload'lar veya expensive layout hesaplamalarıdır. Ölçüm için somut adımlar: cihazı profile moduyla başlatın ve bir baseline çekin:

flutter run --profile -d 
# ardından IDE veya terminalden DevTools'u açın ve Performance -> Start recording
Bu kayıt sırasında 20-50 frame yakalayın, ardından Timeline'da UI thread - Build ve Raster zamanlarını not edin. Bu sayede 'öncesi' verisi elde edersiniz.

Selective rebuild: setState'i daraltma örneği (ValueNotifier ile)

Aşağıdaki örnek, büyük bir liste içindeki tek bir öğeyi güncellemek için ana widget'ta setState kullanmanın yerine her öğeye atanmış ValueNotifier kullanmanın nasıl rebuild maliyetini düşürdüğünü gösterir. Profilleme için önce parent setState versiyonunu çalıştırıp DevTools'ta Performance kaydı alın; sonra bu versiyonu çalıştırıp aynı senaryoyu tekrar kaydederek Build time ve GC allocation farkını kıyaslayın.

// lib/main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(home: Home());
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State {
  // items yerine ayrı notifier listesi kullanıyoruz
  final List> counters =
      List.generate(1000, (_) => ValueNotifier(0));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Selective rebuild demo')),
      body: ListView.builder(
        itemCount: counters.length,
        itemBuilder: (context, i) => CounterTile(notifier: counters[i]),
      ),
    );
  }
}

class CounterTile extends StatelessWidget {
  final ValueNotifier notifier;
  const CounterTile({Key? key, required this.notifier}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // sadece bu widget yeniden build olacak
    return ListTile(
      title: ValueListenableBuilder(
        valueListenable: notifier,
        builder: (_, value, __) => Text('Count: $value'),
      ),
      trailing: IconButton(
        icon: Icon(Icons.add),
        onPressed: () => notifier.value++,
      ),
    );
  }
}

Neden işe yarıyor: parent.setState kullandığınızda parent'ın build()'u çalışır ve ListView.builder içindeki itemBuilder yeniden çağrılabilir (ve dolaylı olarak widget nesne grafiği yeniden oluşturulabilir). ValueListenableBuilder ile rebuild sınırlandırılmış olur; DevTools'ta 'Build' katmanında gözle görülür düşüş ve Memory -> Allocations'ta azalmanın olması beklenir. Ölçüm metodu: ilk kayıtta average build ms ve total allocated-bytes, ikinci kayıtta aynı senaryoyu tekrarlayın ve farkı yüzdesel olarak raporlayın.

RepaintBoundary, CustomPainter ve paint bütçesini azaltma

Paint aşaması genellikle GPU upload ve expensive compositing içerir. Uygulamada sık değişmeyen alt ağaçları RepaintBoundary içine almak, sadece değişen alt ağaçların paint edilmesini sağlar. Ayrıca CustomPainter kullanırken shouldRepaint kontrolünü doğru yazmak ve pahalı çizimleri bir ui.Picture ile cache'lemek önemli. Aşağıda re-paint'i azaltmak için örnek bir CustomPainter cache örneği ve RepaintBoundary kullanımını gösteren kod var:

// lib/painter_cache.dart
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

class CachedPainter extends CustomPainter {
  ui.Picture? _cachedPicture;
  final Size size;
  CachedPainter(this.size);

  @override
  void paint(Canvas canvas, Size s) {
    if (_cachedPicture == null) {
      final recorder = ui.PictureRecorder();
      final c = Canvas(recorder);
      // pahalı çizim
      final paint = Paint()..color = Colors.blue;
      c.drawRRect(RRect.fromRectAndRadius(Offset.zero & size, Radius.circular(8)), paint);
      // diğer expensive işlemler...
      _cachedPicture = recorder.endRecording();
    }
    canvas.drawPicture(_cachedPicture!);
  }

  @override
  bool shouldRepaint(covariant CachedPainter oldDelegate) => false;
}

// Widget içinde kullanımı:
// RepaintBoundary(
//   child: CustomPaint(size: Size(200,200), painter: CachedPainter(Size(200,200))),
// )

Nasıl ölçülür: GPU taraflı maliyeti görmek için skia trace alın. Komut:

flutter run --profile -d  --trace-skia
Ardından DevTools -> Performance'ta kayıtları inceleyin; Raster thread süreleri ile UI thread sürelerini karşılaştırın. RepaintBoundary sonrasında Raster süresinin ve GPU upload (texture upload) sayısının düştüğünü gözlemleyin.

Edge-case'ler: Key kullanımı, const ve element reuse tuzakları

Yanlış key kullanımı (ör. UniqueKey her build'te yeni key üretmek) elementlerin yeniden yaratılmasına yol açar; bu da RenderObject yeniden oluşturma ve dolayısıyla layout/paint maliyeti getirir. Doğru yaklaşım ValueKey veya item id tabanlı Key kullanmaktır. Örnek:

// Kötü: item widget'ı her rebuild'de yeniden create edilir
Widget itemBuilderBad(Item item) => ListTile(key: UniqueKey(), title: Text(item.title));

// İyi: id üzerinden element reuse sağlanır
Widget itemBuilderGood(Item item) => ListTile(key: ValueKey(item.id), title: Text(item.title));

Ayrıca const constructor kullanımı, immutable widget'lar için rebuild sırasında yeni nesne oluşturmayı engelleyerek GC yükünü azaltır. Mümkün olduğunca widget'larda const kullanın ve widget tree'de immutable leaf'leri const ile işaretleyin. DevTools -> Memory -> Allocation profilinde const kullanımı sonrası kısa süreli GC tetiklemelerinin azaldığını görürsünüz.

Profil-iş akışı: ölç, değiştir, karşılaştır ve CI'ye entegre et

Pratik ve tekrarlanabilir bir workflow olmazsa optimizasyonlar subjektif kalır. Önerilen adımlar: 1) Baseline kaydı alın:

flutter run --profile -d 
# DevTools'ta Performance -> Start recording -> reproduce scenario -> Stop
2) Değişiklik yapın (ör. setState -> ValueNotifier). 3) Tekrar ölçün. 4) Trace'leri export edip karşılaştırın (DevTools, JSON export). CI entegrasyonu için integration_test ve flutter driver kullanarak otomatik senaryo koşup profil verisi toplayabilirsiniz. Örnek komut:
flutter drive  --driver=test_driver/integration_test.dart  --target=integration_test/app_test.dart  --profile -d 
5) Metrikleri otomatik kıyaslamak için trace JSON'dan frame sürelerini parse eden küçük bir script yazın (DevTools trace formatı JSON olduğu için Python veya Node ile belirli event'leri (e.g. 'frame', 'Build', 'Raster') çekip before/after yüzdelik düşüşleri raporlayabilirsiniz).

Neden bu akış: manuel gözlem yanıltıcıdır; kesin kararlar almak için frame-level ve allocation-level sayısal karşılaştırma gerekir. Bu yaklaşım aynı zamanda bir flutter kursu veya flutter eğitimi sırasında öğrencilere reproducible perf testleri öğretmek için kullanışlıdır.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

flutter state management ile rebuild azaltmak için hangi araçlarla ölçüm yapmalıyım?

Ölçüm için Dart DevTools (Performance: Timeline, CPU profiler, Memory), flutter run --profile -d ile profile modunda çalışma, ve GPU ile ilgili ayrıntılar için flutter run --profile --trace-skia komutunu kullanın. Adım: 1) DevTools'u açın 2) Performance -> Start recording 3) senaryoyu çalıştırın 4) Stop ve 'Build'/'Raster' sürelerini, allocation histogramını kaydedin. Bu somut verilerle hangi state management değişikliğinin ne kadar etkili olduğunu nicel olarak gösterebilirsiniz.

flutter kursu veya flutter eğitimi verirken granular state management hangi örneklerle öğretilmeli?

Gerçek dünyada öğretilebilecek en etkili örnekler: büyük listelerde (1000+ öğe) parent setState vs ValueNotifier/Provider.select/riverpod.select karşılaştırması; ayrıca RepaintBoundary ve CustomPainter cache örnekleri. Eğitim sırasında öğrencilerden önce profil verisi almalarını isteyin (DevTools Performance) ve değişiklik sonrası karşılaştırma yaptırın; böylece 'görmeden inanma' tuzağını kırarsınız.

cross platform mobil uygulama geliştirme bağlamında Dart performansını CI'de nasıl sürekli test ederim?

CI'de otomatik performans testi için integration_test suite hazırlayın, ardından flutter drive ile profile modunda koşun:

flutter drive --driver=test_driver/driver.dart --target=integration_test/app_test.dart --profile
Test sırasında üretilen timeline/trace dosyalarını (DevTools export) saklayın ve baseline ile karşılaştıran bir script çalıştırın. Kritik metrikler: average frame build time, 90th percentile frame time, toplam allocated bytes. Eşikler aşılırsa pipeline fail eden basit kurallar koyabilirsiniz.

Dart programlama eğitimi sırasında öğrencilere hangi edge-case'leri göstermeliyim?

Öğrencilere gösterebileceğiniz önemli edge-case'ler: UniqueKey kullanımı yüzünden item yeniden yaratma; const eksikliği nedeniyle kısa ömürlü nesne alokasyonları; AnimatedList gibi API'lerin beklenmeyen layout çalıştırmaları; ve Text widget'larda font fallback nedeniyle paint gecikmeleri. Her bir durumda DevTools ile allocation trace ve frame timeline'ı gösterin.

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

📚 Bu konuyu Opendart Akademi'de öğrenin: Flutter eğitimi · Flutter eğitimi hakkında · YouTube video dersleri · Abone ol