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.0Luồ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
| Method | Mô tả | Khi nào dùng |
|---|---|---|
context.watch<T>() | Lắng nghe thay đổi, rebuild widget | Trong build() |
context.read<T>() | Chỉ đọc, không rebuild | Trong 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
- Tách logic ra Provider - Widget chỉ hiển thị UI
- Dùng
context.readcho callbacks - Tránh rebuild không cần thiết - Dùng Selector - Khi chỉ cần một phần của state
- Dispose resources - Override dispose() nếu cần
📝 Tóm tắt
| Component | Vai trò |
|---|---|
ChangeNotifier | Base class cho state |
ChangeNotifierProvider | Provide state cho widget tree |
context.watch | Lắng nghe và rebuild |
context.read | Đọc một lần |
Consumer | Widget alternative cho watch |
Selector | Tối ưu rebuild |
Last updated on