Search code examples
flutterdartriverpod

State value is empty when i use Riverpod StateNotifier with family modifier


My code works perfectly fine without the family modifier, but as soon as I add it, I can't seem to retrieve the state data - it just comes back empty. I'm confused because I've checked the state value while saving it from the API and it's definitely not empty. What could be causing this issue?

This is my 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;
    }
  }
}

This is my provider

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

This is how i'm trying to get the data

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

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

Solution

  • By default, Dart checks for referential equality. If we compare any two non-constant objects in dart with the same value, it evaluates to false.

    final first  =  {
      'lang': 'en-AE',
      'requestID': 1
    };
    
    final second = {
      'lang': 'en-AE',
      'requestID': 1
    };
    
    print(first == second); // false
    

    From riverpod documentation https://riverpod.dev/docs/concepts/modifiers/family

    Parameter restrictions For families to work correctly, it is critical for the parameter passed to a provider to have a consistent hashCode and ==.

    Ideally, the parameter should either be a primitive (bool/int/double/String), a constant (providers), or an immutable object that overrides == and hashCode.

    So first create an object to pass as argument to provider.

    @immutable
    class SingleRequestArguments {
      final String lang;
      final int id;
    
      const SingleRequestArguments({
        required this.lang,
        required this.id,
      });
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is SingleRequestArguments &&
              runtimeType == other.runtimeType &&
              lang == other.lang &&
              id == other.id;
    
      @override
      int get hashCode => lang.hashCode ^ id.hashCode;
    }
    

    Then in you code replace the map that you used to pass as an argument to this object.

    final singleRequestStateProvider =
        StateNotifierProvider.family<SingleRequestNotifier, Request, SingleRequestArguments>((ref, arg) {
      return SingleRequestNotifier(ref: ref, lang: arg.lang, requestID: arg.id);
    });
    
    final args = SingleRequestArguments(
      lang: 'en-AE',
      id: widget.requestID,
    );
    
    Request _request = ref.watch(singleRequestStateProvider(args));