• 24.09.2026 21:28:37
  • Admin Admin

Flutter mobil uygulama geliştirme projelerinde uzun listeleri ölçülebilir kare süresi, bellek ve raster maliyeti hedefleriyle tasarlayın. Bu rehber, DevTools ve Sliver API'leriyle gerçek darboğazları ayırır.

Flutter Mobil Uygulama Geliştirmede Sliver Performans Bütçesi

Flutter mobil uygulama geliştirmede liste için kare bütçesi koymak

Uzun bir akış ekranını optimize etmeye başlamadan önce hedefi sayısallaştırın: 60 Hz cihazda UI ve raster iş parçacıkları için ayrı ayrı 16.67 ms, 120 Hz cihazda 8.33 ms sınırı vardır. Flutter DevTools Performance ekranında Profile mode ile bir kaydı açın, "Track widget builds" ve "Track layouts" seçeneklerini etkinleştirin, ardından aynı hızda 3 kez aşağı kaydırın. Ortalama değil, zaman çizelgesindeki p95 UI frame ve p95 raster frame değerlerini kaydedin. Tek bir 35 ms kare, ortalamada görünmeyebilir ama kullanıcı tarafından takılma olarak görülür.

flutter run --profile --trace-skia
# DevTools Performance ekraninda ayni scroll senaryosunu kaydedin.
# Hedef ornegi:
# p95 UI frame: < 8 ms
# p95 raster frame: < 8 ms
# GC sayisi: scroll basina 0 veya 1

Bu ayrım önemlidir: UI frame yüksekse sorun çoğunlukla build, layout, Dart koleksiyon üretimi veya senkron veri dönüştürmedir. Raster frame yüksekse opacity katmanları, clip, gölge, resim örnekleme ya da gereksiz repaint araştırılır. Aynı kayıtta UI düşük ve raster yüksekken Provider, Bloc veya başka bir flutter state management çözümünü değiştirmek problemi çözmez; darboğaz Dart tarafında değil engine'e gönderilen sahne ağacındadır. Bu teşhis disiplini, flutter eğitimi içeriklerinde sık görülen 'her listeye cache ekle' yaklaşımından daha güvenilirdir.

SliverList ile görünür öğe kadar layout ve build üretmek

Bir CustomScrollView içinde tüm kartları children listesine açmak, öğeler ekrandan uzakta olsa bile Widget, Element ve RenderObject oluşturur. Bunun yerine SliverChildBuilderDelegate kullanın. Delegate yalnızca viewport, cacheExtent ve scroll yönündeki yakın çevre için çocuk ister. 10.000 ürünlük akışta fark, build sayısından çok kalıcı RenderBox sayısında ve GC basıncında görülür.

class ProductFeed extends StatelessWidget {
  const ProductFeed({super.key, required this.products});
  final List<Product> products;

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      cacheExtent: 600,
      slivers: [
        SliverList.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            final product = products[index];
            return ProductRow(
              key: ValueKey(product.id),
              product: product,
            );
          },
        ),
      ],
    );
  }
}

cacheExtent değerini körlemesine büyütmeyin. Örneğin satır yüksekliği 96 logical pixel ise 600, yaklaşık 6 ek satırın önceden build edilmesini ister. Ağır görsel decode yapan satırlarda 2.000 gibi bir değer, hızlı kaydırmada daha az boşluk hissi verse bile aynı anda canlı image cache girişlerini ve layout işini artırabilir. DevTools Memory ekranında baseline, 30 saniye aşağı kaydırma ve başlangıç konumuna dönüşten sonra heap'i karşılaştırın. Dönüş sonrası heap sürekli yükseliyorsa cacheExtent, keep-alive veya satırdaki controller yaşam döngüsü adaydır.

Sabit yükseklikli satırlarda SliverFixedExtentList veya ListView.builder içindeki itemExtent kullanın. Framework bu durumda her çocuğun boyutunu ölçerek toplam scroll extent hesaplamaz; indeks ile kaydırma ofseti doğrudan ilişkilidir. Değişken yükseklikli içerikte yanlış itemExtent vermek ise kesilen metin, hatalı hit test ve scroll sıçraması üretir. Bu edge case özellikle sunucudan gelen açıklama metni satır sayısını değiştirdiğinde ortaya çıkar.

RepaintBoundary ve katman maliyetini DevTools ile doğrulamak

Bir satırdaki fiyat, favori ikonu veya indirme ilerlemesi değiştiğinde tüm listenin yeniden boyanıp boyanmadığını Flutter Inspector'dan "Repaint Rainbow" ile görün. Renklerin her frame'de ekranın büyük kısmında değişmesi, repaint sınırının yanlış yerde olduğuna işaret eder. Android GPU rendering grafiği tek başına Flutter katman nedenini göstermez; önce DevTools Performance içindeki raster event'lerini ve Inspector overlay'ini birlikte kullanın.

class ProductRow extends StatelessWidget {
  const ProductRow({super.key, required this.product});
  final Product product;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 96,
      child: Row(
        children: [
          RepaintBoundary(
            child: ProductThumbnail(url: product.thumbnailUrl),
          ),
          const SizedBox(width: 12),
          Expanded(child: ProductMetadata(product: product)),
          FavoriteToggle(productId: product.id),
        ],
      ),
    );
  }
}

RepaintBoundary her yere eklenmez. Boundary, alt ağacın tekrar rasterize edilmesini üst ağaçtan ayırır; fakat ayrı bir layer ve texture yönetimi maliyeti de doğurabilir. Örneğin her satırın tamamı her scroll frame'inde transform altında hareket ediyorsa, satır başına boundary eklemek compositing işini artırabilir. Sadece thumbnail'ın içindeki ilerleme animasyonu değişiyor ve metin alanı sabitse thumbnail çevresindeki boundary'yi önce-sonra iki ayrı profile kaydıyla ölçün. Kabul kriteri örneği: p95 raster 11.2 ms'den 7.5 ms'ye inerken layer sayısı DevTools'ta kontrolsüz artmamalıdır.

Opacity widget'ı da sık yapılan bir tuzaktır. Opacity 0 ile 1 arasındaysa alt ağacı ara buffer'a çizdirebilir. Basit bir fade için AnimatedOpacity ölçülmeden yasaklanacak bir araç değildir, ancak kaydırılan binlerce satırda sürekli opaklık animasyonu varsa frame budget'ı tüketir. Görseli doğrudan daha şeffaf üretmek veya animasyonu görünür öğelerle sınırlamak, raster event'lerindeki saveLayer maliyetini azaltır.

flutter state management ile satır güncellemelerini indekslemek

Akışta tek bir ürünü favorilemek tüm ProductFeed widget'ını dinleyen bir state değişimi üretirse, görünür her satırın build metodu tekrar çalışabilir. flutter state management seçimi kadar dinleme kapsamı önemlidir. Riverpod kullanıyorsanız seçici provider ile yalnızca productId'si değişen satırı dinleyin; Bloc kullanıyorsanız aynı prensip BlocSelector ile uygulanır. Bu yaklaşım UI thread'deki build süresini azaltır, ancak ağ isteğini veya raster maliyetini otomatik olarak azaltmaz.

final productsProvider = StateNotifierProvider<ProductsController,
    Map<String, Product>>((ref) => ProductsController());

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isFavorite = ref.watch(productsProvider.select(
      (items) => items[productId]?.isFavorite ?? false,
    ));
    return IconButton(
      onPressed: () => ref.read(productsProvider.notifier)
          .toggleFavorite(productId),
      icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border),
    );
  }
}

Buradaki kritik ayrıntı immutable güncellemedir. Controller mevcut Map'i yerinde değiştirip aynı referansı state olarak yayımlarsa seçici dinleyici değişimi kaçırabilir veya karşılaştırma beklendiği gibi çalışmaz. Güncellemede yeni Map ve yeni Product üretin. DevTools'ta "Track widget builds" açıkken tek bir favori tıklamasından önce ve sonra build sayısını karşılaştırın: hedef, görünür 15 satırın değil yalnızca FavoriteToggle ve gerekiyorsa sayaç widget'ının build olmasıdır.

void toggleFavorite(String id) {
  final current = state[id];
  if (current == null) return;
  state = {
    ...state,
    id: current.copyWith(isFavorite: !current.isFavorite),
  };
}

Cross platform mobil uygulama geliştirme için gerçek cihaz matrisi

