如何用Riverpod 2初始化Widget的Provider?现有实现求评估
我用Riverpod开发时遇到这么个情况:有个带两个标签页的Widget,切换Tab时,选中第一个Tab要把infoShare设为address的值,选中第二个Tab设为publicKey的值。我用Notifier实现了逻辑,但首次初始化主Widget时Provider没完成初始化,于是创建了个特定Provider,通过ProviderScope完成初始化,现在代码能正常运行,但不确定这个方案合理不,同时想知道Riverpod注解能不能帮上忙。
final _contactDetailInfoShareProviderArgs = Provider<Contact>( (ref) { throw UnimplementedError(); }, ); final _contactDetailInfoShareProvider = NotifierProvider.autoDispose .family<ContactDetailInfoShareNotifier, String, Contact>( () { return ContactDetailInfoShareNotifier(); }, ); class ContactDetailInfoShareNotifier extends AutoDisposeFamilyNotifier<String, Contact> { ContactDetailInfoShareNotifier(); @override String build(Contact arg) { return arg.address.toUpperCase(); } void setInfoShare(int tab, Contact contact) { if (tab == 1) { state = contact.publicKey.toUpperCase(); } else { state = contact.address.toUpperCase(); } } } abstract class ContactDetailProvider { static final contactDetailInfoShare = _contactDetailInfoShareProvider; static final contactDetailInfoShareProviderArgs = _contactDetailInfoShareProviderArgs; } class ContactDetail extends ConsumerWidget { const ContactDetail({ required this.contact, super.key, }); final Contact contact; @override Widget build(BuildContext context, WidgetRef ref) { return ProviderScope( overrides: [ ContactDetailProvider.contactDetailInfoShareProviderArgs .overrideWithValue( contact, ), ], child: ContactDetailBody( contact: contact, ), ); } } class ContactDetailBody extends ConsumerWidget { const ContactDetailBody({ required this.contact, super.key, }); final Contact contact; @override Widget build(BuildContext context, WidgetRef ref) { return Column( children: <Widget>[ Expanded( child: Container( child: ContainedTabBarView( tabs: [ Text('tab1'), Text('tab2'), ], views: [ ContactDetailTab1(), ContactDetailTab2(), ], onChange: (p0) { ref .watch( ContactDetailProvider.contactDetailInfoShare(contact) .notifier, ) .setInfoShare(p0, contact); }, ), ), ), AppButton( 'Share', onPressed: () { print( ref.watch( ContactDetailProvider.contactDetailInfoShare( ref.read( ContactDetailProvider.contactDetailInfoShareProviderArgs, ), ), ), ); }, ) ], ); } }
现有方案的合理性
你的代码能正常运行,但存在冗余设计:你已经用了FamilyProvider来接收Contact参数,没必要额外创建_contactDetailInfoShareProviderArgs这个Provider,也不需要用ProviderScope去覆盖它。FamilyProvider本身就是用来给Provider传递动态参数的,直接用它的arg就能完成初始化,不需要多此一举。
Riverpod注解的帮助
当然有用,Riverpod的注解(依赖riverpod_generator包)能大幅减少样板代码,让Provider的定义更简洁。比如用@riverpod注解可以自动生成Notifier和对应的Provider,不需要手动写NotifierProvider.autoDispose.family这类冗长的代码,同时还能避免一些手动编写容易出现的错误。
优化后的代码示例
去掉冗余的Provider,直接用Family的参数传递Contact,简化后的代码如下:
// 用注解生成Provider(需要先配置riverpod_generator) @riverpod class ContactDetailInfoShare extends _$ContactDetailInfoShare { late final Contact _contact; @override String build(Contact contact) { _contact = contact; return contact.address.toUpperCase(); } void setInfoShare(int tab) { state = tab == 1 ? _contact.publicKey.toUpperCase() : _contact.address.toUpperCase(); } } // 主Widget不需要ProviderScope了 class ContactDetail extends ConsumerWidget { const ContactDetail({ required this.contact, super.key, }); final Contact contact; @override Widget build(BuildContext context, WidgetRef ref) { return ContactDetailBody( contact: contact, ); } } class ContactDetailBody extends ConsumerWidget { const ContactDetailBody({ required this.contact, super.key, }); final Contact contact; @override Widget build(BuildContext context, WidgetRef ref) { // 直接监听对应的FamilyProvider final infoShare = ref.watch(contactDetailInfoShareProvider(contact)); return Column( children: <Widget>[ Expanded( child: ContainedTabBarView( tabs: [ Text('tab1'), Text('tab2'), ], views: [ ContactDetailTab1(), ContactDetailTab2(), ], onChange: (tabIndex) { // 调用notifier的方法,不用再传contact ref.read(contactDetailInfoShareProvider(contact).notifier) .setInfoShare(tabIndex); }, ), ), AppButton( 'Share', onPressed: () { print(infoShare); }, ) ], ); } }
这样代码更简洁,逻辑也更清晰,同时解决了初始化的问题,不需要额外的Provider和ProviderScope来传递参数。
内容的提问来源于stack exchange,提问作者redDwarf

