flutterdartdio

Flutter: Dio Cache Interceptor not caching


I'm trying to add dio_cache_interceptor to my Flutter app. Here are the versions I'm using:

  dio: ^5.6.0
  dio_cache_interceptor: ^3.5.0

However, when I fire a request, the request is always sent to the webserver, the cache never seems to intercept.

What I've tried or checked:

  1. The response's status code is 200

  2. I tried playing around with the CacheOptions.keyBuilder, but after all, the keys generated for the requests are always the same

  3. I checked my response headers, they're as following:

    • etag: <sha-1-hash-here>
    • content-type: application/json;charset=utf-8
    • cache-control: private, max-age=86400, stale-if-error=21600
    • date: Fri, 16 Aug 2024, 09:42:46 GMT
    • content-length: 2332
    • x-aspnet-version: 4.0.30319

    (The API is mine too, so I could add headers)

  4. Tried reordering the interceptors, since the execution is in the order of addition (hence, the cache interceptor should be first, right?)

  5. I also have the http: ^1.2.1 package installed, but I'm not using it in this file.

  6. I read the Dio Cache Interceptor Reference, but couldn't find anything else.

I wouldn't know where to look next and spent a whole day now. If anybody could point me in the right direction, I'd highly appreciate it.

Here's my code, I stripped out the unneccessary parts:

import 'dart:async';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';

class WebAPIProvider {
  late final Dio dio;
  late final CacheOptions cacheOptions;

  /// WebAPIProvider Constructor
  WebAPIProvider() {
    // Set cache options
    cacheOptions = CacheOptions(
      store: MemCacheStore(),
      policy: CachePolicy.request,
      hitCacheOnErrorExcept: [403],
      maxStale: const Duration(days: 7),
    );

    // Set base options for every request and add interceptors
    dio = Dio(
      BaseOptions(
        baseUrl: 'https://my.domain.com/webapi/v1',
        connectTimeout: const Duration(seconds: 7),
        receiveTimeout: const Duration(seconds: 4),
        responseType: ResponseType.json,
      ),
    )..interceptors.addAll([
        DioCacheInterceptor(
          options: cacheOptions,
        ),
        InterceptorsWrapper(
          onRequest: onDioRequest,
          onResponse: onDioResponse,
          onError: onDioError,
        ),
      ]);
  }

  /// Fired before every Dio request
  /// 
  /// @Stackoverflow: This adds the accessToken (and other stuff) to the header
  FutureOr<void> onDioRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    // Set contentType
    options.headers["contentType"] = "application/json";

    // Set accessToken
    options.headers["accessToken"] = "access-token-here";

    // Continue
    return handler.next(options);
  }

  /// Fired at every response Dio receives
  /// 
  /// @Stackoverflow: Using this for logging the reponse
  FutureOr<void> onDioResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) async {
    // Some logging here...
        
    // Continue
    return handler.next(response);
  }

  /// Fired at any error during the request
  /// 
  /// @Stackoverflow: Using this for logging the error and resolve with an "empty" dataset
  FutureOr<void> onDioError(
    DioException exception,
    handler,
  ) async {
    // Continue with empty response
    return handler.resolve(Response(
      extra: {"exceptionType": exception.type},
      requestOptions: exception.requestOptions,
    ));
  }

  /// 🌐 Invokes the API [endpoint] with optional [requestHeaders] and [queryParameters].
  /// 
  /// @Stackoverflow: This is the method I'm invoking, i.e. data = api.get('/my/endpoint')
  Future<Response> get(
    String endpoint, [
    Map<String, String>? requestHeaders,
    Map<String, dynamic>? queryParameters,
  ]) async {
    // Send request
    Response<dynamic> response = await dio.get(
      endpoint,
      queryParameters: queryParameters,
      options: Options(
        headers: requestHeaders,
      ),
    );

    // Here I would convert it into a specific type, matching my API

    return response;
  }
}

Greetings, Boris

Addendum: I tried also making the function onDioRequest() synchronous. It was async, because I was reading the access token from the secure storage. However, my response always contains the extra: {@cache_key@: always-same-key, @fromNetwork@: true.


Solution

  • I finally figured it out. I initialized the above class within my aaaRepository's constructor. Now whenever I visited page aaa, the repository would be created and initialize above class anew. If I would switch to page bbb and back, the class would be re-initialized and with it it's cache. So Dio would actually cache, just not long enough.

    The solution was to use a repository provider on the main() level, so it would persist trough the app.