Bỏ qua để đến nội dung

📦 Provider

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

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)

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();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
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'),
),
],
);
}
}

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('+'),
),
],
);
}

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
)

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
},
)

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

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();
}
}
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),
),
);
}
}

  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

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