Flutter: Provider ile State Management
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.1Ardından terminalde çalıştırın:
flutter pub getTemel 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.