Riverpod là một thư viện quản lý trạng thái phổ biến trong hệ sinh thái Flutter. Thư viện này cung cấp nhiều khái niệm, trong đó Provider và Notifier giữ vai trò đặc biệt quan trọng. Provider chịu trách nhiệm cung cấp dữ liệu hoặc một đối tượng cho các thành phần khác trong ứng dụng, còn Notifier đại diện cho một đối tượng có khả năng quản lý và thay đổi trạng thái theo một cách có tổ chức. Sự kết hợp giữa hai khái niệm này giúp ứng dụng tách biệt rõ ràng giữa giao diện, dữ liệu và logic nghiệp vụ.
1. Provider là gì?
Trong Riverpod, Provider có thể được hiểu là một đối tượng chịu trách nhiệm cung cấp dữ liệu hoặc một giá trị cho các thành phần khác trong ứng dụng. Provider không nhất thiết phải chứa trạng thái có thể thay đổi. Nó thường được dùng để cung cấp các giá trị cố định, tạo đối tượng dùng chung hoặc tính toán dữ liệu dựa trên những provider khác.
Một ví dụ đơn giản:
final appNameProvider = Provider<String>((ref) {
return 'Ứng dụng học Flutter';
});
Trong ví dụ này, appNameProvider cung cấp một chuỗi. Widget có thể đọc giá trị bằng ref.watch:
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appName = ref.watch(appNameProvider);
return Scaffold(
appBar: AppBar(title: Text(appName)),
body: const Center(child: Text('Trang chủ')),
);
}
}
Provider đóng vai trò như một nguồn dữ liệu trung gian. Widget không cần biết dữ liệu được tạo ra ở đâu hoặc logic bên trong provider hoạt động như thế nào. Điều này giúp giảm sự phụ thuộc giữa giao diện và logic ứng dụng.
2. Các loại Provider phổ biến
Riverpod cung cấp nhiều loại provider phục vụ những nhu cầu khác nhau.
Provider cơ bản
Provider phù hợp với dữ liệu chỉ đọc hoặc các đối tượng không cần thay đổi thường xuyên. Nó có thể cung cấp một hằng số, một service, một repository hoặc một giá trị được tính toán.
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
FutureProvider
FutureProvider dùng cho các tác vụ bất đồng bộ, chẳng hạn như gọi API hoặc đọc dữ liệu từ cơ sở dữ liệu.
final usersProvider = FutureProvider<List<User>>((ref) async {
final apiClient = ref.watch(apiClientProvider);
return apiClient.fetchUsers();
});
Khi sử dụng provider này, giao diện có thể xử lý ba trạng thái phổ biến: đang tải, thành công và lỗi.
StreamProvider
StreamProvider phù hợp với dữ liệu được cập nhật liên tục, chẳng hạn như tin nhắn thời gian thực, dữ liệu cảm biến hoặc trạng thái kết nối.
StateProvider
StateProvider có thể được dùng cho trạng thái đơn giản như một số nguyên, chuỗi hoặc giá trị boolean. Tuy nhiên, đối với logic phức tạp, nên sử dụng Notifier để mã nguồn rõ ràng và dễ mở rộng hơn.
3. Notifier là gì?
Notifier là một lớp chuyên dùng để quản lý trạng thái có thể thay đổi. Khác với Provider cơ bản chỉ cung cấp dữ liệu, Notifier thường chứa cả trạng thái hiện tại và các phương thức thay đổi trạng thái đó.
Ví dụ, một bộ đếm có thể được xây dựng như sau:
class CounterNotifier extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
void decrement() {
state--;
}
}
final counterProvider = NotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);
Widget sử dụng Notifier như sau:
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('$count')),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).increment();
},
child: const Icon(Icons.add),
),
);
}
}
Trong đoạn mã trên, ref.watch(counterProvider) dùng để lắng nghe giá trị trạng thái. Khi phương thức increment được gọi, trạng thái thay đổi và widget tự động cập nhật. Trong khi đó, ref.read(counterProvider.notifier) được dùng để lấy đối tượng Notifier nhằm gọi các phương thức xử lý hành động.
4. Mối quan hệ giữa Provider và Notifier
Provider và Notifier không phải là hai khái niệm hoàn toàn tách biệt. Trong Riverpod, Notifier thường được đăng ký và sử dụng thông qua một loại provider, chẳng hạn như NotifierProvider hoặc AsyncNotifierProvider.
Có thể hình dung mối quan hệ này như sau:
- Notifier chứa logic và quản lý trạng thái.
- NotifierProvider làm cầu nối để cung cấp trạng thái của Notifier cho giao diện.
- Widget đọc trạng thái từ provider và gửi hành động đến Notifier.
Mô hình này tạo ra sự phân tách trách nhiệm rõ ràng. Giao diện chịu trách nhiệm hiển thị, Notifier chịu trách nhiệm xử lý logic và provider chịu trách nhiệm kết nối các phần với nhau.
5. AsyncNotifier và trạng thái bất đồng bộ
Trong các ứng dụng thực tế, dữ liệu thường được tải từ máy chủ hoặc cơ sở dữ liệu. Vì vậy, trạng thái không chỉ có một giá trị đơn giản mà còn bao gồm trạng thái đang tải và trạng thái lỗi. AsyncNotifier được thiết kế cho trường hợp này.
class UserNotifier extends AsyncNotifier<User?> {
@override
Future<User?> build() async {
return null;
}
Future<void> login(String email, String password) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final repository = ref.read(userRepositoryProvider);
return repository.login(email, password);
});
}
}
final userProvider = AsyncNotifierProvider<UserNotifier, User?>(
UserNotifier.new,
);
AsyncNotifier giúp tập trung logic bất đồng bộ vào một nơi. Thay vì tự tạo nhiều biến như isLoading, errorMessage và user, lập trình viên có thể sử dụng AsyncValue để biểu diễn các trạng thái tải dữ liệu một cách nhất quán.
Các trạng thái thường gặp gồm:
- AsyncLoading: dữ liệu đang được tải.
- AsyncData: dữ liệu đã tải thành công.
- AsyncError: quá trình tải dữ liệu xảy ra lỗi.
Cách tiếp cận này làm cho giao diện dễ đọc hơn và giảm nguy cơ bỏ sót trường hợp xử lý lỗi.
6. So sánh Provider và Notifier
| Tiêu chí | Provider | Notifier |
|---|---|---|
| Mục đích chính | Cung cấp dữ liệu hoặc đối tượng | Quản lý trạng thái có thể thay đổi |
| Chứa phương thức thay đổi trạng thái | Thường không | Có |
| Phù hợp với dữ liệu chỉ đọc | Rất phù hợp | Có thể, nhưng thường không cần thiết |
| Phù hợp với logic nghiệp vụ | Hạn chế | Phù hợp |
| Khả năng mở rộng | Tốt với logic đơn giản | Tốt với logic phức tạp |
| Tổ chức mã nguồn | Ngắn gọn | Có cấu trúc rõ ràng hơn |
| Xử lý trạng thái bất đồng bộ | Dùng FutureProvider hoặc StreamProvider | Dùng AsyncNotifier |
Từ bảng trên có thể thấy Provider phù hợp với vai trò cung cấp dữ liệu, còn Notifier phù hợp với việc điều khiển và thay đổi dữ liệu. Trong ứng dụng thực tế, hai thành phần này thường được sử dụng kết hợp thay vì lựa chọn một trong hai.
7. Khi nào nên sử dụng Provider/Notifier?
Provider nên được sử dụng trong những trường hợp sau:
- Cung cấp dependency: Ví dụ API client, repository, database service hoặc một cấu hình ứng dụng.
- Cung cấp dữ liệu chỉ đọc: Ví dụ tên ứng dụng, thông tin phiên bản hoặc các hằng số.
- Tính toán dữ liệu từ provider khác: Provider có thể đọc các nguồn dữ liệu khác và tạo ra kết quả mới.
- Tách logic khởi tạo đối tượng khỏi widget: Widget chỉ cần đọc đối tượng mà không cần tự tạo hoặc quản lý vòng đời của nó.
Ví dụ, repository có thể được cung cấp bằng Provider và được sử dụng bên trong Notifier. Cách tổ chức này giúp Notifier không phụ thuộc trực tiếp vào cách khởi tạo repository.
Notifier nên được sử dụng khi trạng thái có nhiều hành động hoặc có logic nghiệp vụ đáng kể. Một số trường hợp phổ biến gồm:
- Quản lý giỏ hàng.
- Quản lý trạng thái đăng nhập.
- Quản lý biểu mẫu.
- Quản lý bộ lọc và sắp xếp danh sách.
- Quản lý trạng thái tải, thành công và lỗi.
- Thực hiện các thao tác thêm, sửa, xóa dữ liệu.
Ví dụ, thay vì để widget trực tiếp thay đổi danh sách sản phẩm, lập trình viên có thể định nghĩa các phương thức như addProduct, removeProduct và clearCart trong CartNotifier. Nhờ đó, các quy tắc nghiệp vụ được tập trung và có thể tái sử dụng ở nhiều màn hình.
8. Ưu điểm chung của Provider và Notifier
Tách biệt giao diện và logic
Provider và Notifier giúp đưa logic ra khỏi widget. Widget trở nên ngắn gọn, tập trung vào việc hiển thị dữ liệu. Đây là yếu tố quan trọng trong các dự án lớn, nơi giao diện và nghiệp vụ thường thay đổi độc lập.
Khả năng tái sử dụng
Một provider có thể được nhiều widget sử dụng. Notifier cũng có thể cung cấp cùng một logic cho nhiều màn hình khác nhau mà không cần sao chép mã nguồn.
Dễ kiểm thử
Vì logic được đặt trong provider hoặc Notifier thay vì nằm rải rác trong widget, lập trình viên có thể kiểm thử độc lập các phương thức xử lý trạng thái. Điều này làm tăng độ tin cậy của ứng dụng.
Quản lý dependency hiệu quả
Provider hỗ trợ việc cung cấp các dependency theo một cấu trúc rõ ràng. Khi cần thay đổi implementation, chẳng hạn chuyển từ API thật sang API giả để kiểm thử, lập trình viên chỉ cần thay thế provider tương ứng.
Hỗ trợ trạng thái bất đồng bộ
Thông qua FutureProvider, StreamProvider và AsyncNotifier, Riverpod cung cấp cách xử lý thống nhất cho các thao tác bất đồng bộ. Trạng thái loading, data và error được biểu diễn rõ ràng, giảm lỗi trong quá trình phát triển.
Kết luận
Provider và Notifier đại diện cho hai vai trò bổ trợ trong kiến trúc quản lý trạng thái của Riverpod. Provider tạo ra một cơ chế linh hoạt để cung cấp dữ liệu, dependency và các giá trị được tính toán. Notifier mở rộng cơ chế đó bằng cách đóng gói trạng thái cùng các hành động làm thay đổi trạng thái.
Provider và Notifier là hai thành phần quan trọng trong Riverpod, góp phần giải quyết bài toán quản lý trạng thái trong ứng dụng Flutter. Provider phù hợp với việc cung cấp dữ liệu, dependency hoặc các giá trị chỉ đọc. Notifier phù hợp với việc quản lý trạng thái có thể thay đổi và chứa các phương thức xử lý logic nghiệp vụ.








Khoá học lập trình game con rắn cho trẻ em




