http_client_cache

The http_client_cache package provides a simple and efficient way to cache HTTP responses in Dart and Flutter applications. It allows you to store and retrieve cached responses, reducing the need for redundant network requests and improving the performance of your application.

Installation

For Dart

Run the following command:

dart pub add http_client_cache

For Flutter

Run the following command:

flutter pub add http_client_cache

Usage

To use the http_client_cache package, you need to create an instance of HttpClientProxy and use it with your HTTP client. Here's a basic example:

import 'dart:async';

import 'package:http/http.dart' as http;
import 'package:http_client_cache/http_client_cache.dart';
import 'package:http_client_interceptor/http_client_interceptor.dart';

Future<void> main() async {
  final cache = HttpCache();

  // create a cache instance which persists the cache entries on disk
  // final dir = Directory('cache');
  // await cache.initLocal(dir);

  // create a cache instance which persists the cache entries in memory
  await cache.initInMemory();

  unawaited(
    http.runWithClient(
      _myDartApp,
      () => HttpClientProxy(
        interceptors: [
          cache,
        ],
      ),
    ),
  );
}

Future<void> _myDartApp() async {
  final client = http.Client();
  final response = await client.get(Uri.parse('https://api.example.com/data'));
  print(response.body);
}

For Flutter

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http_client_cache/http_client_cache.dart';
import 'package:http_client_interceptor/http_client_interceptor.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  // the HttpCache interceptor
  late final HttpCache httpCache;

  unawaited(
    // Create a new [HttpClientProxy] with the [HttpCache] interceptor
    // and make it the default [http.Client] for the [http.Client.new] factory method.
    //
    // A better way may be to create the [http.Client] and inject it where it is needed, 
    // instead of running your application with [runWithClient].
    //
    // For better performance, reuse the same [http.Client] for multiple http requests. So that
    // open connections are reused.
    http.runWithClient(
      () async {
        // needed for getApplicationCacheDirectory
        WidgetsFlutterBinding.ensureInitialized();

        // we need to init the cache in the runWithClient callback
        // because the runWithClient callback creates a new Zone
        // and we need to init the cache in the same zone.
        httpCache = HttpCache();
        final cacheDirectory = await getApplicationCacheDirectory();
        await httpCache.initLocal(cacheDirectory);

        runApp(const MyApp());
      },
      () => HttpClientProxy(
        interceptors: [
          httpCache,
        ],
      ),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // add your code here
  }
}

Advanced Usage

You can also use a HttpInterceptorWrapper to customize the cache-control header for specific requests.

import 'dart:async';
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:http_client_cache/http_client_cache.dart';
import 'package:http_client_interceptor/http_client_interceptor.dart';

Future<void> main() async {
  final cache = HttpCache();

  // create a cache instance which persists the cache entries on disk
  // final dir = Directory('cache');
  // await cache.initLocal(dir);

  // create a cache instance which persists the cache entries in memory
  await cache.initInMemory();

  unawaited(
    http.runWithClient(
      _myDartApp,
      () => HttpClientProxy(
        interceptors: [
          _CacheControlInterceptor(),
          cache,
        ],
      ),
    ),
  );
}

Future<void> _myDartApp() async {
  final client = http.Client();
  final response = await client.get(Uri.parse('https://api.example.com/data'));
  print(response.body);
}

class _CacheControlInterceptor extends HttpInterceptorWrapper {
  @override
  Future<OnResponse> onResponse(http.StreamedResponse response) async {
    final cacheControlHeader = response.headers[HttpHeaders.cacheControlHeader];
    if (cacheControlHeader == null) {
      return OnResponse.next(response);
    }

    // Add/override the cache control max-age parameter to cache the response.
    // In production, there should be some logic to having different caching
    // strategies for different content/mime types and/or urls.
    final cacheControl = CacheControl.dynamicContent(
      maxAge: const Duration(seconds: 60),
      staleWhileRevalidate: const Duration(seconds: 30),
      staleIfError: const Duration(seconds: 300),
    );

    // Create new headers map with the updated cache control
    final newHeaders = Map<String, String>.from(response.headers);
    newHeaders[HttpHeaders.cacheControlHeader] = cacheControl.toString();

    return OnResponse.next(response.copyWith(headers: newHeaders));
  }
}

Compatibility

See http_client_interceptor for how to use this package with popular Dart http packages, like Chopper, Dio, Retrofit, http_image_provider and other http comppatible packages.

Libraries

http_client_cache