Skip to Content

Provider - State Management cơ bản

1. Giới thiệu

Provider là giải pháp state management đơn giản và được Flutter team khuyến nghị cho hầu hết ứng dụng.

# pubspec.yaml dependencies: provider: ^6.1.0

Luồng dữ liệu (Data Flow)

Provider giúp luồng dữ liệu trở nên rõ ràng và một chiều (unidirectional):

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ChangeNotifier │ notify() │ Provider │ rebuild() │ Widget │ │ (Model) │ ──────────▶ │ (Root) │ ──────────▶ │ (UI) │ │ │ │ │ │ │ │ _count = 0 │ │ │ │ Text("0") │ └────────┬────────┘ └─────────────────┘ └────────┬────────┘ ▲ │ │ │ └───────────────────────────────────────────────────────────────┘ user interaction (onTap)

2. Cài đặt cơ bản

Tạo Model với ChangeNotifier

import 'package:flutter/foundation.dart'; class Counter extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); // Thông báo cho listeners } void decrement() { _count--; notifyListeners(); } }

Provide ở root

void main() { runApp( ChangeNotifierProvider( create: (context) => Counter(), child: MyApp(), ), ); }

Consume trong widget

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // Đọc giá trị Text('Count: ${context.watch<Counter>().count}'), // Gọi method ElevatedButton( onPressed: () => context.read<Counter>().increment(), child: Text('Increment'), ), ], ); } }

3. context.watch vs context.read

MethodMô tảKhi nào dùng
context.watch<T>()Lắng nghe thay đổi, rebuild widgetTrong build()
context.read<T>()Chỉ đọc, không rebuildTrong callbacks
@override Widget build(BuildContext context) { // watch: rebuild khi counter thay đổi final count = context.watch<Counter>().count; return Column( children: [ Text('$count'), ElevatedButton( // read: chỉ gọi method, không cần rebuild onPressed: () => context.read<Counter>().increment(), child: Text('+'), ), ], ); }

4. Consumer Widget

Alternative cho context.watch:

Consumer<Counter>( builder: (context, counter, child) { return Text('Count: ${counter.count}'); }, )

Với child optimization:

Consumer<Counter>( builder: (context, counter, child) { return Row( children: [ Text('${counter.count}'), child!, // Không rebuild ], ); }, child: const Icon(Icons.star), // const, không rebuild )

5. Selector - Rebuild tối ưu

Chỉ rebuild khi giá trị cụ thể thay đổi:

Selector<User, String>( selector: (context, user) => user.name, // Chỉ select name builder: (context, name, child) { return Text(name); // Chỉ rebuild khi name thay đổi }, )

6. Multiple Providers

void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => Counter()), ChangeNotifierProvider(create: (_) => ThemeProvider()), Provider(create: (_) => ApiService()), ], child: MyApp(), ), ); }

7. Ví dụ thực tế - Todo App

Model

class Todo { final String id; final String title; bool isCompleted; Todo({required this.id, required this.title, this.isCompleted = false}); } class TodoProvider extends ChangeNotifier { final List<Todo> _todos = []; List<Todo> get todos => _todos; List<Todo> get completedTodos => _todos.where((t) => t.isCompleted).toList(); int get remainingCount => _todos.where((t) => !t.isCompleted).length; void add(String title) { _todos.add(Todo(id: DateTime.now().toString(), title: title)); notifyListeners(); } void toggle(String id) { final todo = _todos.firstWhere((t) => t.id == id); todo.isCompleted = !todo.isCompleted; notifyListeners(); } void remove(String id) { _todos.removeWhere((t) => t.id == id); notifyListeners(); } }

UI

class TodoPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Todos (${context.watch<TodoProvider>().remainingCount} left)'), ), body: Consumer<TodoProvider>( builder: (context, provider, child) { return ListView.builder( itemCount: provider.todos.length, itemBuilder: (context, index) { final todo = provider.todos[index]; return ListTile( leading: Checkbox( value: todo.isCompleted, onChanged: (_) => provider.toggle(todo.id), ), title: Text(todo.title), trailing: IconButton( icon: Icon(Icons.delete), onPressed: () => provider.remove(todo.id), ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddDialog(context), child: Icon(Icons.add), ), ); } }

8. Best Practices

  1. Tách logic ra Provider - Widget chỉ hiển thị UI
  2. Dùng context.read cho callbacks - Tránh rebuild không cần thiết
  3. Dùng Selector - Khi chỉ cần một phần của state
  4. Dispose resources - Override dispose() nếu cần

📝 Tóm tắt

ComponentVai trò
ChangeNotifierBase class cho state
ChangeNotifierProviderProvide state cho widget tree
context.watchLắng nghe và rebuild
context.readĐọc một lần
ConsumerWidget alternative cho watch
SelectorTối ưu rebuild
Last updated on