Ahmet Balaman LogoAhmet Balaman

Flutter: Provider ile State Management

personAhmet Balaman
calendar_today
FlutterProviderState ManagementChangeNotifierConsumer

Provider, Flutter'da state management için en popüler ve Google tarafından önerilen paketlerden biridir. Widget tree boyunca veriyi paylaşmak ve değişiklikleri dinlemek için kullanılır.

State Nedir ve Neden Yönetilmeli?

State, uygulamanızın herhangi bir anda sahip olduğu veri durumudur. Örneğin:

  • Kullanıcının giriş yapıp yapmadığı
  • Sepetteki ürün sayısı
  • Tema tercihi (açık/koyu)
  • Form alanlarındaki değerler
  • API'den gelen veriler

State yönetimi olmadan, bu verileri widget'lar arasında paylaşmak ve güncellemeleri yansıtmak çok zorlaşır.

setState'in Sınırlamaları

Flutter'da basit state yönetimi için setState kullanılır. Ancak uygulama büyüdükçe ciddi sorunlarla karşılaşırsınız:

1. Prop Drilling Problemi

// Veriyi 5 seviye aşağıya geçirmek zorunda kalırsınız
GrandParent(
  child: Parent(
    child: Child(
      child: GrandChild(
        child: GreatGrandChild(
          userData: userData, // Tüm yol boyunca geçirilmeli!
        ),
      ),
    ),
  ),
)

2. Gereksiz Rebuild'ler

setState kullandığınızda, tüm widget yeniden build olur - sadece değişen kısım değil.

3. State Kaybı

Widget tree'de gezinirken state kaybolabilir. Sayfa değiştirip geri döndüğünüzde veriler sıfırlanır.

4. Test Edilemezlik

UI ve business logic iç içe geçtiğinde birim testleri yazmak zorlaşır.

5. Kod Tekrarı

Aynı veriyi birden fazla yerde kullanmak için sürekli parametre geçirmek gerekir.

Provider Bu Sorunları Nasıl Çözer?

✅ Merkezi State Yönetimi

Veriler tek bir yerden yönetilir, her yerden erişilebilir.

✅ Verimli Rebuild'ler

Sadece değişen widget'lar yeniden build edilir.

✅ Separation of Concerns

Business logic ve UI birbirinden ayrılır.

✅ Kolay Test Edilebilirlik

Provider'ları mock'layarak kolayca test yazabilirsiniz.

✅ Prop Drilling Yok

Veriye ihtiyaç duyan widget, doğrudan Provider'dan okur.

Ne Zaman Provider Kullanmalısınız?

Durum setState Provider
Tek widget'ta basit sayaç
Form validasyonu
Birden fazla sayfada kullanıcı bilgisi
Sepet yönetimi
Tema/Dil ayarları
API'den gelen veriler
Karmaşık form state'leri

Genel Kural: Eğer state birden fazla widget veya sayfa tarafından kullanılıyorsa, Provider düşünün.

Neden Provider?

Özellik setState Provider
Kapsam Tek widget Tüm widget tree
Karmaşıklık Basit Orta
Ölçeklenebilirlik Zor Kolay
Test edilebilirlik Zor Kolay
Prop drilling Gerekli Gereksiz

⚠️ Önemli Not: Provider harici bir pakettir ve DartPad'de çalışmaz. Örnekleri kendi bilgisayarınızda çalıştırmak için aşağıdaki kurulum adımlarını takip edin.

Kurulum

pubspec.yaml dosyanıza ekleyin:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1

Ardından terminalde çalıştırın:

flutter pub get

Temel Kavramlar

1. ChangeNotifier

State'i tutan ve değişiklikleri bildiren sınıf:

import 'package:flutter/foundation.dart';

class CounterProvider extends ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners(); // Dinleyicilere haber ver
  }
  
  void decrement() {
    _count--;
    notifyListeners();
  }
  
  void reset() {
    _count = 0;
    notifyListeners();
  }
}

