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

An enterprise-grade, Dio-free networking foundation for Flutter: pluggable transport adapters, JWT auto-refresh with request queueing, smart retries, circuit breaker, TTL cache with stale-while-revali [...]

flutter_network_plus #

ci License: MIT

An enterprise-grade networking foundation for Flutter. Not a Dio wrapper: flutter_network_plus owns its transport through a small HttpClientAdapter abstraction, which makes SSL pinning, mocking, and hermetic testing first-class rather than bolted on.

final client = NetworkClient(
  environments: EnvironmentRegistry.single(
    NetworkEnvironment(name: 'prod', baseUrl: 'https://api.example.com'),
  ),
);

final result = await client.get<User>('users/42', decoder: User.fromJson);

switch (result) {
  case NetworkSuccess(:final value):
    print('Hello, ${value.name}');
  case NetworkFailure(:final exception):
    print('Request failed: $exception');
}

Why #

Most Flutter networking stacks are a Dio instance plus a folder of interceptors copy-pasted between projects. That works until you need single-flight token refresh, a circuit breaker, an offline queue that survives restarts, and a certificate pin — at which point the interceptor soup becomes the hardest code in the app to reason about.

flutter_network_plus provides those pieces as composable, individually testable units behind one NetworkClient facade, with a Dart 3 sealed result type instead of exceptions-as-control-flow.

Features #

Area What you get
Auth JWT/OAuth refresh flow, single-flight refresh, pending-request queueing, proactive expiry refresh, secure token storage
Verbs GET / POST / PUT / PATCH / DELETE, multipart upload with progress, resumable download manager
Reliability Retry policy with exponential backoff + jitter, Retry-After, circuit breaker, per-phase timeouts
Caching Memory + disk stores, TTL, five cache policies incl. stale-while-revalidate
Offline Persistent request queue, connectivity-aware auto-sync
Security SSL pinning (certificate + SPKI), HMAC request signing, credential-redacting cURL
DX cURL generator, pluggable logging, live network inspector / API timeline, mock adapter, environment switching
Monitoring Analytics events, Crashlytics hooks, OpenTelemetry-style observers

Architecture #

Everything above the socket is pure Dart. The only component that touches a real connection is an HttpClientAdapter; swap in MockHttpClientAdapter and the entire pipeline (auth, retry, cache, circuit breaker, offline) runs deterministically in unit tests.

NetworkClient  ──►  RequestExecutor  ──►  Interceptor pipeline  ──►  HttpClientAdapter
   (facade)          (retry loop,          Headers → user → Signing        │
                      breaker guard,        → Auth → Cache → Offline    IoHttpClientAdapter
                      observability)        (reverse on the way back)   MockHttpClientAdapter

The design follows Clean Architecture and SOLID: each concern is an interface (TokenStorage, CacheStore, ConnectivityMonitor, HttpClientAdapter, RequestSigner, NetworkObserver) with a default implementation you can replace. It is DI-framework-agnostic — plain constructor injection, no dependency on GetX, get_it, or Riverpod.

Installation #

dependencies:
  flutter_network_plus: ^0.1.0

Requires Dart 3.5+ and Flutter 3.35+. Mobile and desktop are supported; web is planned for a future release.

Usage #

Configure a client #

final client = NetworkClient(
  environments: EnvironmentRegistry(
    environments: [
      NetworkEnvironment(name: 'staging', baseUrl: 'https://staging.api.example.com'),
      NetworkEnvironment(name: 'prod', baseUrl: 'https://api.example.com'),
    ],
    initial: 'prod',
  ),
  timeout: const TimeoutConfig(connect: Duration(seconds: 10), receive: Duration(seconds: 30)),
  retryPolicy: const RetryPolicy(maxAttempts: 3),
  logger: const PrintNetworkLogger(),
);

Authentication with auto-refresh #

final client = NetworkClient(
  environments: environments,
  auth: AuthConfig(
    storage: SecureTokenStorage(),
    refresher: (current, transport) async {
      final result = await transport.post<Map<String, Object?>>(
        'oauth/token',
        body: {'grant_type': 'refresh_token', 'refresh_token': current.refreshToken},
      );
      final json = result.getOrThrow();
      return AuthTokenPair(
        accessToken: json['access_token']! as String,
        refreshToken: json['refresh_token'] as String?,
        expiresAt: DateTime.now().add(Duration(seconds: json['expires_in']! as int)),
      );
    },
    onAuthFailure: () => appRouter.goToLogin(),
  ),
);

Ten requests hitting a 401 at once trigger exactly one refresh; the other nine wait for it and retry with the fresh token.

Caching #

final client = NetworkClient(
  environments: environments,
  cache: CacheConfig(
    store: TieredCacheStore(
      fast: MemoryCacheStore(),
      slow: await DiskCacheStore.open(),
    ),
    defaultTtl: const Duration(minutes: 5),
  ),
);

// Instant load from cache, refreshed in the background.
await client.get<Feed>('feed',
    decoder: Feed.fromJson, cachePolicy: CachePolicy.staleWhileRevalidate);

Reliability #

final client = NetworkClient(
  environments: environments,
  retryPolicy: const RetryPolicy(
    maxAttempts: 4,
    baseDelay: Duration(milliseconds: 300),
    jitter: 0.25,
  ),
  circuitBreaker: CircuitBreaker(failureThreshold: 5, resetTimeout: Duration(seconds: 30)),
);

Offline queue #

final client = NetworkClient(
  environments: environments,
  offline: OfflineConfig(
    store: await FileOfflineQueueStore.open(),
    connectivity: ConnectivityPlusMonitor(),
  ),
);

// Accepted while offline and replayed FIFO when connectivity returns.
await client.post<void>('events', body: event, queueIfOffline: true);

Uploads and downloads #

await client.upload<void>('avatar', body: MultipartBody(files: [
  MultipartFile.fromBytes(field: 'file', filename: 'a.png', bytes: bytes),
]), onProgress: (p) => print(p.fraction));

final task = client.download('files/report.pdf', savePath: '/tmp/report.pdf');
task.progress.listen((p) => print(p.fraction));
await task.pause();
await task.resume();

Security #

final client = NetworkClient(
  environments: environments,
  sslPinning: SslPinningConfig(spkiSha256Pins: {'e3b0c44298fc1c14...'}),
  signer: HmacRequestSigner(secret: utf8.encode(mySecret), keyId: 'v1'),
);

Developer experience #

final inspector = NetworkInspector();
final client = NetworkClient(
  environments: environments,
  inspector: inspector,
  observers: [AnalyticsNetworkObserver((event, params) => analytics.log(event, params))],
);

inspector.events.listen(print);          // live API timeline
print(CurlGenerator.generate(request));  // copy-paste reproduction

Testing #

MockHttpClientAdapter exercises the full pipeline without a socket:

final adapter = MockHttpClientAdapter()
  ..onGet('/users/*', (call) => MockResponse.json({'id': 1, 'name': 'Ada'}))
  ..onPost('/login', (call) => MockResponse.json({'token': 'x'}, statusCode: 201));

final client = NetworkClient(environments: env, adapter: adapter);
final result = await client.get<User>('users/1', decoder: User.fromJson);

expect(result.getOrThrow().name, 'Ada');
expect(adapter.capturedRequests, hasLength(1));

Example app #

The example/ app demonstrates cached lists with pull-to- refresh, a live inspector timeline, resumable downloads, and runtime environment switching.

cd example && flutter run

License #

MIT — see LICENSE.

0
likes
130
points
81
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

An enterprise-grade, Dio-free networking foundation for Flutter: pluggable transport adapters, JWT auto-refresh with request queueing, smart retries, circuit breaker, TTL cache with stale-while-revalidate, offline queue, SSL pinning, request signing, mocking, and a live network inspector.

Repository (GitHub)
View/report issues

Topics

#network #http #cache #offline #interceptor

License

MIT (license)

Dependencies

connectivity_plus, crypto, flutter, flutter_secure_storage, meta, path_provider

More

Packages that depend on flutter_network_plus