Search code examples
flutterdartriverpod

Access Provider in Class


I have a class called NavigatorRepository that I am trying to access, userProvider. How should I be accessing the userProvider inside the NavigatorRepository? I feel like i've tried everything, except the right thing...

Here is a snippet from NavigatorRepository

final user = Provider((ref) {
  return ref.watch(userProvider.notifier).getUser();
});
...

class NavigatorRepository {

...

  Future<dynamic> _get(
    String path, {
    Map<String, Object?>? queryParameters,
  }) async {
  
    var x = user; <== How do I get this to work?
}
 

}

UserProvider

class UserNotifier extends StateNotifier<User> {
  UserNotifier()
      : super(User(accessToken: '');

  void setUser(User user) {
    state = user;
  }

  User getUser() {
    return state;
  }
}


final userProvider = StateNotifierProvider<UserNotifier, User>((ref) {
  return UserNotifier();
});


Solution

  • There are many ways to do this.

    1. There is now a newer provider for this purpose, (Async)NotifierProvider:
    final navigatorProvider = NotifierProvider<NavigatorRepository, void>(() {
      return NavigatorRepository();
    });
    
    class NavigatorRepository extends Notifier<void> {
      
      @override
      void build() {
        // you can use ref.watch in this method
        User user = ref.watch(userProvider);
      }
    
      func() {
        // ref is available in any methods of the NavigatorRepository class
        ref.read(...);
      }
    
    1. The next method is to pass the ref parameter to the class constructor:
    final navigatorProvider = Provider<NavigatorRepository>(NavigatorRepository.new);
    
    
    class NavigatorRepository {
      NavigatorRepository(this._ref);
    
      final Ref _ref;
    
      func() {
        // ref is available in any methods of the NavigatorRepository class
        _ref.read(...);
      }
    }
    
    1. The following way better expresses the principle of implementing dependencies, but follows against the design patterns in Riverpod:
    final navigatorProvider = Provider<NavigatorRepository>((ref) {
      return NavigatorRepository(
        authService: ref.watch(AuthService),
        settingService: ref.watch(SettingService),
        userService: ref.watch(UserService),
      );
    });
    
    class NavigatorRepository {
      UserService({
        required AuthService authService,
        required SettingService settingService,
        required UserService userService,
      })  : _authService = authService,
            _settingService = settingService,
            _UserService = userService;
    
      final AuthService _authService;
      final SettingService _settingService;
      final UserService _userService;
    
      func() {
        // simply call the method
        User user = _userService.getUser();
      }
    }
    

    The idea is that you pass all necessary dependencies immediately to the constructor of your class. In the end, you know for sure that your class-service only depends on the services that are in the constructor.

    Note also that I have made all services private to avoid outside access. If you don't need it, make the fields open.

    1. You can simply pass the ref parameter when calling any method:
    class NavigatorRepository {
      ...
    
      Future<dynamic> _get(
        Ref ref,
        String path, {
        Map<String, Object?>? queryParameters,
      }) async {
        var user = ref.read(userProvider);
      }
    }
    

    Although here you could pass User user as a parameter to the method and use it inside. But apparently there can be more such dependencies and then it is easier just to pass ref.