flutter_network_state 1.1.0 copy "flutter_network_state: ^1.1.0" to clipboard
flutter_network_state: ^1.1.0 copied to clipboard

Network-aware state management engine for Flutter. Offline queue, cache strategies, Dio interceptor, auto-sync, and Bloc-friendly sealed states.

flutter_network_state

A network-aware state management and request orchestration engine for Flutter.
Not just another connectivity checker.

pub version Dart 3 Flutter License: MIT


What is this? #

flutter_network_state is a decision engine that sits between your app and the network. It unifies:

  • 🌐 Online/Offline detection β€” stream-based, always reactive
  • πŸ” Real internet check β€” DNS lookup verification, not just interface detection
  • 🎯 Smart request strategies β€” NetworkFirst, CacheFirst, CacheOnly, NetworkOnly
  • πŸ“¦ In-memory caching β€” with TTL and pluggable storage
  • πŸ“‹ Offline request queue β€” failed requests are queued and replayed automatically
  • πŸ”„ Automatic sync β€” queue drains when connectivity is restored
  • πŸ”Œ Dio interceptor β€” drop-in auto-retry and offline queueing
  • 🧱 Reactive widgets β€” NetworkBuilder and ConnectivityBuilder for instant UI binding
  • 🧩 State management ready β€” built-in support for Provider, Cubit, Bloc, and Riverpod
  • πŸ” Retry policies β€” exponential, linear, constant backoff with jitter

Think of it as Dio + Bloc + Offline Sync + Connectivity in one clean abstraction.


Getting Started #

Installation #

dependencies:
  flutter_network_state: ^1.0.0
flutter pub get

Minimal Example #

import 'package:flutter_network_state/flutter_network_state.dart';

final manager = NetworkManager();

// Listen to state changes
manager.stateStream.listen((state) {
  switch (state) {
    case Idle()    => print('Ready');
    case Loading() => print('Loading...');
    case Offline() => print('Offline (${state.queuedRequests} queued)');
    case Syncing() => print('Syncing ${(state.progress * 100).toStringAsFixed(0)}%');
    case Success() => print('Data: ${state.data}');
    case Error()   => print('Error: ${state.message}');
  }
});

// Make a request with strategy
final user = await manager.request(
  () => api.getUser(),
  cacheKey: 'user',
  strategy: CacheFirst(),
);

That's it. The manager handles connectivity detection, caching, offline queueing, and automatic sync β€” all behind that single request() call.


Core Concepts #

Network States #

All states are Dart 3 sealed classes β€” fully exhaustive in switch:

State Description
Idle No operation in progress
Loading A request is executing
Offline Device has no connectivity. Includes queuedRequests count
Syncing Queue is being replayed after reconnect. Includes progress (0.0–1.0)
Success<T> Request completed. Contains data and fromCache flag
Error Request failed. Contains message, exception, and isRetryable

Request Strategies #

Control how each request is resolved:

Strategy Behavior
NetworkFirst() Try network β†’ fall back to cache on failure (default)
CacheFirst() Try cache β†’ fall back to network on miss or expiry
CacheOnly() Only use cache β€” never hits the network
NetworkOnly() Only use network β€” never reads or writes cache
// Cache-first with custom TTL
final config = await manager.request(
  () => api.getAppConfig(),
  cacheKey: 'app_config',
  strategy: CacheFirst(),
  cacheTtl: Duration(hours: 1),
);

// Network-only for mutations
await manager.request(
  () => api.updateProfile(data),
  strategy: NetworkOnly(),
);

State Management Integration #

flutter_network_state ships with built-in support for the most popular Flutter state management solutions β€” without adding any of them as dependencies. Pick the one you use:

Provider (ChangeNotifier) #

Use NetworkNotifier, a ready-made ChangeNotifier:

// Setup
ChangeNotifierProvider(
  create: (_) => NetworkNotifier(networkManager),
  child: MyApp(),
);

