Riverpod — یک مدیر حالت و وابستگی قابل کامپایل برای Flutter است که توسط Rémi Roussel در سال 2021 به عنوان جانشین Provider ساخته شده است. Riverpod مشکلات بنیادین Provider را حل میکند: عدم بررسی کامپایل، وابستگی به BuildContext و دشواری با ProviderNotFoundException. طبق دادههای pub.dev، این بسته بیش از 5 هزار لایک جمع کرده و بهطور فعال در پروژههای جدید جایگزین Provider میشود.
نکات اصلی
Riverpod — کتابخانهای برای مدیریت حالت و تزریق وابستگی در Flutter است که توصیف ارائهدهندهها را به کد Dart امن کامپایل میکند. برخلاف Provider، ارائهدهندههای Riverpod به BuildContext وابسته نیستند: آنها بهصورت سراسری یا در ProviderScope ایجاد میشوند و از هر مکانی قابل دسترسی هستند. کامپایلر انواع، وابستگیها و یکپارچگی گراف ارائهدهندهها را در مرحله ساخت بررسی میکند و خطاهای زمان اجرا مانند ProviderNotFoundException را حذف میکند.
Riverpod از مدل override برای تست استفاده میکند: هر ارائهدهنده میتواند از طریق ProviderScope.overrideWithout نیاز به ایجاد زیرکلاس یا mock کردن رابطها بازنویسی شود. این کار تست را ایزوله میکند: هر تست یک کپی از گراف وابستگی خود را دریافت میکند که کاملاً کنترل شده است.
طبق Flutter Community Survey 2025، Riverpod از نظر محبوبیت بعد از Provider و BLoC در رتبه سوم قرار دارد. در عین حال Riverpod سریعترین بسته در حال رشد است: +120% نصب در سال 2024. دلایل اصلی: ایمنی کامپایل، عدم وجود ProviderNotFoundException، پشتیبانی داخلی از ناهمگامی از طریق AsyncValue.
Riverpod 8 نوع ارائهدهنده را ارائه میدهد که هر کدام برای سناریوی خاصی هستند: Provider (ثابت/سرویس)، StateProvider (حالت ساده)، StateNotifierProvider (منطق پیچیده با StateNotifier)، ChangeNotifierProvider (برای مهاجرت از Provider)، FutureProvider (دادههای ناهمگام، یک بار)، StreamProvider (جریان واکنشی)، NotifierProvider (API جدید، Flutter 3.10+) و AsyncNotifierProvider (Notifier ناهمگام).
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 — شیءای که به هر ارائهدهنده برای دسترسی به سایر ارائهدهندهها ارسال میشود. ref.watch — اشتراک در تغییرات، ref.read — خواندن یکباره، ref.invalidate — بازنشانی حافظه پنهان. ProviderRef جایگزین BuildContext از Provider میشود: هر ارائهدهنده میتواند سایر ارائهدهندهها را بدون دسترسی به درخت ویجت بخواند. این امکان ساخت گراف وابستگی در خارج از لایه UI را فراهم میکند.
ProviderScope — ویجت ریشهای که برای کار Riverpod الزامی است. ProviderScope همه ارائهدهندهها را ذخیره میکند، چرخه عمر آنها را مدیریت میکند و مقادیر را کش میکند. بدون ProviderScope برنامه با ProviderNotFoundException سقوط میکند. ProviderScope میتواند تو در تو باشد — دامنه تو در تو ارائهدهندههای والد را بازنویسی میکند که برای تست و ایزوله کردن ویژگیها استفاده میشود.
AsyncValue — کلاس sealed Riverpod برای نمایش حالت ناهمگام. AsyncValue سه حالت دارد: AsyncData (داده موفق)، AsyncError (خطا)، AsyncLoading (بارگذاری). به جای جابهجایی دستی بین loading/error/data، هر FutureProvider یا StreamProvider بهطور خودکار AsyncValue برمیگرداند و ویجت هر سه حالت را از طریق 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 — متدی برای تطبیق الگوی هر سه حالت. کامپایلر بررسی میکند که هر سه مورد پردازش شدهاند — اگر loading یا error فراموش شود، کد کامپایل نخواهد شد. AsyncValue.whenData — فقط برای data (اگر loading/error مورد نیاز نباشد). AsyncValue.guard — پوششی try-catch برای تبدیل استثنا به AsyncError. keepAlive — فلگی که از تخریب کش ارائهدهنده هنگام خروج از محدوده دید جلوگیری میکند.
کدزایی — ویژگی کلیدی Riverpod 2.0+. حاشیهنویسی @riverpod روی یک تابع بهطور خودکار یک ارائهدهنده با نوع صحیح، پشتیبانی از بازسازی کد و تکمیل خودکار تولید میکند. کدزایی از riverpod_generator و build_runner استفاده میکند. توسعهدهنده یک تابع خالص مینویسد و بقیه چیزها — انواع، کلاسها، سازندههای کارخانهای — بهطور خودکار تولید میشوند.
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello World';
}
// تولید شد: final helloWorldProvider = Provider((ref) => 'Hello World');
@riverpod
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}Notifier — API جدید برای حالت قابل تغییر با کدزایی. Notifier کلاسی با متد build() و متدهای تغییر حالت است. برخلاف StateNotifier، Notifier به یک کلاس حالت جداگانه نیاز ندارد و دسترسی مستقیم به state را از طریق getter/setter فراهم میکند. Riverpod بهطور خودکار برای هر کلاس Notifier با حاشیهنویسی @riverpod یک NotifierProvider تولید میکند.
build_runner: کدزایی با دستور dart run build_runner build اجرا میشود. فایلهای تولید شده پسوند .g.dart دارند و به کد منبع وارد میشوند. هنگام تغییر حاشیهنویسیها یا انواع ارائهدهندهها باید کدزایی را دوباره اجرا کرد. Riverpod 2.x کدزایی را برای همه پروژههای جدید توصیه میکند — ایجاد دستی ارائهدهندهها منسوخ میشود.
تفاوتهای اصلی Riverpod با Provider: استقلال از BuildContext، ایمنی کامپایل، کار داخلی با ناهمگامی، کش خودکار و تست از طریق override. Provider برای دسترسی به حالت به BuildContext نیاز دارد (context.watch, context.read)، Riverpod از WidgetRef و ارائهدهندههای سراسری اعلام شده استفاده میکند.
| ویژگی | Provider | Riverpod |
|---|---|---|
| وابستگی به BuildContext | بله | خیر |
| بررسی کامپایل | خیر | بله (از طریق @riverpod) |
| ProviderNotFoundException | Runtime | غیرممکن |
| ناهمگامی | دستی | AsyncValue (داخلی) |
| تست | پوشش در Provider | ProviderScope.overrideWith |
| کش کردن | خیر | خودکار + keepAlive |
مهاجرت از Provider: Riverpod از ChangeNotifierProvider.adaptive برای استفاده از ChangeNotifierهای موجود بدون بازنویسی پشتیبانی میکند. مهاجرت مرحلهای: ابتدا ویژگیهای جدید با Riverpod نوشته میشوند، سپس Providerهای قدیمی از طریق آداپتور با ارائهدهندههای Riverpod جایگزین میشوند. هر دو بسته میتوانند در یک پروژه همزیستی داشته باشند که امکان مهاجرت بدون توقف توسعه را فراهم میکند.
تست Riverpod بر ProviderScope.overrideWith استوار است. هر ارائهدهنده در داخل ProviderScope تست بدون mock و کانتینرهای DI بازنویسی میشود. ProviderContainer — محیط ایزوله برای تست بدون Flutter (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 — بدون Flutter. از ProviderContainer برای تستهای واحد ارائهدهندهها بدون ویجت استفاده کنید. overrideWithValue — جایگزینی ارائهدهنده با مقدار مشخص. overrideWith — جایگزینی با کارخانه ارائهدهنده (برای mock کردن سرویسها). autodispose — در تستها بررسی کنید که ارائهدهنده هنگام خروج از محدوده دید با container.dispose() تخریب میشود.
سوالات متداول
Riverpod — کتابخانه مدیریت حالت با ارائهدهندههای سراسری، AsyncValue و کدزایی. BLoC — الگوی معماری با Event → Stream → State. Riverpod یادگیری آسانتری دارد و تجربه توسعه بهتری از طریق حاشیهنویسیهای @riverpod فراهم میکند. BLoC ایزولهسازی دقیق منطق کسبوکار و ردیابی Event را از طریق BlocObserver ارائه میدهد. انتخاب به پارادایم پروژه بستگی دارد: Riverpod به Provider نزدیکتر است، BLoC — به جریانهای واکنشی.
Autodispose — مکانیزم تخریب خودکار ارائهدهنده زمانی که هیچکس مشترک آن نیست. بهطور پیشفرض همه ارائهدهندههای Riverpod autodispose هستند: هنگام خروج ویجت از درخت، ارائهدهنده از حافظه حذف میشود. keepAlive — فلگی که autodispose را برای ارائهدهندههایی که باید همیشه زنده بمانند (کلاینتهای API، مخازن، تنظیمات) غیرفعال میکند. این کار از نشت حافظه جلوگیری میکند — ارائهدهندههای استفاده نشده بهطور خودکار تخریب میشوند.
ref.invalidate — متدی که کش ارائهدهنده را بهاجبار بازنشانی میکند. پس از invalidate، ارائهدهنده در خواندن بعدی دوباره ایجاد میشود: FutureProvider دوباره تابع async را اجرا میکند، StreamProvider دوباره در جریان مشترک میشود. از invalidate برای بهروزرسانی اجباری دادهها استفاده کنید (pull-to-refresh، تغییر کاربر). ref.refresh — ترکیبی از invalidate + خواندن: کش را بازنشانی میکند و بلافاصله مقدار جدید را در یک عملیات میخواند.
بله. Riverpod 1.x فقط بدون کدزایی کار میکند — ارائهدهندهها بهصورت دستی از طریق Provider()، StateNotifierProvider()، FutureProvider() و غیره ایجاد میشوند. Riverpod 2.x از هر دو روش پشتیبانی میکند. بدون کدزایی boilerplate بیشتری وجود دارد، اما وابستگی به build_runner و dart run build_runner build وجود ندارد. برای پروژههای کوچک (تا 30 ارائهدهنده) ایجاد دستی موجه است، برای پروژههای بزرگ کدزایی الزامی است.
Family — اصلاحکننده ارائهدهنده که یک پارامتر خارجی میپذیرد. به عنوان مثال، userProvider(123) — ارائهدهندهای که کاربر با ID 123 را بارگیری میکند. ارائهدهندههای Family نتیجه را برای هر پارامتر منحصربهفرد جداگانه کش میکنند. از Family برای لیست عناصری که هر عنصر با ID بارگیری میشود استفاده کنید. اصلاحکننده Family برای همه انواع ارائهدهندهها در دسترس است: Provider.family، FutureProvider.family، StreamProvider.family.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.