2. ChangeNotifierProvider

Provider'ı widget tree'ye ekleme:

import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterProvider(),
      child: MyApp(),
    ),
  );
}

3. Consumer

State'i dinleme ve UI güncelleme:

Consumer<CounterProvider>(
  builder: (context, counter, child) {
    return Text(
      '${counter.count}',
      style: TextStyle(fontSize: 48),
    );
  },
)

4. context.read ve context.watch

// Okuma (değişiklikleri dinlemez) - Butonlarda kullan
context.read<CounterProvider>().increment();

// İzleme (değişiklikleri dinler) - Build metodunda kullan
final count = context.watch<CounterProvider>().count;

Tam Örnek: Sayaç Uygulaması

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 1. State sınıfı
class CounterProvider extends ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();
  }
  
  void decrement() {
    _count--;
    notifyListeners();
  }
}

// 2. Main
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterProvider(),
      child: MaterialApp(
        home: CounterPage(),
      ),
    ),
  );
}

// 3. UI
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider Sayaç')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Sayaç Değeri:'),
            Consumer<CounterProvider>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
                );
              },
            ),
            SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  onPressed: () => context.read<CounterProvider>().decrement(),
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 16),
                FloatingActionButton(
                  onPressed: () => context.read<CounterProvider>().increment(),
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

MultiProvider

Birden fazla provider kullanma:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CounterProvider()),
        ChangeNotifierProvider(create: (_) => ThemeProvider()),
        ChangeNotifierProvider(create: (_) => UserProvider()),
      ],
      child: MyApp(),
    ),
  );
}

Gerçek Dünya Örneği: Tema Değiştirme

// Theme Provider
class ThemeProvider extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.light;
  
  ThemeMode get themeMode => _themeMode;
  
  bool get isDarkMode => _themeMode == ThemeMode.dark;
  
  void toggleTheme() {
    _themeMode = isDarkMode ? ThemeMode.light : ThemeMode.dark;
    notifyListeners();
  }
}

// Main
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeProvider>(
      builder: (context, themeProvider, child) {
        return MaterialApp(
          themeMode: themeProvider.themeMode,
          theme: ThemeData.light(),
          darkTheme: ThemeData.dark(),
          home: HomePage(),
        );
      },
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final themeProvider = context.watch<ThemeProvider>();
    
    return Scaffold(
      appBar: AppBar(title: Text('Tema Ayarları')),
      body: Center(
        child: SwitchListTile(
          title: Text('Karanlık Mod'),
          value: themeProvider.isDarkMode,
          onChanged: (_) => themeProvider.toggleTheme(),
        ),
      ),
    );
  }
}

Gerçek Dünya Örneği: Alışveriş Sepeti

// Ürün modeli
class Product {
  final String id;
  final String name;
  final double price;
  
  Product({required this.id, required this.name, required this.price});
}

// Sepet Provider
class CartProvider extends ChangeNotifier {
  final List<Product> _items = [];
  
  List<Product> get items => List.unmodifiable(_items);
  
  int get itemCount => _items.length;
  
  double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
  
  void addItem(Product product) {
    _items.add(product);
    notifyListeners();
  }
  
  void removeItem(String productId) {
    _items.removeWhere((item) => item.id == productId);
    notifyListeners();
  }
  
  void clearCart() {
    _items.clear();
    notifyListeners();
  }
}

// Kullanım
class ProductCard extends StatelessWidget {
  final Product product;
  
  ProductCard({required this.product});
  
  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        title: Text(product.name),
        subtitle: Text('₺${product.price}'),
        trailing: IconButton(
          icon: Icon(Icons.add_shopping_cart),
          onPressed: () {
            context.read<CartProvider>().addItem(product);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('${product.name} sepete eklendi')),
            );
          },
        ),
      ),
    );
  }
}