// In your widget
class UserPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final notifier = context.watch<NetworkNotifier>();

    return switch (notifier.state) {
      Idle()    => Text('Tap to load'),
      Loading() => CircularProgressIndicator(),
      Offline() => Text('Offline (${(notifier.state as Offline).queuedRequests} queued)'),
      Success() => Text('Hello, ${(notifier.state as Success).data}'),
      Error()   => Text('Error: ${(notifier.state as Error).message}'),
      _         => SizedBox.shrink(),
    };
  }
}

// Trigger a request
final notifier = context.read<NetworkNotifier>();
await notifier.request(
  () => api.getUser(),
  cacheKey: 'user',
  strategy: CacheFirst(),
);

Cubit (No flutter_bloc needed) #

Use NetworkCubit, a lightweight Cubit-like base class:

class UserCubit extends NetworkCubit {
  UserCubit(NetworkManager manager) : super(manager);

  Future<void> loadUser() async {
    await executeRequest(
      () => api.getUser(),
      cacheKey: 'user',
      strategy: CacheFirst(),
    );
  }

  Future<void> updateProfile(Map<String, dynamic> data) async {
    await executeRequest(
      () => api.updateProfile(data),
      strategy: NetworkOnly(),
    );
  }
}

// Usage
final cubit = UserCubit(networkManager);

// Listen to state changes
cubit.stream.listen((state) {
  switch (state) {
    case Success<User>() => print('Got user: ${state.data.name}');
    case Loading()       => print('Loading...');
    case Offline()       => print('Offline');
    case Error()         => print('Error: ${state.message}');
    _                    => null;
  }
});

await cubit.loadUser();

// Don't forget to close
await cubit.close();

Bloc (flutter_bloc) #

If you already use flutter_bloc, use the executeAndEmit() extension:

class UserCubit extends Cubit<NetworkState> {
  UserCubit(this._manager) : super(const Idle());
  final NetworkManager _manager;

  Future<void> loadUser() async {
    await _manager.executeAndEmit(
      () => api.getUser(),
      emit: emit,
      cacheKey: 'user',
      strategy: CacheFirst(),
    );
  }
}

// Or bind the global connectivity state stream
class ConnectivityCubit extends Cubit<NetworkState> {
  ConnectivityCubit(this._manager) : super(const Idle());
  final NetworkManager _manager;
  late final StreamSubscription _sub;

  void init() {
    _sub = _manager.bindToEmit(emit);
  }

  @override
  Future<void> close() {
    _sub.cancel();
    return super.close();
  }
}

Riverpod #

Use NetworkStateNotifier with Riverpod providers:

// 1. Provide the NetworkManager
final networkManagerProvider = Provider<NetworkManager>((ref) {
  final manager = NetworkManager();
  ref.onDispose(() => manager.dispose());
  return manager;
});

// 2. Stream the global connectivity state
final networkStateProvider = StreamProvider<NetworkState>((ref) {
  return ref.watch(networkManagerProvider).stateStream;
});

// 3. Create feature-specific notifiers
final userProvider = Provider<NetworkStateNotifier>((ref) {
  final notifier = ref.watch(networkManagerProvider).createNotifier();
  ref.onDispose(() => notifier.dispose());
  return notifier;
});

// In your widget
class UserPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userNotifier = ref.watch(userProvider);

    return switch (userNotifier.state) {
      Loading() => CircularProgressIndicator(),
      Success() => Text('${(userNotifier.state as Success).data}'),
      Offline() => Text('Offline'),
      Error()   => Text('Error'),
      _         => ElevatedButton(
        onPressed: () => ref.read(userProvider).execute(
          () => api.getUser(),
          cacheKey: 'user',
          strategy: CacheFirst(),
        ),
        child: Text('Load User'),
      ),
    };
  }
}

Stream Transformer (Any architecture) #

Turn any Stream<T> into Stream<NetworkState>:

