skip to Main Content

I successfully used StateNotifier with primitives and also with non-primitives using a generator. But with the manually written non-primitive type, it does not work. I can see in the debugger that the setState method is called, but ConsumerWidget does not reflect any change.

Here is my code:

notifications_settings_provider.dart

class NotificationsSettingsStateNotifier
    extends StateNotifier<NotificationsSettings> {
  NotificationsSetttingsStateNotifier()
      : super(NotificationsSettings.defaultValues());

  setState(NotificationsSettings val) {
    state = val;
  }
}

final notificationsProvider = StateNotifierProvider<
    NotificationsSettingsStateNotifier, NotificationsSettings>((ref) {
  return NotificationsSettingsStateNotifier();
});

notifications_settings.dart

class NotificationsSettings {
  bool? enabled;
  int? timeoutInDays;
  int? delayInHours;
  String? startTime;

  NotificationsSettings(
      {this.enabled, this.timeoutInDays, this.delayInHours, this.startTime});

  NotificationsSettings.defaultValues() {
    enabled = true;
    timeoutInDays = 1;
    delayInHours = 3;
    startTime = '09:00 AM';
  }

  NotificationsSettings.fromJson(Map<String, dynamic> json) {
    enabled = json['enabled'];
    timeoutInDays = json['timeoutInDays'];
    delayInHours = json['delayInHours'];
    startTime = json['startTime'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['enabled'] = this.enabled;
    data['timeoutInDays'] = this.timeoutInDays;
    data['delayInHours'] = this.delayInHours;
    data['startTime'] = this.startTime;
    return data;
  }

  @override
  bool operator ==(Object other) =>
      other is NotificationsSettings &&
      other.runtimeType == runtimeType &&
      other.enabled == enabled &&
      other.timeoutInDays == timeoutInDays &&
      other.delayInHours == delayInHours &&
      other.startTime == startTime;

  int get hashCode =>
      Object.hash(enabled, timeoutInDays, delayInHours, startTime);
}

notifications_screen.dart

class Notifications extends ConsumerWidget {
 Notifications({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    var notificationsSettings = ref.watch(notificationsProvider);
    ...
    onToggle: (index) {
                  notificationsSettings.enabled = index == 0 ? true : false;
                  ref
                      .read(notificationsProvider.notifier)
                      .setState(notificationsSettings);

I thought that it can be somehow related to how StateNotifier internally compares state objects and I have overridden == and hashcode. Did not help.

Currently, I think that maybe my NotificationsSettings object needs copyWith method, but does not sure yet.

I know that I can use a code generator, but the question still stands even as purely academic.

2

Answers


  1. Chosen as BEST ANSWER

    So, thanks to Ruble's answer revealed that the problem was that class properties were not defined as final.

    Now, the class looks as follows:

    class NotificationsSettings {
      final bool? enabled;
      final int? timeoutInDays;
      final int? delayInHours;
      final String? startTime;
    
      NotificationsSettings(
          {this.enabled, this.timeoutInDays, this.delayInHours, this.startTime});
    
      static NotificationsSettings defaultValues() {
        return NotificationsSettings(
          enabled: true, timeoutInDays: 1, delayInHours:1, startTime: '09:00 AM');
      }
    
      NotificationsSettings fromJson(Map<String, dynamic> json) {
    
        return NotificationsSettings(
            enabled: json['enabled'] ?? this.enabled,
            timeoutInDays: json['timeoutInDays'] ?? this.timeoutInDays,
            delayInHours: json['delayInHours'] ?? this.delayInHours,
            startTime: json['startTime'] ?? this.startTime,
          );
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = new Map<String, dynamic>();
        data['enabled'] = this.enabled;
        data['timeoutInDays'] = this.timeoutInDays;
        data['delayInHours'] = this.delayInHours;
        data['startTime'] = this.startTime;
        return data;
      }
    
      @override
      bool operator ==(Object other) =>
          other is NotificationsSettings &&
          other.runtimeType == runtimeType &&
          other.enabled == enabled &&
          other.timeoutInDays == timeoutInDays &&
          other.delayInHours == delayInHours &&
          other.startTime == startTime;
    
      int get hashCode =>
          Object.hash(enabled, timeoutInDays, delayInHours, startTime);
    
      NotificationsSettings copyWith(
              {bool? enabled,
              int? timeoutInDays,
              int? delayInHours,
              String? startTime}) =>
          NotificationsSettings(
            enabled: enabled ?? this.enabled,
            timeoutInDays: timeoutInDays ?? this.timeoutInDays,
            delayInHours: delayInHours ?? this.delayInHours,
            startTime: startTime ?? this.startTime,
          );
    }
    

    I also added copyWith method, as otherwise there is no way to change the state.

    BTW, there is online JSON to Dart converter that generates Dart classes with final fields and copyWith method.


  2. Try rewriting your NotificationsSettings class using the freezed or equatable package.

    A simpler way would be to implement all fields of the NotificationsSettings class as final.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search