You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:35:25