final stateStream = myDataStream.asNetworkStates();
// Each emission becomes Success<T>, errors become Error

Dio Integration #

Drop the interceptor into any existing Dio instance:

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));

dio.interceptors.add(
  NetworkDioInterceptor(
    monitor: manager.monitor,
    queue: manager.queue,
    config: DioInterceptorConfig(
      retryPolicy: RetryPolicy(
        maxRetries: 3,
        backoff: ExponentialBackoff(baseDelay: Duration(seconds: 1)),
      ),
      queueWhenOffline: true,
      logRequests: true,
      logResponses: true,
    ),
  ),
);

The interceptor automatically:

  • βœ… Retries transient failures (timeouts, 500s, 502s, 503s, 429s)
  • βœ… Queues requests when the device is offline
  • βœ… Replays queued requests when connectivity is restored

Retry Policies #

Every retry policy supports configurable backoff:

// Exponential backoff with jitter (default)
RetryPolicy(
  maxRetries: 3,
  backoff: ExponentialBackoff(
    baseDelay: Duration(seconds: 1),
    maxDelay: Duration(seconds: 30),
    withJitter: true, // Β±25% to prevent thundering herd
  ),
);

// Constant delay
RetryPolicy(
  maxRetries: 5,
  backoff: ConstantBackoff(duration: Duration(seconds: 2)),
);

// Linear backoff
RetryPolicy(
  maxRetries: 4,
  backoff: LinearBackoff(baseDelay: Duration(seconds: 1)),
);

// Disable retries
RetryPolicy.none;

Custom retry predicate:

RetryPolicy(
  maxRetries: 3,
  shouldRetry: (error, attempt) {
    return error is TimeoutException;
  },
);

Cache Management #

Basic operations #

// Store
manager.cache.set('key', myData, ttl: Duration(hours: 1));

// Retrieve (null if expired)
final data = manager.cache.get<MyModel>('key');

// Check existence (respects TTL)
manager.cache.has('key');

// Invalidate
manager.invalidateCache('key');

// Clear all
manager.clearCache();

Custom storage backend #

Implement CacheStore to use Hive, Isar, SharedPreferences, or any persistence layer:

class HiveCacheStore implements CacheStore {
  final Box _box;
  HiveCacheStore(this._box);

  @override
  dynamic get(String key) => _box.get(key);
  @override
  void set(String key, dynamic value) => _box.put(key, value);
  @override
  void remove(String key) => _box.delete(key);
  @override
  void clear() => _box.clear();
  @override
  bool containsKey(String key) => _box.containsKey(key);
}

final manager = NetworkManager(
  cacheManager: CacheManager(store: HiveCacheStore(box)),
);

Offline Queue & Sync #

Automatic flow #

  1. Request fails while offline β†’ automatically queued
  2. Device reconnects β†’ SyncEngine drains the queue
  3. State stream emits Syncing(progress) β†’ UI shows progress
  4. All done β†’ state returns to Idle

Manual queue operations #

// Enqueue manually
manager.queue.enqueue(
  QueuedRequest(
    id: 'update_profile',
    execute: () => api.updateProfile(data),
    retryPolicy: RetryPolicy(maxRetries: 5),
  ),
);

// Inspect
print('Pending: ${manager.queue.length}');

// Force sync
final results = await manager.syncNow();

// Clear
manager.clearQueue();

Persistent queue (survives app restarts) #

By default, the queue is in-memory and lost on restart. For persistence, use PersistentQueueStore:

import 'package:path_provider/path_provider.dart';

// 1. Create the persistent store
final appDir = await getApplicationDocumentsDirectory();
final store = PersistentQueueStore(
  directory: appDir,
  executor: (request) async {
    // This callback replays persisted requests using your HTTP client
    final response = await dio.request(
      request.url,
      options: Options(method: request.method, headers: request.headers),
      data: request.body,
    );
    return response.data;
  },
);
await store.initialize(); // Load queue from disk

