Flutter uygulamalarında state management her zaman tartışmalı bir konu olmuştur. Provider, Bloc, GetX… Seçenekler çok fazla. Bu yazıda Riverpod’u inceleyeceğiz - Provider’ın yaratıcısı tarafından geliştirilen, daha güvenli ve esnek bir alternatif.
Neden Riverpod?
Riverpod, Provider’ın bilinen sorunlarını çözmek için tasarlandı:
- Compile-time güvenlik - Runtime hatalarını minimuma indirir
- BuildContext bağımsızlığı - Provider’lara her yerden erişebilirsiniz
- Test edilebilirlik - Mock ve override işlemleri çok kolay
- Kod üretimi - riverpod_generator ile boilerplate azalır
Kurulum
pubspec.yaml dosyasına ekleyin:
dependencies:
flutter_riverpod: ^2.4.9
riverpod_annotation: ^2.3.3
dev_dependencies:
riverpod_generator: ^2.3.9
build_runner: ^2.4.8
Temel Kavramlar
Provider Türleri
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
// Basit bir değer provider'ı
@riverpod
String appName(AppNameRef ref) {
return 'My Flutter App';
}
// Async provider - API çağrıları için
@riverpod
Future<List<User>> users(UsersRef ref) async {
final response = await http.get(Uri.parse('https://api.example.com/users'));
final data = jsonDecode(response.body) as List;
return data.map((json) => User.fromJson(json)).toList();
}
// Stream provider - gerçek zamanlı veriler için
@riverpod
Stream<int> counter(CounterRef ref) {
return Stream.periodic(
const Duration(seconds: 1),
(count) => count,
);
}
Notifier - State Yönetimi
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'todo_notifier.g.dart';
@immutable
class Todo {
final String id;
final String title;
final bool completed;
const Todo({
required this.id,
required this.title,
this.completed = false,
});
Todo copyWith({String? title, bool? completed}) {
return Todo(
id: id,
title: title ?? this.title,
completed: completed ?? this.completed,
);
}
}
@riverpod
class TodoList extends _$TodoList {
@override
List<Todo> build() {
// Başlangıç state'i
return [];
}
void addTodo(String title) {
state = [
...state,
Todo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
),
];
}
void toggleTodo(String id) {
state = state.map((todo) {
if (todo.id == id) {
return todo.copyWith(completed: !todo.completed);
}
return todo;
}).toList();
}
void removeTodo(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
Widget’larda Kullanım
ConsumerWidget
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TodoScreen extends ConsumerWidget {
const TodoScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
final completedCount = todos.where((t) => t.completed).length;
return Scaffold(
appBar: AppBar(
title: Text('Yapılacaklar ($completedCount/${todos.length})'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return TodoTile(todo: todo);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddDialog(context, ref),
child: const Icon(Icons.add),
),
);
}
void _showAddDialog(BuildContext context, WidgetRef ref) {
final controller = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Yeni Görev'),
content: TextField(
controller: controller,
decoration: const InputDecoration(
hintText: 'Görev adı...',
),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('İptal'),
),
FilledButton(
onPressed: () {
if (controller.text.isNotEmpty) {
ref.read(todoListProvider.notifier).addTodo(controller.text);
Navigator.pop(context);
}
},
child: const Text('Ekle'),
),
],
),
);
}
}
class TodoTile extends ConsumerWidget {
final Todo todo;
const TodoTile({super.key, required this.todo});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Dismissible(
key: Key(todo.id),
onDismissed: (_) {
ref.read(todoListProvider.notifier).removeTodo(todo.id);
},
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 16),
child: const Icon(Icons.delete, color: Colors.white),
),
child: ListTile(
leading: Checkbox(
value: todo.completed,
onChanged: (_) {
ref.read(todoListProvider.notifier).toggleTodo(todo.id);
},
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.completed
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
),
);
}
}
AsyncValue ile Yükleme Durumları
class UserListScreen extends ConsumerWidget {
const UserListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return Scaffold(
appBar: AppBar(title: const Text('Kullanıcılar')),
body: usersAsync.when(
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text('Hata: $error'),
const SizedBox(height: 16),
FilledButton(
onPressed: () => ref.invalidate(usersProvider),
child: const Text('Tekrar Dene'),
),
],
),
),
data: (users) => RefreshIndicator(
onRefresh: () => ref.refresh(usersProvider.future),
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(child: Text(user.name[0])),
title: Text(user.name),
subtitle: Text(user.email),
);
},
),
),
),
);
}
}
Provider’lar Arası İletişim
@riverpod
class FilterType extends _$FilterType {
@override
TodoFilter build() => TodoFilter.all;
void setFilter(TodoFilter filter) {
state = filter;
}
}
enum TodoFilter { all, active, completed }
@riverpod
List<Todo> filteredTodos(FilteredTodosRef ref) {
final todos = ref.watch(todoListProvider);
final filter = ref.watch(filterTypeProvider);
switch (filter) {
case TodoFilter.all:
return todos;
case TodoFilter.active:
return todos.where((t) => !t.completed).toList();
case TodoFilter.completed:
return todos.where((t) => t.completed).toList();
}
}
Test Yazma
Riverpod ile test yazmak çok kolay:
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
void main() {
group('TodoList', () {
test('başlangıçta boş olmalı', () {
final container = ProviderContainer();
addTearDown(container.dispose);
expect(container.read(todoListProvider), isEmpty);
});
test('todo eklenebilmeli', () {
final container = ProviderContainer();
addTearDown(container.dispose);
container.read(todoListProvider.notifier).addTodo('Test görevi');
final todos = container.read(todoListProvider);
expect(todos.length, 1);
expect(todos.first.title, 'Test görevi');
expect(todos.first.completed, false);
});
test('todo tamamlanabilmeli', () {
final container = ProviderContainer();
addTearDown(container.dispose);
container.read(todoListProvider.notifier).addTodo('Test');
final todoId = container.read(todoListProvider).first.id;
container.read(todoListProvider.notifier).toggleTodo(todoId);
expect(container.read(todoListProvider).first.completed, true);
});
});
}
Sonuç
Riverpod, Flutter’da state management için güçlü ve modern bir çözüm sunuyor. Özellikle:
- Tip güvenliği sayesinde hataları erken yakalarsınız
- Kod üretici ile boilerplate kodu minimuma inersiniz
- Test edilebilirlik ile kaliteli kod yazarsınız
Provider’dan geçiş yapmayı düşünüyorsanız, Riverpod kesinlikle değerlendirilmesi gereken bir seçenek.