Flutter uygulamalari buyudukce state management en kritik karar haline gelir. setState ile baslayan yolculuk, buyuk projelerde kabus haline donebilir. Riverpod, Flutter ekosisteminin en modern ve guvenilir state management cozumu olarak one cikiyor. Provider'in yaraticisi Remi Rousselet tarafindan sifirdan tasarlanan Riverpod, compile-time safety, test edilebilirlik ve olceklenebilirlik sunuyor.
Bu rehberde Riverpod 2.0'in tum ozelliklerini, gercek dunya ornekleriyle ve production-ready pattern'lerle kesfedeceksin. Kahveni hazirla, cunku bu yolculuk detayli olacak.
Not: Tum kod ornekleri Riverpod 2.x ve Flutter 3.x ile test edilmistir.
Icindekiler
- Neden Riverpod?
- Kurulum ve Yapilandirma
- Provider Turleri
- StateNotifier ve StateNotifierProvider
- AsyncValue ile Asenkron State
- Code Generation ile Riverpod
- Family ve AutoDispose
- Provider Scope ve Override
- Test Stratejileri
- Best Practices ve Anti-Pattern'ler
- Sonuc ve Oneriler
1. Neden Riverpod?
Flutter'da bircok state management cozumu var: setState, InheritedWidget, Provider, BLoC, GetX, MobX... Peki neden Riverpod?
State Management Karsilastirma Tablosu
Ozellik | setState | Provider | BLoC | Riverpod |
|---|---|---|---|---|
**Ogrenme Egrisi** | Cok Kolay | Kolay | Orta | Orta |
**Compile-time Safety** | Yok | Kismen | Evet | Tam |
**Test Edilebilirlik** | Zor | Orta | Iyi | Mukemmel |
**Code Generation** | Yok | Yok | Var | Var |
**Performans** | Kotu (rebuild) | Iyi | Iyi | Cok Iyi |
**BuildContext Bagimliligi** | Evet | Evet | Evet | Hayir |
**Global Erisim** | Yok | Kismen | Var | Tam |
**DevTools Desteigi** | Yok | Var | Var | Var |
Riverpod'un en buyuk avantaji BuildContext'e bagimli olmamasidir. Provider'da context olmadan provider'a erisamezsiniz — bu test yazarken ve service katmaninda buyuk sorun olusturur. Riverpod bunu tamamen cozuyor.
2. Kurulum ve Yapilandirma
yaml
1# pubspec.yaml2dependencies:3 flutter_riverpod: ^2.5.14 riverpod_annotation: ^2.3.55 6dev_dependencies:7 riverpod_generator: ^2.4.08 build_runner: ^2.4.89 riverpod_lint: ^2.3.10dart
1// main.dart2import 'package:flutter/material.dart';3import 'package:flutter_riverpod/flutter_riverpod.dart';4 5void main() {6 runApp(7 const ProviderScope(8 child: MyApp(),9 ),10 );11}12 13class MyApp extends StatelessWidget {14 const MyApp({super.key});15 16 @override17 Widget build(BuildContext context) {18 return MaterialApp(19 title: 'Riverpod Demo',20 home: const HomeScreen(),21 );22 }23}Onemli: ProviderScope, uygulamanin en ustunde olmalidir. Tum provider'lar bu scope icerisinde yasarlar.
3. Provider Turleri
Riverpod'da farkli ihtiyaclara yonelik provider turleri vardir:
3.1 Provider (Salt Okunur)
dart
1// Basit bir deger provider'i2final greetingProvider = Provider((ref) { 3 return 'Merhaba Flutter Dunyasi!';4});5 6// Baska bir provider'a bagimli provider7final formattedGreetingProvider = Provider((ref) { 8 final greeting = ref.watch(greetingProvider);9 return greeting.toUpperCase();10});11 12// Widget icinde kullanim13class GreetingWidget extends ConsumerWidget {14 const GreetingWidget({super.key});15 16 @override17 Widget build(BuildContext context, WidgetRef ref) {18 final greeting = ref.watch(formattedGreetingProvider);19 return Text(greeting);20 }21}3.2 StateProvider (Basit State)
dart
1// Counter ornegi2final counterProvider = StateProvider((ref) => 0); 3 4// Tema secimi5final themeProvider = StateProvider((ref) => ThemeMode.system); 6 7// Widget icinde kullanim8class CounterWidget extends ConsumerWidget {9 const CounterWidget({super.key});10 11 @override12 Widget build(BuildContext context, WidgetRef ref) {13 final count = ref.watch(counterProvider);14 15 return Column(16 children: [17 Text('Sayi: $count'),18 ElevatedButton(19 onPressed: () => ref.read(counterProvider.notifier).state++,20 child: const Text('Artir'),21 ),22 ElevatedButton(23 onPressed: () => ref.invalidate(counterProvider),24 child: const Text('Sifirla'),25 ),26 ],27 );28 }29}3.3 FutureProvider (Asenkron Tek Seferlik)
dart
1final userProvider = FutureProvider((ref) async { 2 final repository = ref.watch(userRepositoryProvider);3 return repository.getCurrentUser();4});5 6class UserProfile extends ConsumerWidget {7 const UserProfile({super.key});8 9 @override10 Widget build(BuildContext context, WidgetRef ref) {11 final userAsync = ref.watch(userProvider);12 13 return userAsync.when(14 data: (user) => Text('Hosgeldin, ${user.name}'),15 loading: () => const CircularProgressIndicator(),16 error: (error, stack) => Text('Hata: $error'),17 );18 }19}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
4. StateNotifier ve StateNotifierProvider
Karmasik state yonetimi icin StateNotifier kullanilir. Immutable state ile calisir ve Redux benzeri bir yaklasim sunar.
dart
1// State modeli (immutable)2class TodoListState {3 final List todos; 4 final bool isLoading;5 final String? errorMessage;6 7 const TodoListState({8 this.todos = const [],9 this.isLoading = false,10 this.errorMessage,11 });12 13 TodoListState copyWith({14 List? todos, 15 bool? isLoading,16 String? errorMessage,17 }) {18 return TodoListState(19 todos: todos ?? this.todos,20 isLoading: isLoading ?? this.isLoading,21 errorMessage: errorMessage ?? this.errorMessage,22 );23 }24}25 26// StateNotifier27class TodoListNotifier extends StateNotifier { 28 final TodoRepository _repository;29 30 TodoListNotifier(this._repository) : super(const TodoListState());31 32 Future loadTodos() async { 33 state = state.copyWith(isLoading: true, errorMessage: null);34 try {35 final todos = await _repository.fetchAll();36 state = state.copyWith(todos: todos, isLoading: false);37 } catch (e) {38 state = state.copyWith(isLoading: false, errorMessage: e.toString());39 }40 }41 42 void addTodo(String title) {43 final newTodo = Todo(44 id: DateTime.now().millisecondsSinceEpoch.toString(),45 title: title,46 isCompleted: false,47 );48 state = state.copyWith(todos: [...state.todos, newTodo]);49 }50 51 void toggleTodo(String id) {52 state = state.copyWith(53 todos: state.todos.map((todo) {54 if (todo.id == id) {55 return todo.copyWith(isCompleted: !todo.isCompleted);56 }57 return todo;58 }).toList(),59 );60 }61 62 void removeTodo(String id) {63 state = state.copyWith(64 todos: state.todos.where((todo) => todo.id != id).toList(),65 );66 }67}68 69// Provider tanimlamasi70final todoListProvider =71 StateNotifierProvider((ref) { 72 final repository = ref.watch(todoRepositoryProvider);73 return TodoListNotifier(repository);74});5. AsyncValue ile Asenkron State
AsyncValue, Riverpod'un en guclu ozelliklerinden biridir. Asenkron islemlerin uc durumunu (loading, data, error) type-safe sekilde yonetir.
dart
1final productsProvider = FutureProvider.autoDispose>((ref) async {
2 final api = ref.watch(apiClientProvider);3 final category = ref.watch(selectedCategoryProvider);4 return api.getProducts(category: category);5});6 7class ProductList extends ConsumerWidget {8 const ProductList({super.key});9 10 @override11 Widget build(BuildContext context, WidgetRef ref) {12 final productsAsync = ref.watch(productsProvider);13 14 return productsAsync.when(15 data: (products) {16 if (products.isEmpty) {17 return const Center(child: Text('Urun bulunamadi'));18 }19 return ListView.builder(20 itemCount: products.length,21 itemBuilder: (context, index) {22 return ProductCard(product: products[index]);23 },24 );25 },26 loading: () => const Center(child: CircularProgressIndicator()),27 error: (error, stackTrace) => Center(28 child: Column(29 mainAxisAlignment: MainAxisAlignment.center,30 children: [31 Text('Bir hata olustu: $error'),32 ElevatedButton(33 onPressed: () => ref.invalidate(productsProvider),34 child: const Text('Tekrar Dene'),35 ),36 ],37 ),38 ),39 );40 }41}6. Code Generation ile Riverpod
Riverpod 2.0 ile gelen code generation, boilerplate'i minimize eder:
dart
1import 'package:riverpod_annotation/riverpod_annotation.dart';2 3part 'providers.g.dart';4 5// Basit provider (Provider yerine)6@riverpod7String greeting(GreetingRef ref) {8 return 'Merhaba!';9}10 11// FutureProvider yerine12@riverpod13Future> users(UsersRef ref) async {
14 final repository = ref.watch(userRepositoryProvider);15 return repository.fetchAll();16}17 18// StateNotifierProvider yerine (Notifier kullanilir)19@riverpod20class TodoList extends _$TodoList {21 @override22 List build() { 23 return [];24 }25 26 void add(Todo todo) {27 state = [...state, todo];28 }29 30 void remove(String id) {31 state = state.where((t) => t.id != id).toList();32 }33 34 void toggle(String id) {35 state = state.map((t) {36 if (t.id == id) return t.copyWith(isCompleted: !t.isCompleted);37 return t;38 }).toList();39 }40}Ardindan code generation calistirilir:
bash
1dart run build_runner build --delete-conflicting-outputs7. Family ve AutoDispose
Family: Parametreli Provider
dart
1@riverpod2Future userById(UserByIdRef ref, String userId) async { 3 final repository = ref.watch(userRepositoryProvider);4 return repository.getUser(userId);5}6 7// Kullanim8class UserDetail extends ConsumerWidget {9 final String userId;10 const UserDetail({super.key, required this.userId});11 12 @override13 Widget build(BuildContext context, WidgetRef ref) {14 final userAsync = ref.watch(userByIdProvider(userId));15 return userAsync.when(16 data: (user) => Text(user.name),17 loading: () => const CircularProgressIndicator(),18 error: (e, s) => Text('Hata: $e'),19 );20 }21}AutoDispose Davranisi
Code generation ile tum provider'lar varsayilan olarak autoDispose'dur. Sayfadan ciktiginda state otomatik temizlenir — memory leak riski sifira iner.
8. Provider Scope ve Override
Test ve farkli konfigurasyonlar icin provider override cok gucludur:
dart
1// Override ile test2void main() {3 runApp(4 ProviderScope(5 overrides: [6 apiClientProvider.overrideWithValue(MockApiClient()),7 authProvider.overrideWith((ref) => MockAuthNotifier()),8 ],9 child: const MyApp(),10 ),11 );12}9. Test Stratejileri
dart
1import 'package:flutter_riverpod/flutter_riverpod.dart';2import 'package:flutter_test/flutter_test.dart';3 4void main() {5 group('TodoListNotifier', () {6 late ProviderContainer container;7 late MockTodoRepository mockRepository;8 9 setUp(() {10 mockRepository = MockTodoRepository();11 container = ProviderContainer(12 overrides: [13 todoRepositoryProvider.overrideWithValue(mockRepository),14 ],15 );16 });17 18 tearDown(() {19 container.dispose();20 });21 22 test('baslangitta bos liste olmali', () {23 final state = container.read(todoListProvider);24 expect(state.todos, isEmpty);25 expect(state.isLoading, isFalse);26 });27 28 test('todo eklendiginde liste guncellenmeli', () {29 container.read(todoListProvider.notifier).addTodo('Test Todo');30 final state = container.read(todoListProvider);31 expect(state.todos.length, 1);32 expect(state.todos.first.title, 'Test Todo');33 });34 });35}10. Best Practices ve Anti-Pattern'ler
Yapilmasi Gerekenler
- ref.watch: kullanarak reaktif bagimliliklar olusturun
- autoDispose: ile memory leak'leri onleyin
- Code generation: kullanarak boilerplate'i azaltin
- ProviderScope override: ile test yazin
Yapilmamasi Gerekenler
- build metodu icinde ref.read kullanmayin (reaktif olmaz)
- Provider icinde side-effect yapmayin (onTap handler'da ref.read kullanin)
- Her sey icin StateProvider kullanmayin (karmasik state icin Notifier tercih edin)
Sonuc ve Oneriler
Riverpod, Flutter state management dunyasinda oyun kurallarini degistiriyor. Compile-time safety, test edilebilirlik ve performans ucgeninde en iyi dengeyi sunan cozum.
Onerilen Yol Haritasi
Asama | Gorev | Sure |
|---|---|---|
1. Hafta | Provider ve StateProvider ogrenin | 3-5 gun |
2. Hafta | StateNotifier ve AsyncValue | 5-7 gun |
3. Hafta | Code generation ve Notifier | 3-5 gun |
4. Hafta | Test stratejileri ve production pattern'ler | 5-7 gun |
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