// 2. Inject into NetworkManager
final manager = NetworkManager(
  queue: RequestQueue(store: store),
);

// 3. Enqueue serializable requests (persisted to disk)
await store.enqueueSerializable(SerializableRequest(
  id: 'update_profile',
  method: 'PUT',
  url: 'https://api.example.com/profile',
  headers: {'Authorization': 'Bearer $token'},
  body: jsonEncode({'name': 'John'}),
  cacheKey: 'profile',
  maxRetries: 5,
));

// Queue survives app restarts β€” requests replay automatically on reconnect

Hive queue store #

For apps already using Hive, see doc/hive_queue_store.md β€” copy the implementation into your project:

# pubspec.yaml
dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0
import 'package:hive_flutter/hive_flutter.dart';

await Hive.initFlutter();
final box = await Hive.openBox<String>('network_queue');

final store = HiveQueueStore(
  box: box,
  executor: (req) async {
    return await dio.request(
      req.url,
      options: Options(method: req.method, headers: req.headers),
      data: req.body,
    );
  },
);

final manager = NetworkManager(queue: RequestQueue(store: store));

sqflite queue store #

For apps using SQLite, see doc/sqflite_queue_store.md β€” copy the implementation into your project:

# pubspec.yaml
dependencies:
  sqflite: ^2.3.0
  path: ^1.8.0
final store = await SqfliteQueueStore.open(
  executor: (req) async {
    return await dio.request(
      req.url,
      options: Options(method: req.method, headers: req.headers),
      data: req.body,
    );
  },
);

final manager = NetworkManager(queue: RequestQueue(store: store));

// Don't forget to close when done
await store.close();

Which one should I use?

Store Best for
PersistentQueueStore (built-in) Simple apps, no extra dependencies
HiveQueueStore Apps already using Hive
SqfliteQueueStore Apps needing SQL queries or large queues

Logging #

// Default β€” prints to console
final manager = NetworkManager();

// Silent
final manager = NetworkManager(logger: NetworkLogger.silent);

// Custom β€” forward to Crashlytics, Sentry, etc.
final manager = NetworkManager(
  logger: NetworkLogger.custom(
    (level, message) {
      FirebaseCrashlytics.instance.log('[$level] $message');
    },
    minLevel: LogLevel.warning,
  ),
);

Configuration #

final manager = NetworkManager(
  config: NetworkManagerConfig(
    defaultStrategy: NetworkFirst(),
    defaultCacheTtl: Duration(minutes: 5),
    retryPolicy: RetryPolicy(maxRetries: 3),
    autoStart: true,
  ),
);

Real Internet Check #

connectivity_plus only checks if a network interface is available β€” it doesn't verify actual internet access (captive portals, DNS failures, etc.). InternetChecker solves this:

final checker = InternetChecker();

// One-shot check
final isOnline = await checker.hasInternetAccess();
print('Internet: $isOnline');

// Periodic monitoring (emits only on change)
checker.startPeriodicCheck();
checker.statusStream.listen((isOnline) {
  print('Internet changed: $isOnline');
});

// Custom config
final checker = InternetChecker(
  config: InternetCheckerConfig(
    lookupAddresses: ['google.com', 'cloudflare.com'],
    timeout: Duration(seconds: 5),
    checkInterval: Duration(seconds: 15),
  ),
);

Widgets #

Drop-in widgets that rebuild automatically based on network state:

NetworkBuilder #

NetworkBuilder(
  manager: networkManager,
  builder: (context, state) {
    return switch (state) {
      Idle()    => Text('Ready'),
      Loading() => CircularProgressIndicator(),
      Offline() => Text('Offline (${state.queuedRequests} queued)'),
      Syncing() => LinearProgressIndicator(value: state.progress),
      Success() => Text('Data: ${state.data}'),
      Error()   => Text('Error: ${state.message}'),
    };
  },
)

