Search code examples
flutterriverpoddio

Is it a good practice to pass a Ref as an argument to a service?


I'm playing with Flutter coming from a Xamarin.Forms experience and I'm trying to implement some common scenarios I dealt with in my previous applications.

I have a RestClient service class containing all the low-level Rest calls performed with Dio.

I want to logout from the application when one of the calls returns 401 - Unauthorized (I know it can be handled better... I'm simplifying).

I'm using Riverpod to manage my business logic and I was struggling to try to communicate with an authenticationProvider, because inside RestClient there is no Ref to interact with providers.

The only way I found was to pass a Ref in RestClient constructor, so that I can update authenticationProvider state.

While it's working fine, I'm doubtful about mixing things and creating a deep link between two components that maybe should not communicate in this way.

Here's my code.

rest_client.dart

class RestClient {
  final Dio dio;
  final Ref ref;

  RestClient._({required this.dio, required this.ref});

  factory RestClient({required Ref ref}) {
    var dio = Dio(BaseOptions(
      baseUrl: "https://myurl.com/",
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
    ));

    dio.interceptors.add(InterceptorsWrapper(
      onError: (e, handler) {
        if (e.response != null && e.response!.statusCode == 401) {
          ref.read(authenticationProvider.notifier).invalidateToken();
        }
      },
    ));

    return RestClient._(dio: dio, ref: ref);
  }

  /**** ... *****/
  /**** Methods to call api endpoints *****/
  /**** ... *****/
}

authentication_provider.dart

class AutenthicationNotifier extends StateNotifier<String?> {
  // *** Constructor and other methods ***

  void invalidateToken() {
    // body
  }
}

final authenticationProvider = StateNotifierProvider<AutenthicationNotifier, String?>((ref) {
  return AutenthicationNotifier();
});

Finally, I have a simple Provider that gives access to RestClient.

final restClientProvider = Provider((ref) => RestClient(ref: ref));

Solution

  • This is the exact way that you should do it. If the class used has its own provider function using ref is the intended way to communicate. After all from the perspective of riverpod the only difference between the two is one having a state.

    A syntax suggestion: You can make your RestClient much simpler by just having the constructor
    RestClient({required this.dio, required this.ref});
    and doing all the initialization in the provider function:

    final restClientProvider = Provider((ref) {
     var dio = Dio(BaseOptions(
          baseUrl: "https://myurl.com/",
          connectTimeout: const Duration(seconds: 10),
          receiveTimeout: const Duration(seconds: 10),
        ));
    
        dio.interceptors.add(InterceptorsWrapper(
          onError: (e, handler) {
            if (e.response != null && e.response!.statusCode == 401) {
              ref.read(authenticationProvider.notifier).invalidateToken();
            }
          },
        ));
    return RestClient(ref: ref, dio: dio);
    });