Riverpod — Quản lý phụ thuộc biên dịch cho Flutter

Tác giả: IT Sectr Đã đăng: 2026-02-19 Thời gian đọc: 7 phút

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

  • ProviderRef — đối tượng để truy cập các provider khác bên trong một provider
  • AsyncValue — trình bao bọc cho dữ liệu bất đồng bộ với các trạng thái loading/error/data
  • Notifier — lớp cho trạng thái có thể thay đổi với các phương thức biến đổi
  • ProviderScope — widget gốc quản lý tất cả các provider
  • Code Generation — chú thích @riverpod để tự động sinh provider

Riverpod là gì?

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.

Các loại Provider

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ộ).

Dart
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 và làm việc với bất đồng bộ

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.

Dart
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ã và @riverpod

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_generatorbuild_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.

Dart
@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.

Riverpod vs Provider

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ểmProviderRiverpod
Phụ thuộc BuildContextKhông
Kiểm tra biên dịchKhôngCó (qua @riverpod)
ProviderNotFoundExceptionRuntimeKhông thể xảy ra
Bất đồng bộThủ côngAsyncValue (tích hợp)
Kiểm thửBao bọc trong ProviderProviderScope.overrideWith
Lưu cacheKhôngTự độ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

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.

Dart
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 khác BLoC như thế nào?

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 trong Riverpod là gì?

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 hoạt động như thế nào?

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ó thể sử dụng Riverpod mà không cần sinh mã không?

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.

Provider Family là gì?

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

  • Riverpod — trình quản lý trạng thái biên dịch, người kế thừa Provider không có ProviderNotFoundException
  • ProviderRef — sự thay thế cho BuildContext để truy cập provider bên trong các provider khác
  • AsyncValue — lớp sealed với các trạng thái loading/error/data cho dữ liệu bất đồng bộ
  • Sinh mã @riverpod — suy luận kiểu tự động và nhà máy provider
  • ProviderScope.overrideWith — kiểm thử cô lập không cần mock và container DI
  • Family — provider được tham số hóa với lưu cache riêng lẻ
  • autodispose và keepAlive — quản lý vòng đời provider tự động

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.

Thảo luận dự án

Đọc thêm