cross platform mobil uygulama geliştirme işinde aynı Dart kodunun aynı kare davranışını verdiğini varsaymayın. En az bir düşük bellekli Android fiziksel cihaz ve bir iOS fiziksel cihazda profile kaydı alın; emulator ve simulator GPU sürücüsü, termal durum ve disk önbelleği açısından üretim davranışını temsil etmez. Her cihaz için uygulamayı soğuk başlatın, ağ yanıtını fixture ile sabitleyin, 1.000 satırlık akışta sabit hızda 30 saniye scroll yapın ve p95 UI/raster frame, peak RSS ve dropped frame sayısını tabloya koyun.

# Envanterdeki fiziksel cihazlari gorun
flutter devices

# Belirli cihazda profile build ve calistirma
flutter run --profile -d DEVICE_ID

# Widget testinde sabit veri ile liste maliyetini tekrar edilebilir kilin
flutter test test/product_feed_scroll_test.dart

Resim içeren satırlarda cihaz yoğunluğu farkını da ölçün. 120x120 logical pixel alan için sunucudan 1600x1600 görsel indirmek, ekranda küçük görünse bile decode sırasında büyük bir bitmap ayırır. Image.network için cacheWidth ve cacheHeight değerlerini cihaz pixel ratio ile çarpılmış hedef boyuta göre verin. Örneğin 120 logical pixel ve 3.0 DPR için yaklaşık 360 piksel istenir. Aşırı küçük istek bulanıklık, aşırı büyük istek ise decode ve bellek maliyetidir; karar ekran görüntüsüyle değil DevTools Memory ve raster frame karşılaştırmasıyla verilmelidir.

Bu ölçüm yaklaşımı bir flutter kursu veya dart programlama eğitimi sonrasında ekip standardına dönüştürülebilir: pull request'e yalnızca 'liste akıcı' notu değil, cihaz modeli, senaryo, commit SHA, önce-sonra p95 değerleri ve kullanılan DevTools export'u ekleyin. Böylece cacheExtent ya da RepaintBoundary değişikliğinin gerçekten fayda mı sağladığı, yoksa yalnızca test cihazındaki sıcak image cache'ten mi etkilendiği denetlenebilir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter mobil uygulama geliştirmede ListView mi SliverList mi daha az bellek kullanır?

ListView.builder zaten sliver tabanlı lazy bir yol kullanır; belirleyici fark ListView ile CustomScrollView arasında değil, builder kullanıp kullanmamanızdadır. Başlık, grid ve listeyi tek scroll koordinatında birleştirecekseniz CustomScrollView + SliverList kullanın. DevTools Memory'de 1.000 öğelik children listesi ile SliverList.builder senaryosunu, 30 saniyelik scroll sonrası heap ve GC sayısıyla karşılaştırın.

flutter state management kullanırken tek satır güncellemesinde tüm liste neden build olur?

Liste kökünde state nesnesinin tamamını watch ediyorsanız her yeni state referansı ProductFeed build'ini tetikler. Riverpod select, BlocSelector veya eşdeğer bir seçiciyle satırı productId alanına bağlayın. DevTools'ta Track widget builds etkin halde bir toggle işlemi yapın; görünür tüm ProductRow'lar işaretleniyorsa dinleme sınırı h'l' liste kökündedir.

Flutter eğitimi kapsamında RepaintBoundary ne zaman eklenmeli?

Bir alt ağaç sık repaint oluyor, komşu içerik sabit kalıyor ve profile kaydında raster süresi yüksekse adaydır. Önce Repaint Rainbow ile alanı doğrulayın, sonra boundary öncesi ve sonrası p95 raster frame ile layer sayısını ölçün. Her liste satırına otomatik eklemek doğru değildir; çok sayıda boundary compositing ve texture maliyeti oluşturabilir.

Dart programlama eğitimi alan bir ekip Flutter scroll performansını nasıl regression test eder?

Widget testi yalnızca fonksiyonel scroll doğrulaması sağlar, gerçek raster maliyetini ölçmez. Testte sabit fixture ile 1.000 öğe üretin ve scroll davranışını koruyun; ardından fiziksel cihazda flutter run --profile ile aynı senaryonun DevTools timeline kaydını CI artefact'ı veya benchmark raporu olarak saklayın. Eşik olarak p95 UI ve raster sürelerini cihaz bazında karşılaştırın.

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