TB
← Tüm yazılar

Widget agaci ve rebuild stratejisi

Flutter uygulamalarinda performansin temeli widget agacini anlamaktir; rebuild kapsamini daraltmak gereksiz layout ve paint maliyetini dusurur.

Flutter gelistiricilerinin büyük cogunlugu widget kavramini bilir, ancak uretimde yasanan takilma ve gereksiz CPU tuketiminin önemli bir kismi widget agacinin nasil calistiginin yuzeyel anlasilmasindan kaynaklanir. Flutter, React benzeri bir deklaratif model kullanır; fakat alt tarafta uc ayri agac vardir: widget agaci, element agaci ve render agaci. Performansli bir rebuild stratejisi kurmak icin bu uc katmanı birlikte düşünmek gerekir.

Uc agac modeli

Widget agaci yapılandırma (configuration) tasir. Widget'lar hafiftir ve her build() cagrisinda yeniden oluşturulabilir. Element agaci, widget ile render object arasinda duran ve yaşam dongusunu yoneten katmandir. Ayni runtimeType ve key ile eslesen widget geldiginde element yeniden kullanılır; bu durumda alt agacin tamami yeniden olusturulmaz. Render agaci ise gerçek layout, paint ve hit-test islemlerini yapar.

Bir setState çağrısı yalnizca ilgili State nesnesinin build metodunu tetikler. Ancak donen widget agaci önceki frame ile karsilastirilir. Değişen dallar icin element güncellenir veya yeniden oluşturulur; render katmaninda ise yalnizca gerekli markNeedsLayout ve markNeedsPaint islemleri çalışır.

Build asamasinin maliyeti

Build pahali olabilir, fakat layout ve özellikle rasterization çoğu zaman daha pahalidir. Bu nedenle strateji su olmalidir: once build kapsamini daralt, sonra layout tetikleyen widget degisikliklerini azalt, en son paint sinirlarini ciz. Flutter DevTools Performance sekmesinde UI ve Raster thread'lerini ayri izlemek bu ayrimi somutlastirir.

Rebuild kapsamini daraltma teknikleri

const constructor kullanımı

const ile isaretlenen widget'lar derleme zamaninda canonical instance olarak saklanir. Parent rebuild oldugunda const alt agac tekrar olusturulmaz; element ve render tarafinda da stabil referans korunur. Özellikle statik metin, ikon ve bosluk widget'larinda const kullanmak düşük maliyetli ama geniş etkili bir optimizasyon sağlar.

class ProductBadge extends StatelessWidget {
  const ProductBadge({super.key, required this.label});
  final String label;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: DecoratedBox(
        decoration: const BoxDecoration(color: Color(0xFFEEF2FF)),
        child: Text(label),
      ),
    );
  }
}

State izolasyonu

Monolitik bir build metodu, küçük bir sayac degisiminde tüm ekrani yeniden kurar. Çözüm, değişen state'i alt widget'lara tasimaktir. StatefulWidget parcalara bolmek, ValueListenableBuilder veya AnimatedBuilder ile yalnizca bağımlı subtree'yi guncellemek rebuild maliyetini dusurur.

  • Global state degisimini ekranin tamamina yaymayin.
  • Form alanlarini ayri stateful bilesenlere ayirin.
  • Animasyon controller'larini yalnizca ilgili subtree'de dinleyin.
  • Büyük listelerde item bazli state tutun.

Key stratejileri

Key, element eslestirmesini kontrol eder. ValueKey, ObjectKey ve UniqueKey farklı senaryolarda kullanılır. Liste elemanlari yeniden siralandiginda veya filtre uygulandiginda doğru key, state kaybini ve gereksiz element yaratimini onler. Yanlış key kullanımı ise tam tersine pahali rebuild'lere yol acabilir.

Örneğin filtrelenmis bir listede her satira ValueKey(item.id) vermek, ayni kimlige sahip satirin state'ini korur. Geçici görsel bilesenler icin key eklemek genellikle gereksizdir; yalnizca stateful ve konum değişen widget'larda bilincli kullanın.

InheritedWidget ve rebuild propagasyonu

InheritedWidget, asagiya veri aktarirken dependOnInheritedWidgetOfExactType cagiran widget'lari isaretler. Ustteki deger degistiginde yalnizca bağımlı alt widget'lar rebuild olur. Theme, MediaQuery ve Provider tabanli kutuphaneler bu mekanizmayi kullanır. Kendi InheritedWidget'inizi yazarken updateShouldNotify metodunu gereksiz true donmeyecek şekilde tasarlayin; referans esitligi yerine anlamli alan karsilastirmasi yapin.

Selector ve granular dinleme

Provider ve Riverpod gibi cozumlerde tüm modeli dinlemek yerine secili alanlari dinlemek rebuild sayisini azaltir. Riverpod'da select, Provider'da Selector widget'i bu amaca hizmet eder. Özellikle 60 fps hedefleyen ekranlarda bu ayrim belirgindir.

RepaintBoundary ve layer yönetimi

Rebuild her zaman repaint anlamina gelmez; fakat animasyonlu bolgeler ust ekrani sik sik boyuyorsa RepaintBoundary fayda sağlar. Grafik, harita veya Lottie animasyonu iceren alanlari sinirlamak raster thread yukunu dusurur. Her widget'a körlemesine RepaintBoundary eklemek ise layer sayisini artirarak bellek tuketimini yukari cikar; ölçüm yapmadan uygulanmamali.

Liste ve grid performansi