// Sepet ikonu (AppBar'da)
class CartIcon extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        IconButton(
          icon: Icon(Icons.shopping_cart),
          onPressed: () {
            // Sepet sayfasına git
          },
        ),
        Positioned(
          right: 0,
          top: 0,
          child: Consumer<CartProvider>(
            builder: (context, cart, child) {
              return cart.itemCount > 0
                  ? CircleAvatar(
                      radius: 10,
                      backgroundColor: Colors.red,
                      child: Text(
                        '${cart.itemCount}',
                        style: TextStyle(fontSize: 12, color: Colors.white),
                      ),
                    )
                  : SizedBox.shrink();
            },
          ),
        ),
      ],
    );
  }
}

Selector ile Optimizasyon

Sadece belirli değişiklikleri dinleme:

// Tüm değişiklikleri dinler (verimsiz)
Consumer<CartProvider>(
  builder: (context, cart, child) {
    return Text('${cart.itemCount} ürün');
  },
)

// Sadece itemCount değişince rebuild (verimli)
Selector<CartProvider, int>(
  selector: (context, cart) => cart.itemCount,
  builder: (context, itemCount, child) {
    return Text('$itemCount ürün');
  },
)

Provider Türleri

Tür Kullanım
Provider Değişmeyen değerler
ChangeNotifierProvider Değişen state (en yaygın)
FutureProvider Async veriler
StreamProvider Stream veriler
ProxyProvider Bağımlı provider'lar

FutureProvider Örneği

final userProvider = FutureProvider<User>((ref) async {
  final response = await http.get(Uri.parse('https://api.example.com/user'));
  return User.fromJson(jsonDecode(response.body));
});

// Kullanım
FutureProvider<List<Product>>(
  create: (_) => fetchProducts(),
  initialData: [],
  child: ProductList(),
)

Best Practices

1. Provider'ı mümkün olduğunca yukarıda tanımlayın

// ✅ Doğru - Main'de
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => MyProvider(),
      child: MyApp(),
    ),
  );
}

// ❌ Yanlış - Derinlerde
class SomePage extends StatelessWidget {
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => MyProvider(), // Her build'de yeni instance!
      child: ...,
    );
  }
}

2. context.read vs context.watch

// ✅ Build metodunda watch kullan
Widget build(BuildContext context) {
  final count = context.watch<CounterProvider>().count;
  return Text('$count');
}

// ✅ Event handler'larda read kullan
onPressed: () {
  context.read<CounterProvider>().increment();
}

// ❌ Build metodunda read kullanma
Widget build(BuildContext context) {
  final count = context.read<CounterProvider>().count; // Güncellenme
  return Text('$count');
}

3. Consumer'ı mümkün olduğunca dar tutun

// ✅ Doğru - Sadece gerekli kısım rebuild olur
Scaffold(
  appBar: AppBar(title: Text('Sayfa')),
  body: Consumer<CounterProvider>(
    builder: (context, counter, child) {
      return Text('${counter.count}');
    },
  ),
)

// ❌ Yanlış - Tüm sayfa rebuild olur
Consumer<CounterProvider>(
  builder: (context, counter, child) {
    return Scaffold(
      appBar: AppBar(title: Text('Sayfa')),
      body: Text('${counter.count}'),
    );
  },
)

Özet

  • Provider: Flutter için önerilen state management çözümü
  • ChangeNotifier: State tutan ve bildiren sınıf
  • notifyListeners(): UI'ı güncellemek için çağrılır
  • Consumer: State değişikliklerini dinler
  • context.watch: Build'de kullan (dinler)
  • context.read: Event'lerde kullan (dinlemez)
  • MultiProvider: Birden fazla provider
  • Selector: Performans optimizasyonu

Provider, küçükten büyüğe tüm Flutter projelerinde kullanılabilecek güçlü ve esnek bir state management çözümüdür.

Yorumlar