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

使用Riverpod StateNotifier+family修饰符时状态值为空求助

问题:添加family修饰符后无法获取状态数据

不使用family修饰符时代码运行完全正常,但一旦添加该修饰符,读取状态数据就返回空值。确认从API保存状态时数据不为空(能打印出value.category),但读取时却为空。

State Notifier代码

class SingleRequestNotifier extends StateNotifier<Request> {
  SingleRequestNotifier({this.ref, this.lang, this.requestID})
      : super(Request.initial()) {
    fetchSingleRequestData(lang: lang, requestID: requestID);
  }
  final Ref? ref;
  final String? lang;
  final String? requestID;

  final RequestServices dataService = GetIt.instance<RequestServices>();

  Request get getSingleRequest {
    return state;
  }

  Future fetchSingleRequestData({String? lang, String? requestID}) async {
    try {
      await dataService
          .getSingleRequestList(lang: lang!, requestID: requestID!)
          .then((value) {
        state = value;
        print(value.category); // here value is printing
        ref!.read(isLoadingProvider.notifier).state = false;
      });
    } catch (e) {
      rethrow;
    }
  }
}

Provider代码

final singleRequestStateProvider =
    StateNotifierProvider.family<SingleRequestNotifier, Request, Map<String,dynamic>>(
        (ref, arg) {
  return SingleRequestNotifier(
      ref: ref,
      lang: arg['lang'],
      requestID: arg['requestID']);
});

获取数据的代码

Map<String, dynamic> args = {
    'lang': 'en-AE',
    'requestID': widget.requestID
  };

Request _request = ref.watch(singleRequestStateProvider(args));
print(_request.category); // here value is empty

原因分析

核心问题出在family的参数相等性判断逻辑:

  • 你使用普通Map<String, dynamic>作为family的参数,但Dart中普通Map的相等性是基于对象引用的——即使两个Map的键值对完全一致,只要是不同的Map实例,就会被family判定为不同参数。
  • 如果args是在Widget的build方法内创建的,每次Widget重建都会生成新的Map对象,这会触发family创建全新的SingleRequestNotifier实例。而新实例刚初始化时状态是Request.initial(),此时API请求还未完成,所以读取到的就是空值。

解决方法

1. 复用参数对象

把args提取为Widget的成员变量,避免每次build都创建新Map:

// 在Widget类中定义
late final Map<String, dynamic> args;

@override
void initState() {
  super.initState();
  args = {
    'lang': 'en-AE',
    'requestID': widget.requestID
  };
}

// 在build中使用
Request _request = ref.watch(singleRequestStateProvider(args));

2. 使用不可变且正确实现相等性的类型

自定义参数类

创建包含必要字段的类,重写==和hashCode方法:

class RequestArgs {
  final String lang;
  final String requestID;

  RequestArgs({required this.lang, required this.requestID});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is RequestArgs &&
          runtimeType == other.runtimeType &&
          lang == other.lang &&
          requestID == other.requestID;

  @override
  int get hashCode => lang.hashCode ^ requestID.hashCode;
}

更新Provider和调用代码:

// Provider
final singleRequestStateProvider =
    StateNotifierProvider.family<SingleRequestNotifier, Request, RequestArgs>(
        (ref, arg) {
  return SingleRequestNotifier(
      ref: ref,
      lang: arg.lang,
      requestID: arg.requestID);
});

// 获取数据
final args = RequestArgs(lang: 'en-AE', requestID: widget.requestID);
Request _request = ref.watch(singleRequestStateProvider(args));

使用元组

借助tuple包中的Tuple2(默认实现了正确的相等性判断):

// Provider
final singleRequestStateProvider =
    StateNotifierProvider.family<SingleRequestNotifier, Request, Tuple2<String, String>>(
        (ref, arg) {
  return SingleRequestNotifier(
      ref: ref,
      lang: arg.item1,
      requestID: arg.item2);
});

// 获取数据
final args = Tuple2('en-AE', widget.requestID);
Request _request = ref.watch(singleRequestStateProvider(args));

内容的提问来源于stack exchange,提问作者Nashaf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:57:44