karna_network 0.1.0 copy "karna_network: ^0.1.0" to clipboard
karna_network: ^0.1.0 copied to clipboard

A stale-while-revalidate (SWR) caching layer for Dio: serves cached GET responses instantly, revalidates in the background, dedupes concurrent requests, and falls back to stale data on network failure.

karna_network #

A lightweight, robust networking toolkit for Dio. It adds client-side resilience and performance patterns out of the box: Stale-While-Revalidate (SWR) caching, automatic token refresh, retry with circuit breaking, offline mutation queuing, request batching, and error normalization.


Features #

  • ⚑ SWR Caching: Serves cached responses instantly, revalidates in the background, dedupes in-flight calls, and supports ETag / 304 Not Modified.
  • πŸ’Ύ Pluggable Persistence: Built-in in-memory LRU (MemoryCacheStore) and disk persistence with Hive CE (HiveCacheStore).
  • πŸ” Auth & Single-Flight Refresh: Attaches tokens and refreshes once on 401 across concurrent requests before retrying.
  • πŸ›‘οΈ Retry & Circuit Breaker: Exponential backoff for network errors/5xx and fast-failing circuit breaker to protect struggling endpoints.
  • πŸ“¦ Request Coalescing: Batches multiple simultaneous single-item requests (BatchCollector) into a single API call.
  • πŸ“΄ Offline Mutation Queue: Queues non-GET requests when offline and replays them in order when connectivity returns (HiveMutationQueueStore).
  • 🎯 Normalized Errors: Consistent AppError shape across all failure types.

Installation #

Add karna_network to your pubspec.yaml:

dependencies:
  karna_network: ^0.1.0

Quick Start #

Wire the interceptors into your Dio instance (order matters: Auth β†’ Retry β†’ SWR β†’ Error normalization):

import 'package:dio/dio.dart';
import 'package:karna_network/karna_network.dart';

late final Dio dio;

// 1. Configure components
final auth = AuthInterceptor(
  dioProvider: () => dio,
  tokenProvider: myTokenProvider, // implements TokenProvider
);

final retry = RetryInterceptor(
  dioProvider: () => dio,
  circuitBreaker: CircuitBreaker(failureThreshold: 3),
  maxRetries: 2,
);

final swr = SwrInterceptor(
  dioProvider: () => dio,
  store: MemoryCacheStore(maxEntries: 200), // or HiveCacheStore(box)
  defaultOptions: const SwrOptions(
    freshDuration: Duration(seconds: 30),
    staleDuration: Duration(minutes: 5),
  ),
);

final errors = ErrorNormalizingInterceptor();

// 2. Attach to Dio
dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
  ..interceptors.addAll([auth, retry, swr, errors]);

Core Guides #

1. SWR Caching & Persistence #

GET requests return cached data immediately if within freshDuration. If stale (within staleDuration), cached data is served instantly while fetching fresh data in the background.

// Fast in-memory cache
final store = MemoryCacheStore(maxEntries: 200);

// Or persistent disk cache with Hive CE (no TypeAdapter needed)
final box = await Hive.openBox<Map<dynamic, dynamic>>('swr_cache');
final store = HiveCacheStore(box);

// Per-request options:
final response = await dio.get<dynamic>(
  '/profile',
  options: Options(
    extra: {
      swrExtraKey: const SwrOptions(
        freshDuration: Duration(minutes: 1),
        staleDuration: Duration(hours: 1),
      ),
    },
  ),
);

print(response.extra['fromCache']); // true / false

Listen to background revalidation events:

swr.events.listen((event) {
  switch (event) {
    case SwrRevalidating():
      // Show subtle "refreshing" indicator
    case SwrRevalidated(:final data):
      // Update UI with fresh payload
    case SwrRevalidationFailed(:final error):
      // Stale data remains visible; log or ignore
  }
});

2. Auth & Token Refresh #

Implement TokenProvider. When any request returns 401, concurrent requests share a single refresh call before retrying:

class MyTokenProvider implements TokenProvider {
  @override
  Future<String?> getAccessToken() async => secureStorage.read(key: 'jwt');

  @override
  Future<String?> refreshAccessToken() async {
    // Refresh token once; return new token or null
    return await authApi.refreshToken();
  }
}

3. Error Normalization #

Catches upstream Dio errors and wraps them in a unified AppError:

try {
  await dio.get<dynamic>('/dashboard');
} on DioException catch (e) {
  final appError = e.error as AppError;
  print(appError.kind); // timeout, network, unauthorized, badResponse, etc.
  print(appError.message);
  print(appError.retryable);
}

4. Offline Mutation Queue #

Queue POST/PUT/DELETE requests when offline and replay them upon reconnection:

final box = await Hive.openBox<Map<dynamic, dynamic>>('offline_queue');

final queue = OfflineMutationQueue(
  dio: dio,
  store: HiveMutationQueueStore(box),
  isOnline: () async => checkConnectivity(),
);

try {
  await queue.send('POST', '/todos', data: {'title': 'Buy groceries'});
} on MutationQueuedException {
  // Saved locally; notify user "Saved, will sync when online"
}

// When internet is restored:
await queue.flush();

5. Request Coalescing (Batching) #

Prevents multiple UI widgets from firing individual requests for items in the same frame:

final userBatcher = BatchCollector<Map<String, dynamic>>(
  batchFetcher: (ids) async {
    final res = await dio.get<dynamic>('/users', queryParameters: {'ids': ids.join(',')});
    final list = (res.data as List).cast<Map<String, dynamic>>();
    return {for (final u in list) u['id'] as String: u};
  },
);

// Multiple callers in the same frame trigger only ONE network call:
final user = await userBatcher.load('123');

License #

MIT

0
likes
150
points
84
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A stale-while-revalidate (SWR) caching layer for Dio: serves cached GET responses instantly, revalidates in the background, dedupes concurrent requests, and falls back to stale data on network failure.

Repository (GitHub)
View/report issues

Topics

#dio #cache #swr #networking #http

License

MIT (license)

Dependencies

dio, hive_ce

More

Packages that depend on karna_network