Ottavio Miele
Ottavio Miele

Reputation: 38

Riverpod generator and state classes

I'm migrating from old Provider and his ChangeNotifier, to RiverPod and his RiverPod generator, and now I'm playing around to understand and feel more confident with the new syntax. I usually represent the states in complex classes. For example, in my previous project, using Provider, I used a class to represent the state of a Logged User, like:

class UserLoggedProvider extends ChangeNotifier {
   User? user;
   Map<String, dynamic>? userConfig;
   ...

   setLoginData(LoginResponse response) {
      this.user = response.user;
      this.userConfig = response.configs;
      notifyListeners();
   }
   ...
}

Now I'm trying to replicate this in a new way.

In a Sandbox project, I tried 2 approach.

Approach 1:

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'test.g.dart';

class TestProvider {
  final TestProviderRef _ref;

  TestProvider(this._ref);

  String? _testString;

  String? get testString => _testString;

  void setTestString(String value) {
    _testString = value;
    _ref.notifyListeners();
  }
}

@riverpod
TestProvider testProvider(TestProviderRef ref) => TestProvider(ref);

Approach 2:

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'test2.freezed.dart';
part 'test2.g.dart';

@freezed
class Test2Data with _$Test2Data {
  const factory Test2Data({
    String? testString,
  }) = _Test2Data;

  factory Test2Data.fromJson(Map<String, dynamic> json) =>
      _$Test2DataFromJson(json);
}

@riverpod
class Test2Provider extends _$Test2Provider {
  @override
  Test2Data build() => const Test2Data();

  void setTestString(String value) {
    state = state.copyWith(testString: value);
  }
}

Both works, and I somehow feel the second one is more correct, using immutable state and state notifier, but it is also more "boilerplate" than the first one.

It is boilerplate also in updating the state:

ElevatedButton(onPressed: () {
   testRef.setTestString("Random Value: ${Random.secure().nextDouble()}");
   ref.watch(test2ProviderProvider.notifier).setTestString("Random Value: ${Random.secure().nextDouble()}");
}, child: Text("Set value"))

In what approach do I have to stick in future? And, if it's the case, is there another approach better than this 2 examples? Thank you so much in advance

Upvotes: 0

Views: 840

Answers (1)

Mike
Mike

Reputation: 509

the second solution is not boilerplate, but it is correct. in fact in the second solution you have two classes: one is the Entity that you 're handling, and the second is the notifier ("controller") that mades all the work. finally you have the View (the Widget) that calls the Notifier to make all the works. In this manner you have separated all your concerns: the entity, the controller and the view. It's cleaner and more maintenable.

Finally, in my opinion , in the View it should be better to write only one line of code that calls the notifier:

ElevatedButton(onPressed: () {
   ref.watch(test2ProviderProvider.notifier)
        .setTestString("Random Value:         
    ${Random.secure().nextDouble()}");

   }, child: Text("Set value"))

And in the Notifier you will do all the stuff.

Upvotes: 0

Related Questions