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



