Riverpod — trình quản lý trạng thái và phụ thuộc biên dịch cho Flutter, được tạo bởi Remi Rousselet vào năm 2021 như là người kế thừa của Provider. Riverpod giải quyết các vấn đề cơ bản của Provider: thiếu kiểm tra biên dịch, phụ thuộc vào BuildContext và phức tạp với ProviderNotFoundException. Theo pub.dev, gói này đã có hơn 5 nghìn lượt thích và đang tích cực thay thế Provider trong các dự án mới.
Những điểm chính
Riverpod là một thư viện quản lý trạng thái và tiêm phụ thuộc cho Flutter biên dịch các mô tả provider thành mã Dart an toàn. Không giống như Provider, các provider của Riverpod không bị ràng buộc với BuildContext: chúng được tạo toàn cục hoặc trong ProviderScope và có thể truy cập từ bất kỳ đâu. Trình biên dịch kiểm tra kiểu, phụ thuộc và tính toàn vẹn của đồ thị provider tại thời điểm xây dựng, loại bỏ các lỗi runtime như ProviderNotFoundException.
Riverpod sử dụng mô hình override để kiểm thử: mỗi provider có thể được ghi đè qua ProviderScope.overrideWith mà không cần tạo lớp con hay mock interface. Điều này làm cho kiểm thử được cô lập: mỗi bài kiểm tra nhận bản sao riêng của đồ thị phụ thuộc được kiểm soát hoàn toàn.
Theo Flutter Community Survey 2025, Riverpod đứng thứ ba về mức độ phổ biến sau Provider và BLoC. Tuy nhiên, Riverpod là gói phát triển nhanh nhất: +120% lượt cài đặt trong năm 2024. Lý do chính: an toàn tại thời điểm biên dịch, không có ProviderNotFoundException, hỗ trợ bất đồng bộ tích hợp qua AsyncValue.
Riverpod cung cấp 8 loại provider, mỗi loại cho một kịch bản cụ thể: Provider (hằng số/dịch vụ), StateProvider (trạng thái nguyên thủy), StateNotifierProvider (logic phức tạp với StateNotifier), ChangeNotifierProvider (để di chuyển từ Provider), FutureProvider (dữ liệu bất đồng bộ, một lần), StreamProvider (luồng phản ứng), NotifierProvider (API mới, Flutter 3.10+) và AsyncNotifierProvider (Notifier bất đồng bộ).
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}ProviderRef — đối tượng được truyền cho mỗi provider để truy cập các provider khác. ref.watch — đăng ký thay đổi, ref.read — đọc một lần, ref.invalidate — đặt lại bộ nhớ đệm. ProviderRef thay thế BuildContext của Provider: bất kỳ provider nào cũng có thể đọc các provider khác mà không cần truy cập vào cây widget. Điều này cho phép xây dựng đồ thị phụ thuộc bên ngoài lớp UI.
ProviderScope — widget gốc, bắt buộc để Riverpod hoạt động. ProviderScope lưu trữ tất cả các provider, quản lý vòng đời của chúng và lưu trữ giá trị vào bộ nhớ đệm. Không có ProviderScope, ứng dụng sẽ bị treo với ProviderNotFoundException. ProviderScope có thể được lồng — phạm vi lồng ghi đè các provider cha, được sử dụng để kiểm thử và cô lập tính năng.
AsyncValue — lớp sealed của Riverpod để biểu diễn trạng thái bất đồng bộ. AsyncValue có ba biến thể: AsyncData (dữ liệu thành công), AsyncError (lỗi), AsyncLoading (đang tải). Thay vì chuyển đổi thủ công giữa loading/error/data, mỗi FutureProvider hoặc StreamProvider tự động trả về AsyncValue và widget xử lý cả ba trạng thái qua ref.watch.
final userProvider = FutureProvider((ref) async {
final api = ref.watch(apiProvider);
return await api.fetchUser();
});
class UserScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => UserWidget(user),
error: (e, _) => ErrorWidget(e.toString()),
loading: () => CircularProgressIndicator(),
);
}
}AsyncValue.when — phương thức để so khớp mẫu cả ba trạng thái. Trình biên dịch kiểm tra rằng cả ba trường hợp đều được xử lý — nếu bạn quên loading hoặc error, mã sẽ không biên dịch được. AsyncValue.whenData — chỉ cho data (nếu loading/error không cần thiết). AsyncValue.guard — trình bao bọc try-catch để chuyển đổi ngoại lệ thành AsyncError. keepAlive — cờ ngăn bộ nhớ đệm provider bị hủy khi ra khỏi phạm vi.
Sinh mã — tính năng chính của Riverpod 2.0+. Chú thích @riverpod trên một hàm tự động tạo một provider với kiểu chính xác, hỗ trợ tái cấu trúc và tự động hoàn thành. Sinh mã sử dụng riverpod_generator và build_runner. Nhà phát triển viết một hàm thuần túy, và mọi thứ khác — kiểu, lớp, hàm tạo factory — được tạo tự động.
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello World';
}
// Đã tạo: final helloWorldProvider = Provider((ref) => 'Hello World');
@riverpod
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}Notifier — API mới cho trạng thái có thể thay đổi với sinh mã. Notifier là một lớp có phương thức build() và các phương thức thay đổi trạng thái. Không giống StateNotifier, Notifier không yêu cầu lớp trạng thái riêng biệt và cung cấp truy cập trực tiếp đến state qua getter/setter. Riverpod tự động tạo NotifierProvider cho mỗi lớp Notifier được chú thích bằng @riverpod.
build_runner: sinh mã được chạy bằng lệnh dart run build_runner build. Các tệp được tạo có hậu tố .g.dart và được import vào mã nguồn. Khi chú thích hoặc kiểu provider thay đổi, cần chạy lại sinh mã. Riverpod 2.x khuyến nghị sinh mã cho tất cả các dự án mới — việc tạo provider thủ công đang trở nên lỗi thời.
Sự khác biệt chính giữa Riverpod và Provider: độc lập với BuildContext, an toàn tại thời điểm biên dịch, hỗ trợ bất đồng bộ tích hợp, tự động lưu cache và kiểm thử qua override. Provider yêu cầu BuildContext để truy cập trạng thái (context.watch, context.read), Riverpod sử dụng WidgetRef và các provider được khai báo toàn cục.
| Đặc điểm | Provider | Riverpod |
|---|---|---|
| Phụ thuộc BuildContext | Có | Không |
| Kiểm tra biên dịch | Không | Có (qua @riverpod) |
| ProviderNotFoundException | Runtime | Không thể xảy ra |
| Bất đồng bộ | Thủ công | AsyncValue (tích hợp) |
| Kiểm thử | Bao bọc trong Provider | ProviderScope.overrideWith |
| Lưu cache | Không | Tự động + keepAlive |
Di chuyển từ Provider: Riverpod hỗ trợ ChangeNotifierProvider.adaptive để sử dụng ChangeNotifier hiện có mà không cần viết lại. Di chuyển dần dần: đầu tiên các tính năng mới được viết bằng Riverpod, sau đó các phiên bản Provider cũ được thay thế bằng provider Riverpod qua bộ chuyển đổi. Cả hai gói có thể cùng tồn tại trong một dự án, cho phép di chuyển mà không đóng băng phát triển.
Kiểm thử Riverpod dựa trên ProviderScope.overrideWith. Mỗi provider được ghi đè bên trong ProviderScope kiểm thử mà không cần mock hay container DI. ProviderContainer — môi trường cô lập cho kiểm thử không có Flutter (Dart thuần túy), cho phép kiểm thử provider mà không cần kết xuất widget.
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
void main() {
test('Counter increments correctly', () {
final container = ProviderContainer();
container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider), 1);
});
testWidgets('UI updates on increment', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [counterProvider.overrideWithValue(5)],
child: CounterScreen(),
),
);
expect(find.text('5'), findsOneWidget);
});
}ProviderContainer — không có Flutter. Sử dụng ProviderContainer để kiểm thử đơn vị provider mà không cần widget. overrideWithValue — thay thế provider bằng một giá trị cụ thể. overrideWith — thay thế bằng nhà máy provider (để mock dịch vụ). autodispose — trong kiểm thử, hãy xác minh rằng provider bị hủy khi ra khỏi phạm vi bằng container.dispose().
Câu hỏi thường gặp
Riverpod là thư viện quản lý trạng thái với provider toàn cục, AsyncValue và sinh mã. BLoC là mẫu kiến trúc với Event → Stream → State. Riverpod dễ học hơn và có DX tốt hơn qua các chú thích @riverpod. BLoC cung cấp sự cô lập logic nghiệp vụ chặt chẽ và theo dõi Event qua BlocObserver. Lựa chọn phụ thuộc vào mô hình dự án: Riverpod gần với Provider, BLoC — với các luồng phản ứng.
Autodispose là cơ chế tự động hủy provider khi không ai đăng ký theo dõi nó. Theo mặc định, tất cả các provider Riverpod đều autodispose: khi widget rời khỏi cây, provider bị xóa khỏi bộ nhớ. keepAlive — cờ vô hiệu hóa autodispose cho các provider cần sống mãi (API client, kho lưu trữ, cài đặt). Điều này ngăn rò rỉ bộ nhớ — các provider không sử dụng sẽ tự động bị hủy.
ref.invalidate — phương thức buộc đặt lại bộ nhớ đệm provider. Sau khi invalidate, provider được tạo lại ở lần đọc tiếp theo: FutureProvider thực thi lại hàm bất đồng bộ, StreamProvider đăng ký lại luồng. Sử dụng invalidate để buộc làm mới dữ liệu (pull-to-refresh, chuyển đổi người dùng). ref.refresh — kết hợp invalidate + đọc: đặt lại và ngay lập tức đọc giá trị mới trong một thao tác.
Có. Riverpod 1.x chỉ hoạt động mà không cần sinh mã — provider được tạo thủ công bằng Provider(), StateNotifierProvider(), FutureProvider(), v.v. Riverpod 2.x hỗ trợ cả hai cách tiếp cận. Không có sinh mã thì có nhiều boilerplate hơn nhưng không phụ thuộc vào build_runner và dart run build_runner build. Cho các dự án nhỏ (tới 30 provider), việc tạo thủ công là hợp lý; cho các dự án lớn, sinh mã là bắt buộc.
Family — bộ sửa đổi provider chấp nhận tham số bên ngoài. Ví dụ, userProvider(123) — provider tải người dùng có ID 123. Provider Family lưu cache kết quả riêng cho mỗi tham số duy nhất. Sử dụng Family cho danh sách các mục mà mỗi mục được tải theo ID. Bộ sửa đổi Family có sẵn cho tất cả các loại provider: Provider.family, FutureProvider.family, StreamProvider.family.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm