Flutter'da Performance Optimizasyon Teknikleri

Flutter genellikle 60 veya 120 fps'de sorunsuz çalışır; ancak yanlış kullanım kalıpları yeniden çizimleri ve gereksiz build'leri tetikleyerek uygulamayı yavaşlatır. Bu yazıda ölçülebilir fark yaratan optimizasyon tekniklerini ele alıyoruz.

const Kullanımı: En Kolay Optimizasyon

Bir widget const olduğunda Flutter onu bir kez oluşturur ve üst widget yeniden build edilse bile bu widget'ı atlar:

// KÖTÜ — her build'de yeni instance
Widget build(BuildContext context) {
  return Column(
    children: [
      Padding(
        padding: EdgeInsets.all(16), // her seferinde yeni EdgeInsets
        child: Text('Başlık', style: TextStyle(fontSize: 20)),
      ),
      Icon(Icons.star, color: Colors.amber),
    ],
  );
}

// İYİ — const widget'lar atlanır
Widget build(BuildContext context) {
  return const Column(
    children: [
      Padding(
        padding: EdgeInsets.all(16),
        child: Text('Başlık', style: TextStyle(fontSize: 20)),
      ),
      Icon(Icons.star, color: Colors.amber),
    ],
  );
}

// Kuralı basit tut: statik içerik varsa const ekle
// flutter analyze --no-preamble kural ihlallerini gösterir
// pubspec.yaml → analyzer → rules: prefer_const_constructors: true

ListView Optimizasyonu: Builder Kullanın

// KÖTÜ — tüm item'lar aynı anda build edilir ve bellekte tutulur
ListView(
  children: products.map((p) => ProductCard(product: p)).toList(),
)

// İYİ — sadece görünen item'lar build edilir
ListView.builder(
  itemCount: products.length,
  itemExtent: 120, // bilinen yükseklik performansı artırır
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

// Büyük listeler için itemExtent veya prototypeItem zorunlu
ListView.builder(
  prototypeItem: const ProductCard(product: Product.placeholder),
  itemCount: products.length,
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

// Sliver versiyonu — CustomScrollView içinde
SliverList(
  delegate: SliverChildBuilderDelegate(
    (context, index) => ProductCard(product: products[index]),
    childCount: products.length,
  ),
)

setState Kapsamını Küçültmek

// KÖTÜ — küçük değişiklik tüm sayfayı yeniden build eder
class ProductPageBad extends StatefulWidget { ... }
class _State extends State<ProductPageBad> {
  bool _isFavorite = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // Büyük ve pahalı liste widget'ı
          const HeavyProductList(),
          // Sadece bu değişiyor ama tüm sayfa rebuild oluyor
          IconButton(
            icon: Icon(_isFavorite ? Icons.favorite : Icons.favorite_border),
            onPressed: () => setState(() => _isFavorite = !_isFavorite),
          ),
        ],
      ),
    );
  }
}

// İYİ — setState kapsamı küçük bir widget'a izole edilir
class FavoriteButton extends StatefulWidget {
  const FavoriteButton({super.key});
  @override State<FavoriteButton> createState() => _FavoriteButtonState();
}

class _FavoriteButtonState extends State<FavoriteButton> {
  bool _isFavorite = false;
  @override
  Widget build(BuildContext context) => IconButton(
    icon: Icon(_isFavorite ? Icons.favorite : Icons.favorite_border,
        color: _isFavorite ? Colors.red : null),
    onPressed: () => setState(() => _isFavorite = !_isFavorite),
  );
}

// Sayfa — HeavyProductList hiç rebuild olmaz
class ProductPage extends StatelessWidget {
  const ProductPage({super.key});
  @override
  Widget build(BuildContext context) => const Scaffold(
    body: Column(children: [HeavyProductList(), FavoriteButton()]),
  );
}

RepaintBoundary: Yeniden Çizim Sınırı

// Animasyonlu bir widget sık sık repaint oluyor ama etrafını etkilememeli
class AnimatedCounter extends StatelessWidget {
  final int count;
  const AnimatedCounter({super.key, required this.count});

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary( // Bu alt ağacı ayrı bir layer'da tut
      child: TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: count.toDouble()),
        duration: const Duration(milliseconds: 500),
        builder: (context, value, _) => Text(
          value.round().toString(),
          style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}

// Hangi widget'lara RepaintBoundary koymalı?
// - Sürekli animasyon içerenler
// - Sıkça güncellenen ama büyük bir ağacın küçük parçası olanlar
// - Kompleks custom painter'lar
// DevTools'daki "Highlight Repaints" özelliğiyle doğrulayın

compute() ile Ağır İşlemleri Isolate'e Taşımak

// UI thread'i bloke eden işlemler jank yaratır
// KÖTÜ — UI thread'de JSON parse etmek
Future<List<Product>> parseProducts(String jsonString) async {
  final list = jsonDecode(jsonString) as List; // büyük JSON'da donma
  return list.map((e) => Product.fromJson(e)).toList();
}

// İYİ — ayrı isolate'de çalıştır
Future<List<Product>> parseProductsInBackground(String jsonString) {
  return compute(_parseProductsIsolate, jsonString);
}

// Top-level fonksiyon olmalı
List<Product> _parseProductsIsolate(String jsonString) {
  final list = jsonDecode(jsonString) as List;
  return list.map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
}

// Kullanım — loading gösterirken arka planda parse et
Future<void> _loadProducts() async {
  final response = await apiClient.get('/products');
  final products = await parseProductsInBackground(response.data);
  emit(ProductsLoaded(products));
}

// compute() için uygun durumlar:
// - 50ms+ süren JSON parse işlemleri
// - Görüntü işleme
// - Şifreleme/hash işlemleri
// - Büyük liste sıralama/filtreleme

Image Önbellekleme

// cached_network_image: ^3.4.1
CachedNetworkImage(
  imageUrl: product.imageUrl,
  placeholder: (_, __) => const ShimmerPlaceholder(),
  errorWidget: (_, __, ___) => const Icon(Icons.broken_image),
  memCacheWidth: 300, // bellek kullanımını sınırla
  memCacheHeight: 300,
)

// Image boyutlarını widget boyutuna göre ayarla
Image.network(
  url,
  width: 100, height: 100,
  fit: BoxFit.cover,
  cacheWidth: 200, // 2x density için
)

Optimizasyona her zaman ölçüm ile başlayın. Flutter DevTools'un Performance ve CPU Profiler sekmelerini kullanarak gerçek darboğazları bulun. const eklemek ve ListView.builder kullanmak her projede işe yarar; RepaintBoundary ve isolate ise ölçümle doğrulandıktan sonra uygulanmalıdır.