Flutter: Riverpod 3 ile Modern State Management
Son güncelleme:
8 dk okuma

Riverpod, Provider paketinin yazarı Remi Rousselet tarafından geliştirilen bir state management çözümü. Provider'ın InheritedWidget üzerine kurulu olmasından kaynaklanan sınırlamaları gidermek için tasarlandı: eksik provider hatalarını derleme anına taşıyor, provider'ları birleştirmeyi kolaylaştırıyor ve test etmeyi sadeleştiriyor. Kurulum adımlarının ve "Hello world" örneklerinin tamamı Riverpod'un resmî başlangıç dokümanında yer alıyor.
Bu yazı Riverpod 3 (flutter_riverpod 3.x) ile yazıldı. İnternetteki eski örneklerin çoğu Riverpod 2 ile yazılmış ve StateProvider ya da StateNotifier kullanıyor. Bunlar Riverpod 3'te hâlâ var ama "legacy" (eski) olarak ayrı bir dosyaya taşındı; yazının sonunda eski kodu nasıl okuyup güncelleyeceğinizi ayrıca anlatıyorum.
State Management Neden Önemli?
Modern uygulamalar karmaşık veri akışlarına sahiptir:
- Kullanıcı girişi ve oturum yönetimi
- Anlık mesajlaşma ve bildirimler
- Çevrimdışı veri senkronizasyonu
- Çoklu API entegrasyonları
- Gerçek zamanlı güncellemeler
Aynı veriyi birden fazla ekranın okuduğu, bir verinin başka bir veriye bağlı olduğu bu tür durumlarda state'i düzenli bir yapıda tutmak gerekiyor.
Provider'ın Sınırlamaları
Provider iyi bir araç; ChangeNotifier, Consumer ve MultiProvider ile nasıl çalıştığını Provider yazısında anlattım. Riverpod'un dokümantasyonu, Provider'ın şu sınırlamalarını Riverpod'un çıkış sebebi olarak sayıyor:
1. Okuma için BuildContext gerekir
// Provider, InheritedWidget üzerine kurulu: okumak için context gerekir
final user = Provider.of<UserProvider>(context);Bir provider'ın create fonksiyonu içinde başka bir provider'ı context.watch ile okumak da güvenilir değildir.
2. Eksik provider hatası çalışma anında çıkar
// Provider ağaçta yoksa uygulama ÇALIŞIRKEN hata verir
final data = context.read<SomeProvider>(); // ProviderNotFoundExceptionBu hata özellikle yeniden düzenleme (refactor) sırasında sık görülür.
3. Provider birleştirmek zahmetlidir
Bir provider başka bir provider'a bağlı olduğunda ProxyProvider gibi yapılar gerekir ve kod hızla karmaşıklaşır.
4. Aynı tipte iki provider güvenilir çalışmaz
İki ayrı Provider<Item> tanımlarsanız InheritedWidget bunlardan yalnızca birini bulur.
5. Otomatik temizleme ve parametre desteği yok
Provider, bir değeri artık kimsenin dinlemediğini bilemez; bu yüzden kullanılmayan state'i kendiliğinden temizleyemez. Parametreye göre farklı değer üreten (örneğin ürün ID'sine göre ürün) bir provider tanımlamanın da hazır bir yolu yoktur.
Riverpod Bu Sorunları Nasıl Çözer?
✅ Provider'lar widget ağacına bağlı değil, ama bir ref gerekiyor
Riverpod'da provider'lar global değişken olarak tanımlanır; state ise uygulamanın en üstündeki ProviderScope içinde tutulur. Bir provider'ı okumak için BuildContext yerine bir ref kullanırsınız. Bu, "context olmadan her yerden erişim" anlamına gelmiyor; ref'i nereden aldığınız bağlama göre değişiyor:
// 1) Widget içinde: ConsumerWidget'ın verdiği WidgetRef
final user = ref.watch(userProvider);
// 2) Başka bir provider içinde: provider'ın kendi Ref'i
final greetingProvider = Provider<String>((ref) {
final name = ref.watch(userNameProvider);
return 'Merhaba $name';
});
// 3) Widget ağacı dışında (test, Dart betiği): ProviderContainer
final container = ProviderContainer();
final name = container.read(userNameProvider);
container.dispose();Sıradan bir servis sınıfının içinden provider okuyamazsınız; o sınıfın ya kendisi bir provider olmalı ya da ihtiyacı olan değeri dışarıdan almalı. Asıl kazanç, provider'ların birbirini ref.watch ile okuyabilmesi ve bunun için widget ağacına hiç ihtiyaç duymaması.
✅ Eksik provider derleme anında yakalanır
// Provider bir değişken: yanlış yazılmış ya da tanımlanmamışsa kod DERLENMEZ
final data = ref.watch(someProvider);Riverpod'da ProviderNotFoundException diye bir şey yok. Tek şart, uygulamanın kökünü ProviderScope ile sarmak; bunu unutursanız ilk okumada çalışma anında hata alırsınız.
✅ Kolay provider birleştirme
// Bir provider başka provider'ları izleyebilir
final userOrdersProvider = FutureProvider<List<Order>>((ref) async {
final user = await ref.watch(userProvider.future);
return fetchOrders(user.id);
});userProvider değiştiğinde userOrdersProvider kendiliğinden yeniden hesaplanır.
✅ Test edilebilirlik
// Testte gerçek API yerine sahte veri
final container = ProviderContainer.test(
overrides: [
userProvider.overrideWith((ref) async => fakeUser),
],
);ProviderContainer.test() Riverpod 3 ile geldi ve test bitince kendini temizliyor. Widget testlerinde aynı overrides listesini ProviderScope'a verebilirsiniz.
✅ autoDispose ile bellek yönetimi
.autoDispose ile tanımlanan bir provider, onu dinleyen kalmadığında state'ini temizler.
Riverpod 3'te Neler Değişti?
Riverpod 2'den gelen biri için en önemli değişiklikler şunlar (resmî "What's new" sayfası):
NotifierveAsyncNotifiertek önerilen yol.StateProvider,StateNotifierProviderveChangeNotifierProviderartıkpackage:flutter_riverpod/legacy.dartiçinden içe aktarılıyor ve yeni kodda önerilmiyor.- AutoDispose ve Family sınıfları birleşti.
AutoDisposeNotifier,FamilyNotifiergibi ayrı sınıflar yok; family parametresi Notifier'a constructor üzerinden geliyor. Refartık generic değil ve async işlemlerden sonra provider'ın hâlâ yaşayıp yaşamadığını kontrol etmek içinref.mountedvar.- Otomatik yeniden deneme: Hata veren provider'lar varsayılan olarak artan aralıklarla yeniden deneniyor. İstemiyorsanız
ProviderScope(retry: (retryCount, error) => null, ...)ile kapatabilirsiniz. - Görünmeyen widget'ların dinlemesi duraklatılıyor: Ekranda olmayan widget'lar provider'ları dinlemeyi geçici olarak bırakıyor.
- Tüm provider'lar değişikliği
==ile filtreliyor. Yeni değer eskisine eşitse dinleyiciler bilgilendirilmiyor. AsyncValuesealed oldu vevalueOrNullyerinevaluekullanılıyor.
Ne Zaman Riverpod Kullanmalısınız?
| Durum | Provider | Riverpod |
|---|---|---|
| Basit uygulamalar | ✅ | ✅ |
| Büyük projeler | ⚠️ | ✅ |
| Çok sayıda bağımlı provider | ❌ | ✅ |
| Yoğun test gereksinimleri | ⚠️ | ✅ |
| Widget ağacı dışından okuma | ❌ | ✅ (ProviderContainer ile) |
| Yeni projeler | ⚠️ | ✅ |
Genel kural: Yeni bir projeye başlıyorsanız veya birbirine bağlı çok sayıda state'iniz varsa Riverpod iyi bir tercih.
Provider vs Riverpod
| Özellik | Provider | Riverpod |
|---|---|---|
| Okuma | BuildContext ile |
ref ile (WidgetRef, Ref, ProviderContainer) |
| Eksik provider | Çalışma anında hata | Derleme hatası |
| Aynı tipte iki provider | Güvenilir değil | Sorunsuz |
| Provider birleştirme | ProxyProvider ile, zahmetli |
ref.watch ile |
| Otomatik temizleme | Yok | autoDispose |
| Parametreli provider | Yok | family |
Her ikisinden de önce, tek bir ekrana ait geçici veriyi widget'ın kendi state'inde tutmanın yeterli olduğunu unutmayın; initState ve dispose'un bu işi nasıl üstlendiğini yaşam döngüsü yazısında bulabilirsiniz.
⚠️ Önemli Not: Riverpod 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
En kolayı paketi terminalden eklemek; bu komut pub.dev'deki en güncel sürümü pubspec.yaml'a yazar:
flutter pub add flutter_riverpodElle eklemek isterseniz güncel sürümü flutter_riverpod'un pub.dev sayfasından kontrol edin. Bu yazı güncellendiğinde son sürüm 3.4.3'tü:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.4.3Kod üretimiyle (@riverpod anotasyonu) çalışmak isterseniz riverpod_annotation ve riverpod_generator paketleri de var. Bu yazıdaki örnekler kod üretimi olmadan, doğrudan yazılabilen sözdizimini kullanıyor.
Temel Kurulum
Uygulamanızı ProviderScope ile sarmalayın:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}Provider Türleri
1. Provider (Değişmeyen ya da türetilen değerler)
// Basit değer
final greetingProvider = Provider<String>((ref) {
return 'Merhaba Flutter!';
});
// Kullanım
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}2. NotifierProvider (Değişen state)
Riverpod 3'te değişen state için önerilen yol Notifier. Başlangıç değerini build() metodu döndürür, state'i değiştiren işlemler sınıfın metotlarıdır:
class Counter extends Notifier<int> {
@override
int build() => 0; // Başlangıç değeri
void increment() => state++;
void decrement() => state--;
void reset() => state = 0;
}
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
// Kullanım
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('$count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}ref.watch(counterProvider) değeri (int), ref.read(counterProvider.notifier) ise Counter nesnesini verir. State'i sınıfın dışından ref.read(counterProvider.notifier).state = 5 şeklinde değiştirmeyin: state setter'ı @protected işaretli ve analiz aracı uyarı verir. Değişikliği her zaman bir metot üzerinden yapın; böylece state'i kimin nasıl değiştirdiği tek bir sınıfta toplanır.
3. FutureProvider (Salt okunur async veri)
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
class UserPage extends ConsumerWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Hata: $error'),
data: (user) => Text('Merhaba ${user.name}'),
);
}
}AsyncValue Riverpod 3'te sealed bir sınıf olduğu için when yerine Dart'ın switch ifadesini de kullanabilirsiniz:
return switch (userAsync) {
AsyncData(:final value) => Text('Merhaba ${value.name}'),
AsyncError(:final error) => Text('Hata: $error'),
_ => const CircularProgressIndicator(),
};4. AsyncNotifierProvider (Async veri + değiştirme)
Veriyi sunucudan yükleyip sonra üzerinde işlem de yapacaksanız AsyncNotifier kullanın. build() burada bir Future döndürür:
class ProfileNotifier extends AsyncNotifier<User> {
@override
Future<User> build() => fetchUser();
Future<void> rename(String newName) async {
final current = await future;
state = const AsyncLoading();
state = await AsyncValue.guard(
() => saveUser(current.copyWith(name: newName)),
);
}
}
final profileProvider =
AsyncNotifierProvider<ProfileNotifier, User>(ProfileNotifier.new);
// Kullanım: ref.watch(profileProvider) bir AsyncValue<User> döndürür
// ref.read(profileProvider.notifier).rename('Ayşe');AsyncValue.guard, işlem hata fırlatırsa state'i AsyncError'a, başarılı olursa AsyncData'ya çevirir; try/catch yazmanıza gerek kalmaz.
5. StreamProvider (Akan veriler)
final messagesProvider = StreamProvider<List<Message>>((ref) {
return FirebaseFirestore.instance
.collection('messages')
.snapshots()
.map((snapshot) => snapshot.docs.map((doc) => Message.fromDoc(doc)).toList());
});
// Kullanım
class MessagesPage extends ConsumerWidget {
const MessagesPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final messagesAsync = ref.watch(messagesProvider);
return messagesAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Hata: $error'),
data: (messages) => ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) => ListTile(
title: Text(messages[index].text),
),
),
);
}
}ConsumerWidget vs Consumer
ConsumerWidget (Tüm widget)
class MyPage extends ConsumerWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}Consumer (Sadece bir bölüm)
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sayfa')), // Rebuild olmaz
body: Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('$count'); // Sadece bu rebuild olur
},
),
);
}
}initState, dispose ya da bir TextEditingController gerektiren ekranlarda ConsumerStatefulWidget ve ConsumerState kullanılır; ref orada State sınıfının bir alanı olarak hazır gelir.
ref.watch vs ref.read
// ✅ Build metodunda watch kullan (değişiklikleri dinler)
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
// ✅ Event handler'larda read kullan (tek seferlik okuma)
onPressed: () {
ref.read(counterProvider.notifier).increment();
}
// ❌ Build metodunda read kullanma
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.read(counterProvider); // Değer değişince güncellenmez
return Text('$count');
}Gerçek Dünya Örneği: Todo Uygulaması
// Todo modeli
class Todo {
final String id;
final String title;
final bool isCompleted;
const Todo({required this.id, required this.title, this.isCompleted = false});
Todo copyWith({String? title, bool? isCompleted}) {
return Todo(
id: id,
title: title ?? this.title,
isCompleted: isCompleted ?? this.isCompleted,
);
}
}
// Görev listesi
class TodoList extends Notifier<List<Todo>> {
@override
List<Todo> build() => [];
void add(String title) {
state = [
...state,
Todo(id: DateTime.now().microsecondsSinceEpoch.toString(), title: title),
];
}
void toggle(String id) {
state = [
for (final todo in state)
if (todo.id == id) todo.copyWith(isCompleted: !todo.isCompleted) else todo,
];
}
void remove(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
final todoListProvider = NotifierProvider<TodoList, List<Todo>>(TodoList.new);
// Filtre
enum TodoFilter { all, completed, uncompleted }
class TodoFilterNotifier extends Notifier<TodoFilter> {
@override
TodoFilter build() => TodoFilter.all;
void change(TodoFilter filter) => state = filter;
}
final todoFilterProvider =
NotifierProvider<TodoFilterNotifier, TodoFilter>(TodoFilterNotifier.new);
// Filtrelenmiş liste: iki provider'dan türetilir
final filteredTodosProvider = Provider<List<Todo>>((ref) {
final todos = ref.watch(todoListProvider);
final filter = ref.watch(todoFilterProvider);
return switch (filter) {
TodoFilter.completed => todos.where((todo) => todo.isCompleted).toList(),
TodoFilter.uncompleted => todos.where((todo) => !todo.isCompleted).toList(),
TodoFilter.all => todos,
};
});
// UI
class TodoPage extends ConsumerWidget {
const TodoPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(filteredTodosProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Görevler'),
actions: [
PopupMenuButton<TodoFilter>(
onSelected: (filter) {
ref.read(todoFilterProvider.notifier).change(filter);
},
itemBuilder: (context) => const [
PopupMenuItem(value: TodoFilter.all, child: Text('Tümü')),
PopupMenuItem(value: TodoFilter.completed, child: Text('Tamamlanan')),
PopupMenuItem(value: TodoFilter.uncompleted, child: Text('Tamamlanmayan')),
],
),
],
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
leading: Checkbox(
value: todo.isCompleted,
onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => ref.read(todoListProvider.notifier).remove(todo.id),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddDialog(context, ref),
child: const Icon(Icons.add),
),
);
}
void _showAddDialog(BuildContext context, WidgetRef ref) {
final controller = TextEditingController();
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Yeni Görev'),
content: TextField(
controller: controller,
decoration: const InputDecoration(hintText: 'Görev adı'),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('İptal'),
),
ElevatedButton(
onPressed: () {
if (controller.text.isNotEmpty) {
ref.read(todoListProvider.notifier).add(controller.text);
Navigator.pop(context);
}
},
child: const Text('Ekle'),
),
],
),
);
}
}Riverpod 2 ile yazılmış örneklerde filtre için genellikle StateProvider görürsünüz. Riverpod 3'te aynı işi yukarıdaki gibi küçük bir Notifier yapıyor; değişiklik change metodu üzerinden geçtiği için kodu okuyan biri filtrenin nereden değiştiğini tek bakışta görebiliyor.
Provider Birleştirme
// Kullanıcı provider'ı
final userProvider = FutureProvider<User>((ref) async {
return fetchUser();
});
// Kullanıcının siparişleri (userProvider'a bağlı)
final userOrdersProvider = FutureProvider<List<Order>>((ref) async {
final user = await ref.watch(userProvider.future);
return fetchOrders(user.id);
});
// Toplam sipariş tutarı
final totalOrderAmountProvider = Provider<double>((ref) {
final orders = ref.watch(userOrdersProvider).value ?? [];
return orders.fold(0.0, (sum, order) => sum + order.amount);
});Riverpod 3'te AsyncValue.value, veri yoksa (yükleniyor ya da hata durumunda) null döndürür; Riverpod 2'deki valueOrNull'ın yerini aldı.
Family Modifier
Parametreli provider oluşturma:
// Ürün detayı provider'ı
final productProvider = FutureProvider.family<Product, String>((ref, productId) async {
final response = await http.get(
Uri.parse('https://api.example.com/products/$productId'),
);
return Product.fromJson(jsonDecode(response.body));
});
// Kullanım
class ProductPage extends ConsumerWidget {
final String productId;
const ProductPage({super.key, required this.productId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productAsync = ref.watch(productProvider(productId));
return productAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Hata: $error'),
data: (product) => Text(product.name),
);
}
}Notifier ile family kullanırken parametre Riverpod 3'te constructor'dan gelir:
class ProductQuantity extends Notifier<int> {
ProductQuantity(this.productId);
final String productId;
@override
int build() => 1;
void increment() => state++;
}
final productQuantityProvider =
NotifierProvider.family<ProductQuantity, int, String>(ProductQuantity.new);
// Kullanım
// ref.watch(productQuantityProvider('urun-42'));
// ref.read(productQuantityProvider('urun-42').notifier).increment();Family parametreleri == ile karşılaştırılır. Her build'de yeni oluşturulan bir liste ya da =='i tanımlanmamış bir nesne verirseniz her seferinde yeni bir provider oluşur; parametre olarak String, int ya da =='i doğru tanımlanmış değer nesneleri kullanın.
AutoDispose Modifier
Kimse dinlemediğinde state'i temizlemek için .autoDispose eklenir. Arama kutusu gibi her tuşta yeniden çalışan bir istek için Riverpod dokümantasyonundaki "debounce" (bekletme) kalıbı şöyle:
final searchProvider =
FutureProvider.autoDispose.family<List<Product>, String>((ref, query) async {
// Kullanıcı yazmaya devam ederse bu provider dispose edilir
var didDispose = false;
ref.onDispose(() => didDispose = true);
await Future<void>.delayed(const Duration(milliseconds: 500));
if (didDispose) {
throw Exception('İptal edildi');
}
return searchProducts(query);
});Riverpod 3'te aynı kontrolü if (!ref.mounted) ... ile de yapabilirsiniz; ref.mounted, await sonrasında provider'ın hâlâ yaşayıp yaşamadığını söyler.
ref.listen ile Yan Etkiler
class MyPage extends ConsumerWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// State değiştiğinde yan etki çalıştır
ref.listen<int>(counterProvider, (previous, next) {
if (next == 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('10\'a ulaştınız!')),
);
}
});
return Text('${ref.watch(counterProvider)}');
}
}ref.invalidate ile Yenileme
// Provider'ı yeniden çalıştır
ElevatedButton(
onPressed: () {
ref.invalidate(userProvider);
},
child: const Text('Yenile'),
)En İyi Uygulamalar
1. Provider'ları global tanımlayın
// ✅ Dosyanın en üst seviyesinde
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
// ❌ Widget içinde tanımlamayın: her build'de yeni bir provider oluşur
class MyWidget extends ConsumerWidget {
final counterProvider = NotifierProvider<Counter, int>(Counter.new); // Yanlış!
}2. Küçük, odaklı provider'lar oluşturun
// ✅ Doğru: her provider tek bir iş yapar
final userProvider = FutureProvider<User>(...);
final userOrdersProvider = FutureProvider<List<Order>>(...);
final userBalanceProvider = Provider<double>(...);
// ❌ Yanlış: çok fazla iş yapan provider
final everythingProvider = FutureProvider<Everything>(...);3. select ile gereksiz rebuild'leri önleyin
// ✅ Sadece görev sayısı değişince rebuild olur
final count = ref.watch(todoListProvider.select((todos) => todos.length));
// ❌ Listedeki herhangi bir değişiklikte rebuild olur
final todos = ref.watch(todoListProvider);Eski Kod: StateProvider ve StateNotifier
Riverpod 2 ile yazılmış bir projeyi 3'e yükselttiğinizde StateProvider ve StateNotifierProvider kullanan dosyalar derlenmez; çünkü bu sınıflar ana içe aktarmadan çıkarıldı. İki seçeneğiniz var.
Hızlı çözüm: Eski sınıfları kullanan dosyalara legacy içe aktarmasını ekleyin. Kod olduğu gibi çalışmaya devam eder:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart'; // StateProvider, StateNotifierProvider, ChangeNotifierProviderKalıcı çözüm: Zamanla Notifier'a geçin. Dönüşüm çoğunlukla mekanik:
// Riverpod 2: StateNotifier
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
}
final counterProvider =
StateNotifierProvider<CounterNotifier, int>((ref) => CounterNotifier());
// Riverpod 3: Notifier
class Counter extends Notifier<int> {
@override
int build() => 0; // super(0) yerine build()
void increment() => state++;
}
final counterProvider = NotifierProvider<Counter, int>(Counter.new);Widget tarafındaki ref.watch(counterProvider) ve ref.read(counterProvider.notifier).increment() çağrıları değişmiyor. StateProvider kullanan yerlerde ise ref.read(provider.notifier).state = x yerine, yukarıdaki filtre örneğindeki gibi küçük bir Notifier ve bir metot yazıyorsunuz. Adım adım rehber için Riverpod 3 geçiş dokümanına bakabilirsiniz.
Özet
Riverpod: Provider'ın sınırlamalarını gidermek için tasarlanmış state management paketiProviderScope: Uygulamayı sarmalayan, state'in tutulduğu kapsamref: Provider okumak için gereken nesne (WidgetRef,Ref,ProviderContainer)NotifierProvider: Değişen senkron state içinAsyncNotifierProvider: Yüklenen ve değiştirilen async state içinFutureProvider/StreamProvider: Salt okunur async veriler içinref.watch: Build'de kullan (dinler)ref.read: Event'lerde kullan (dinlemez)family: Parametreli providerautoDispose: Otomatik temizlemelegacy.dart: EskiStateProviderveStateNotifierProviderkodu için
Riverpod, birbirine bağlı çok sayıda state'i olan Flutter projelerinde state yönetimi için güçlü bir çözüm. Tek bir asenkron isteği ekrana basmak gibi dar bir ihtiyaç için pakete hiç gerek olmayabilir; o durumda FutureBuilder yeterli olur.
Sık Sorulan Sorular
Provider'dan Riverpod'a geçmeli miyim?
Çalışan bir Provider kurulumunu sırf yenilik için değiştirmeye gerek yok. Yeni bir projeye başlıyorsanız veya ProviderNotFoundException ve provider birleştirme zorlukları canınızı sıkıyorsa Riverpod'a geçmek mantıklı; geçiş kademeli yapılabilir.
Riverpod 2 ile yazdığım kod Riverpod 3'te çalışır mı?
Büyük kısmı çalışır. StateProvider, StateNotifierProvider ve ChangeNotifierProvider kullanan dosyalara package:flutter_riverpod/legacy.dart içe aktarmasını eklemeniz gerekir. AutoDisposeNotifier ve FamilyNotifier gibi sınıflar kaldırıldığı için onları Notifier'a çevirmeniz, family parametresini constructor'a taşımanız gerekir.
ref.watch ile ref.read arasındaki fark nedir?
ref.watch değeri okur ve provider değiştiğinde widget'ı yeniden build eder; build metodunun içinde kullanılır. ref.read yalnızca o anki değeri alır, aboneliğe yol açmaz ve buton geri çağrıları gibi olay işleyicilerinde tercih edilir.
ConsumerWidget ile Consumer'ı ne zaman kullanmalıyım?
Widget'ın tamamı provider'a bağlıysa ConsumerWidget daha sade bir yazım sunar. Ekranın yalnızca küçük bir bölümü değişiyorsa o bölümü Consumer ile sarın; böylece yeniden build edilen ağaç küçük kalır.
İlgili Yazılar
Flutter: Provider ile State Management
Flutter'da Provider paketi ile state yönetimi, ChangeNotifier, Consumer, MultiProvider kullanımı ve best practices.
Flutter Yaşam Döngüsü (Life Cycle): StatefulWidget Rehberi
Flutter'da StatefulWidget yaşam döngüsü metodları ve uygulama yaşam döngüsü yönetimi. initState, dispose, didUpdateWidget ve setState kullanımı.
Flutter'da Navigator ile Sayfalar Arası Geçiş ve Veri Transferi
Flutter'da Navigator ile sayfa geçişi, veri aktarımı ve back stack yönetimi: push, pop, pushReplacement ve geri tuşu için güncel PopScope kullanımı.