Riverpod - State Management hiện đại
1. Giới thiệu
Riverpod là phiên bản cải tiến của Provider, khắc phục nhiều hạn chế:
- Compile-time safety
- Không cần BuildContext
- Testable
- Có nhiều loại providers
# pubspec.yaml
dependencies:
flutter_riverpod: ^2.4.02. Setup cơ bản
Wrap app với ProviderScope
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}Tạo Provider
// State đơn giản
final counterProvider = StateProvider<int>((ref) => 0);
// State từ async
final userProvider = FutureProvider<User>((ref) async {
return await api.fetchUser();
});Consume
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
}
}3. Các loại Provider
| Provider | Mục đích |
|---|---|
Provider | Giá trị read-only |
StateProvider | State primitive đơn giản |
StateNotifierProvider | State phức tạp với logic |
FutureProvider | Async data |
StreamProvider | Stream data |
ChangeNotifierProvider | Migrate từ Provider |
4. Provider - Read-only
final greetingProvider = Provider<String>((ref) {
return 'Hello, World!';
});
// Sử dụng
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}5. StateProvider - State đơn giản
final counterProvider = StateProvider<int>((ref) => 0);
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('$count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('+'),
),
],
);
}
}6. StateNotifier - State phức tạp
Định nghĩa
class Todo {
final String id, title;
final bool completed;
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,
);
}
}
class TodoNotifier extends StateNotifier<List<Todo>> {
TodoNotifier() : super([]);
void add(String title) {
state = [...state, Todo(id: DateTime.now().toString(), title: title)];
}
void toggle(String id) {
state = [
for (final todo in state)
if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,
];
}
void remove(String id) {
state = state.where((t) => t.id != id).toList();
}
}
final todoProvider = StateNotifierProvider<TodoNotifier, List<Todo>>((ref) {
return TodoNotifier();
});Sử dụng
class TodoPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoProvider);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.completed,
onChanged: (_) => ref.read(todoProvider.notifier).toggle(todo.id),
),
);
},
);
}
}7. FutureProvider - Async data
final userProvider = FutureProvider<User>((ref) async {
return await api.fetchCurrentUser();
});
class ProfilePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text('Hello, ${user.name}'),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}8. ref.watch vs ref.read
| Method | Rebuild | Khi nào dùng |
|---|---|---|
ref.watch() | Có | Trong build() |
ref.read() | Không | Trong callbacks |
@override
Widget build(BuildContext context, WidgetRef ref) {
// watch: rebuild khi provider thay đổi
final count = ref.watch(counterProvider);
return ElevatedButton(
// read: không cần rebuild
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('$count'),
);
}9. Provider với dependencies
final userIdProvider = StateProvider<int>((ref) => 1);
final userProvider = FutureProvider<User>((ref) async {
final userId = ref.watch(userIdProvider); // Phụ thuộc vào userId
return await api.fetchUser(userId);
});
// Khi userIdProvider thay đổi, userProvider tự động refresh10. Best Practices
- Khai báo providers ở global scope - top-level
- Dùng ref.watch trong build - ref.read trong callbacks
- Ưu tiên StateNotifier cho logic phức tạp
- Dùng .family cho parameterized providers
// Family provider
final userProvider = FutureProvider.family<User, int>((ref, userId) async {
return await api.fetchUser(userId);
});
// Sử dụng
ref.watch(userProvider(123));📝 Tóm tắt
| Loại | Khi nào dùng |
|---|---|
Provider | Constant, computed values |
StateProvider | Primitive state (int, bool, string) |
StateNotifierProvider | Complex state với logic |
FutureProvider | Async one-time data |
StreamProvider | Reactive data streams |
| Method | Mục đích |
|---|---|
ref.watch | Lắng nghe và rebuild |
ref.read | Đọc một lần |
ref.listen | Side effects |
Last updated on