Skip to Content

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.0

2. 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

ProviderMục đích
ProviderGiá trị read-only
StateProviderState primitive đơn giản
StateNotifierProviderState phức tạp với logic
FutureProviderAsync data
StreamProviderStream data
ChangeNotifierProviderMigrate 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

MethodRebuildKhi nào dùng
ref.watch()Trong build()
ref.read()KhôngTrong 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 refresh

10. Best Practices

  1. Khai báo providers ở global scope - top-level
  2. Dùng ref.watch trong build - ref.read trong callbacks
  3. Ưu tiên StateNotifier cho logic phức tạp
  4. 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ạiKhi nào dùng
ProviderConstant, computed values
StateProviderPrimitive state (int, bool, string)
StateNotifierProviderComplex state với logic
FutureProviderAsync one-time data
StreamProviderReactive data streams
MethodMục đích
ref.watchLắng nghe và rebuild
ref.readĐọc một lần
ref.listenSide effects
Last updated on