ConnectivityBuilder #

ConnectivityBuilder(
  manager: networkManager,
  onlineBuilder: (context) => MyMainContent(),
  offlineBuilder: (context, queuedCount) => Column(
    children: [
      Icon(Icons.wifi_off),
      Text('You are offline'),
      Text('$queuedCount requests queued'),
    ],
  ),
)

Architecture #

lib/
 β”œβ”€β”€ core/
 β”‚    β”œβ”€β”€ internet_checker.dart     ← Real internet verification (DNS)
 β”‚    β”œβ”€β”€ network_state.dart        ← Sealed state hierarchy
 β”‚    β”œβ”€β”€ network_status.dart       ← Connectivity monitor
 β”‚    β”œβ”€β”€ network_strategy.dart     ← Request strategies
 β”‚    β”œβ”€β”€ retry_policy.dart         ← Backoff strategies
 β”‚    └── logger.dart               ← Pluggable logger
 β”œβ”€β”€ manager/
 β”‚    └── network_manager.dart      ← Central orchestrator
 β”œβ”€β”€ queue/
 β”‚    └── request_queue.dart        ← Offline request queue
 β”œβ”€β”€ cache/
 β”‚    └── cache_manager.dart        ← TTL cache
 β”œβ”€β”€ sync/
 β”‚    └── sync_engine.dart          ← Auto-sync on reconnect
 β”œβ”€β”€ dio/
 β”‚    └── dio_interceptor.dart      ← Dio interceptor
 β”œβ”€β”€ widgets/
 β”‚    └── network_builder.dart      ← NetworkBuilder & ConnectivityBuilder
 β”œβ”€β”€ extensions/
 β”‚    β”œβ”€β”€ bloc_extension.dart       ← Bloc helpers
 β”‚    β”œβ”€β”€ cubit_extension.dart      ← Standalone Cubit base class
 β”‚    β”œβ”€β”€ provider_extension.dart   ← ChangeNotifier for Provider
 β”‚    └── riverpod_extension.dart   ← StateNotifier for Riverpod
 └── flutter_network_state.dart     ← Barrel export

Every sub-component can be injected individually for testing or customization.


Requirements #

Requirement Version
Dart SDK >=3.0.0 <4.0.0
Flutter >=3.10.0
Null safety βœ…

Dependencies #

Package Purpose
connectivity_plus Platform connectivity detection
dio HTTP client integration
meta Annotation utilities

Note: No dependency on provider, flutter_bloc, or riverpod. All state management integrations are built on plain Dart streams and Flutter SDK primitives.


Comparison #

Feature connectivity_plus dio_cache_interceptor flutter_network_state
Online/Offline detection βœ… ❌ βœ…
Request strategies ❌ Partial βœ… 4 strategies
Cache with TTL ❌ βœ… βœ… + pluggable
Offline queue ❌ ❌ βœ…
Auto sync on reconnect ❌ ❌ βœ…
Retry with backoff ❌ ❌ βœ… 3 strategies
Dio interceptor ❌ βœ… βœ…
Provider support ❌ ❌ βœ…
Cubit support ❌ ❌ βœ…
Bloc support ❌ ❌ βœ…
Riverpod support ❌ ❌ βœ…
Unified state stream ❌ ❌ βœ…

πŸ’– Support #

If this package helps you build better Flutter apps, consider supporting the development:

Support on SociaBuzz

Your support helps keep this package maintained and up-to-date. Every contribution is greatly appreciated! πŸ™


License #

MIT β€” see LICENSE for details.

6
likes
160
points
32
downloads

Documentation

API reference

Publisher

verified publisherridltech.my.id

Weekly Downloads

Network-aware state management engine for Flutter. Offline queue, cache strategies, Dio interceptor, auto-sync, and Bloc-friendly sealed states.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

connectivity_plus, dio, flutter, meta

More

Packages that depend on flutter_network_state