ListView icinde tüm cocuklari tek seferde üretmek, uzun listelerde build ve layout maliyetini patlatir. ListView.builder yalnizca görünür ve yakın item'lari oluşturur. Ayrica itemExtent veya prototypeItem ile sabit yukseklik vermek scroll sirasinda layout hesaplarini azaltir.

  1. Veri kaynagini ScrollController ile sayfalayin.
  2. Agir gorselleri cacheWidth / cacheHeight ile sinirlayin.
  3. Item icinde gereksiz ClipRRect ve golge kombinasyonlarindan kaçın.
  4. AutomaticKeepAliveClientMixin yalnizca gerekli sekmelerde kullan.

Rebuild ölçüm ve profil cikarma

Flutter DevTools'ta Rebuild Stats ve Track Widget Rebuilds secenekleri hangi widget'in kac kez build oldugunu gösterir. Debug modda debugPrintRebuildDirtyWidgets acmak sorunlu alanlari hizla buldurur. Release profilinde ise flutter run --profile ile gerçek frame zamanlarini olcmek daha anlamlidir.

Tipik bir anti-pattern: ust seviye StreamBuilder veya FutureBuilder ile tüm sayfayi sarmak. Veri geldikce butun ekran rebuild olur. Çözüm, yükleme durumunu yerel bir alana indirmek veya önceki veriyi AsyncSnapshot uzerinden koruyarak yalnizca değişen bolumu guncellemektir.

Element.updateChild akisi

Framework, yeni widget'i mevcut element ile karsilastirir. RuntimeType ve key uyusuyorsa Element.update cagrilir; uyusmuyorsa eski element deactivate edilir ve yeni element mount edilir. Bu nedenle widget tipini kosullu dalda değiştirmek (örneğin ayni konumda bir frame Container, sonraki frame SizedBox) state kaybina ve pahali yeniden mount'a neden olur. Kosullu dallarda tutarlı widget tipi kullanmak önemlidir.

Pratik mimari oneriler

Büyük ekranlarda composition over inheritance ilkesi rebuild acisindan da gecerlidir. Presenter, view model veya controller katmanı UI'dan ayri tutulsa bile, UI tarafinda her veri degisiminin tüm agaci yenilememesi gerekir. BLoC/Cubit ile birlikte BlocBuilder'da buildWhen, Riverpod'da select, MobX'te reaktif alanlar bu filtrelemeyi sağlar.

Son olarak, erken optimizasyon yerine ölçüm-öncelikli yaklaşım benimseyin. Once kullanıcı akisini doğru kurun, DevTools ile darbogazlari tespit edin, ardindan const, key, state parcalama ve RepaintBoundary gibi hedefli mudahaleler yapin. Widget agacini anlamak, Flutter'da sürdürülebilir performansin temelidir; rebuild stratejisi ise bu anlayisin ekrana yansiyan pratik yuzudur.

SchedulerBinding ve frame pipeline da rebuild stratejisinin gorunmeyen parcasidir. Her vsync sinyalinde handleBeginFrame ve handleDrawFrame asamalari çalışır; build işlemi draw frame icinde gerçekleşir. Agir build islemleri bu pencereyi asarsa jank olusur. Bu yuzden build metodunda senkron agir JSON parse, büyük liste map'leme veya disk erisimi yapmak yerine bu isleri isolate veya önceki katmana tasimak gerekir.

LayoutBuilder parent constraint degistiginde alt agaci yeniden kurar. Orientation veya klavye acilmasi gibi durumlarda bu beklenen bir davranistir; fakat ic ice LayoutBuilder kullanımı rebuild dalgasini buyutur. Responsive tasarimda tek bir LayoutBuilder ile breakpoint belirleyip alt bilesenlere parametre gecmek daha kontrolludur.

GlobalKey kullanımı güçlü ama pahalidir; state'e disaridan erişim gerektiren formlar ve animasyon senaryolarinda anlamli olabilir. Ancak performans kritik listelerde GlobalKey ile her satiri isaretlemek element lookup maliyetini artirir. GlobalKey yalnizca gercekten imperative API gerektiren yerlerde tercih edilmelidir.

Impeller ve Skia gecisi sonrasi raster maliyeti cihazdan cihaza degisebilir; rebuild optimizasyonu yine gecerlidir cunku layout ve build CPU tarafinda kalir. Eski cihazlarda shader compilation jank'i ayri bir konu olsa da, gereksiz rebuild azaltmak tüm profillerde kazanctir.

Sonuç olarak widget agaci sadece teorik bir kavram degildir; DevTools timeline'inda somut olarak izlenebilir. Takiminiz icin rebuild checklist'i olusturun: const kullanımı, buildWhen/select filtreleri, builder listeler, olculmus RepaintBoundary ve key politikasi. Bu checklist'i code review'larda kontrol etmek, performans regresyonlarini erken yakalamanizi sağlar.

RenderObjectWidget ile dogrudan render katmanina inmek nadiren gerekir; fakat CustomPaint veya RenderBox alt sinifi yazarken rebuild ile layout/paint iliskisini bilmek zorunludur. CustomPaint'te painter degisiminde yalnizca repaint tetiklenir; boyut degisimi layout gerektirir.

AnimatedSwitcher ve Hero animasyonlari geçici olarak ek rebuild ve layer maliyeti getirir. Hero ucuslari sirasinda her iki route'un subtree'si canli kalir; bu yuzden agir listeleri Hero ile sarmalamaktan kacinmak frame drop riskini azaltir.