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

A production-grade Flutter networking runtime for Dio with environments, encryption, isolate parsing, retries, caching, offline queues, transfers, and observability.

Nexio #

The networking runtime for Flutter.

Nexio is a production-grade networking package for Flutter applications on Android and iOS. It uses Dio as its HTTP engine and adds the coordination layer that large applications repeatedly rebuild: runtime environments, interceptor-driven context, encryption, typed parsing, isolate-aware JSON decoding, retries, caching, offline replay, cancellation, uploads, downloads, request scheduling, logging, and monitoring.

Nexio is not a custom HTTP implementation and it does not hide Dio from advanced use cases. It provides one predictable request lifecycle while keeping backend-specific policy in your application.

Use Nexio When #

  • your app has development, QA, staging, regional, tenant, or production APIs;
  • headers and session context must be evaluated before every request;
  • large JSON responses can cause dropped Flutter frames;
  • payment, telecom, self-care, marketplace, or enterprise flows need explicit retry, cache, cancellation, priority, and idempotency decisions;
  • you need CBC/GCM payload envelopes that match a cooperating backend;
  • multiple callers should share an identical in-flight request;
  • you want request timing, lifecycle events, network state, and optional Chucker inspection from one runtime.

What Remains App-Owned #

Nexio deliberately does not define your token format, refresh endpoint, secure storage, certificate-pinning policy, analytics backend, route navigation, or UI design. It provides hooks so those decisions remain testable and specific to your backend.

Features #

Area Included
Transport Dio-powered GET, POST, PUT, PATCH, DELETE, multipart upload, and file download
Environments Any number of named environments, runtime switching, per-request base URL override, stable Dio pool per environment
Interceptors Built-in context, encryption, logging, and conditional Chucker interceptors plus app-owned factories
Encryption AES-CBC, AES-GCM, no encryption, and pluggable custom ciphers
Parsing JSON, string, bytes, XML, custom parsers, registered typed models, automatic background JSON decoding
Resilience Fixed/exponential retries, cancellation, priorities, concurrency limits, in-flight deduplication
Caching Memory and disk cache, TTL, network-only, cache-only, cache-first, and network-first policies
Connectivity Online/offline streams and optional persisted offline request replay
Transfers Multipart progress and cancellation; download progress, pause, resume, and cancel
UI hooks Optional global/per-request loaders without imposing an app design
Observability Lifecycle events, console/in-memory logs, Chucker, response timing, and aggregate health outcomes

Request Pipeline #

Nexio call
  -> priority scheduler and in-flight deduplication
  -> environment URL and context headers
  -> app interceptors
  -> optional request encryption
  -> Dio asynchronous network I/O
  -> optional response decryption
  -> automatic main/background JSON decoding
  -> typed parser
  -> cache, events, health counters, and timing metrics

Installation #

dependencies:
  nexio: ^0.1.0
flutter pub get

Import the single public library:

import 'package:nexio/nexio.dart';

60-Second Start #

Initialize once before the app starts making requests:

void main() {
  Nexio.initialize(
    environments: const {
      'dev': NexioEnvironment(baseUrl: 'https://dev.example.com'),
      'production': NexioEnvironment(baseUrl: 'https://api.example.com'),
    },
    initialEnvironment: 'dev',
  );

  runApp(const MyApp());
}

Make a typed request:

final response = await Nexio.get<List<User>>(
  '/users',
  parser: parseUsers,
);

final users = response.data;
print('Loaded ${users.length} users');
print('Total time: ${response.metrics.totalDuration}');

Future<List<User>> parseUsers(Object? input) async {
  final items = input! as List<Object?>;
  return items
      .cast<Map<String, Object?>>()
      .map(User.fromJson)
      .toList();
}

NexioResponse<T> also exposes status, headers, cache origin, Dio request options, and network/decrypt/parse/total timing metrics.

Complete Initialization #

All settings have conservative defaults. Enable only the capabilities your app uses:

Nexio.initialize(
  environments: const {
    'local': NexioEnvironment(baseUrl: 'http://10.0.2.2:8080'),
    'qa-east': NexioEnvironment(
      baseUrl: 'https://qa-east.example.com',
      headers: {'X-Region': 'east'},
    ),
    'uat': NexioEnvironment(baseUrl: 'https://uat.example.com'),
    'production': NexioEnvironment(baseUrl: 'https://api.example.com'),
  },
  initialEnvironment: 'qa-east',
  defaultHeaders: const {'Accept': Headers.jsonContentType},
  loggerEnabled: true,
  enableChucker: true,
  defaultLogInChucker: false,
  defaultThreadMode: ThreadMode.auto,
  parseThresholdKb: 64,
  retryPolicy: const RetryPolicy(retries: 2),
  cacheConfig: const CacheConfig(
    defaultTtl: Duration(minutes: 5),
  ),
  maxConcurrentRequests: 6,
  offlineQueueEnabled: true,
  interceptorFactories: [
    () => CorrelationIdInterceptor(),
  ],
);

Environments #

Environment names belong to your application. Switch without reinitializing:

Nexio.switchEnvironment('production');

print(Nexio.currentEnvironment);
print(Nexio.currentEnvironmentConfig.baseUrl);

Override one request without creating another Dio instance:

final response = await Nexio.get<Map<String, Object?>>(
  '/status',
  baseUrlOverride: 'https://status.example.com',
  parser: parseMap,
);

Nexio captures the selected environment when a request enters the scheduler. Later environment changes affect future calls, not already queued calls.

Request Configuration #

final response = await Nexio.post<PaymentReceipt>(
  '/payments',
  data: payment.toJson(),
  headers: {'X-Idempotency-Key': payment.idempotencyKey},
  encryptionMode: EncryptionMode.aesGcm,
  threadMode: ThreadMode.auto,
  retryPolicy: const RetryPolicy(retries: 0),
  cachePolicy: CachePolicy.networkOnly,
  priority: RequestPriority.high,
  deduplicate: false,
  cancelGroup: 'payments',
  showLoader: true,
  logInChucker: false,
  parser: PaymentReceipt.parse,
);

Use Nexio.request<T> when the HTTP method or advanced Dio options are chosen dynamically.

Registered Parsers #

Register parsers used across many endpoints:

Nexio.registerParser<User>((input) async {
  return User.fromJson(Map<String, Object?>.from(input! as Map));
});

final user = (await Nexio.get<User>('/me')).data;

The parser receives already decrypted and JSON-decoded data.

Await and Isolates #

await Nexio.get(...) does not block Flutter's UI isolate. Dio performs asynchronous network I/O; await suspends only the current async function while the event loop continues rendering frames and processing input.

Isolates solve a different problem: CPU work. Nexio's ThreadMode.auto measures raw JSON size and uses Flutter's compute for decoding when the configured threshold is crossed. ThreadMode.background always moves JSON decoding; ThreadMode.main keeps it on the current isolate.

final catalog = await Nexio.get<List<Item>>(
  '/large-catalog',
  threadMode: ThreadMode.auto,
  parseThresholdKb: 32,
  parser: Item.parseList,
);

The network wait and the isolate result are both Futures, so callers correctly use await for both. See the threading guide.

Encryption #

Configure backend-compatible keys centrally and select a mode per request:

Nexio.initialize(
  environments: environments,
  initialEnvironment: 'production',
  encryptionConfig: EncryptionConfig(
    aesCbcKey: secureConfig.cbcKey,
    aesCbcIv: secureConfig.cbcIv,
    aesGcmKey: secureConfig.gcmKey,
  ),
);

final receipt = await Nexio.post<PaymentReceipt>(
  '/payments',
  data: payment.toJson(),
  encryptionMode: EncryptionMode.aesGcm,
  logInChucker: false,
  parser: PaymentReceipt.parse,
);

Request flow is serialize then encrypt then send. Response flow is receive then decrypt then parse. Local payload encryption complements TLS; it does not replace TLS. Never hardcode production secrets in source code. See the encryption guide and security guide.

Authentication Hooks #

Nexio can coordinate one app-owned refresh operation while other protected requests wait:

authConfig: NexioAuthConfig(
  headersProvider: () => {
    'Authorization': 'Bearer ${session.accessToken}',
    'X-Gateway-Token': session.gatewayToken,
  },
  decide: (signal) {
    if (signal.statusCode == 401) {
      return NexioAuthDecision.refreshAndRetry;
    }
    return NexioAuthDecision.proceed;
  },
  refresh: (_) => session.refreshTokens(),
  onSessionExpired: (_) => session.expire(),
),

Your callback owns credentials, storage, endpoint selection, and token parsing. See authentication hooks.

Retries, Cache, Priority, and Deduplication #

final offers = await Nexio.get<List<Offer>>(
  '/offers',
  retryPolicy: const RetryPolicy(
    retries: 3,
    strategy: RetryStrategy.exponential,
  ),
  cachePolicy: CachePolicy.networkFirst,
  cacheTtl: const Duration(minutes: 15),
  cacheKeyExtra: {
    'apiVersion': offersVersion,
    'country': countryCode,
  },
  priority: RequestPriority.normal,
  parser: Offer.parseList,
);

Identical in-flight requests are deduplicated by default. Disable deduplication for analytics or transaction calls that the backend must receive separately. Do not retry non-idempotent operations unless the backend contract and an idempotency key make the retry safe.

Cancellation #

final token = CancelToken();

final request = Nexio.get<User>(
  '/profile',
  cancelToken: token,
  cancelTag: 'profile',
  cancelGroup: 'account',
);

token.cancel('Screen closed');
Nexio.cancelTag('profile');
Nexio.cancelGroup('account');

Uploads and Downloads #

final uploaded = await Nexio.upload<Map<String, Object?>>(
  '/documents',
  files: const [
    NexioUploadFile(
      fieldName: 'document',
      path: '/path/to/document.pdf',
      contentType: 'application/pdf',
    ),
  ],
  onSendProgress: (sent, total) => print('$sent / $total'),
  logInChucker: false,
  parser: parseMap,
);
final task = Nexio.download(
  '/reports/monthly.pdf',
  destinationPath: '/path/to/monthly.pdf',
  onProgress: (received, total) => print('$received / $total'),
);

await task.pause();
await task.resume();
final savedPath = await task.completed;

Download resume requires server support for HTTP range requests.

Logging and Chucker #

Enable Chucker capability globally and opt in only safe requests:

Nexio.initialize(
  environments: environments,
  initialEnvironment: 'qa',
  enableChucker: true,
  defaultLogInChucker: false,
);

await Nexio.get('/catalog', logInChucker: true);
await Nexio.post('/payments', data: payment, logInChucker: false);

Attach ChuckerFlutter.navigatorKey to MaterialApp, then call Nexio.showLogs(context). Keep payment, PIN, OTP, identity, and KYC traffic out of inspection tools.

Events, Connectivity, and Health #

final eventSubscription = Nexio.events.listen((event) {
  switch (event) {
    case NexioRequestFailedEvent():
      reportFailure(event);
    case NexioUnauthorizedEvent():
      openSignInOnce();
    default:
      break;
  }
});

final onlineSubscription = Nexio.online.listen((_) => retryVisibleWork());
final snapshot = Nexio.healthSnapshot;
print('Observed outcomes: ${snapshot.total}');
await Nexio.flushHealth();

Health aggregation stores endpoint paths and outcomes, not hosts, query strings, headers, tokens, or payloads.

Error Handling #

try {
  await Nexio.get<User>('/me');
} on NexioHttpException<User> catch (error) {
  print(error.response.statusCode);
} on NexioOfflineQueuedException catch (error) {
  print('Queued as ${error.queueId}');
} on NexioException catch (error) {
  print(error.message);
}

See the error guide for the full exception model.

Production Rules #

  • Use HTTPS in every non-local environment.
  • Provision encryption material outside source control and rotate it with the backend.
  • Keep defaultLogInChucker: false; opt in only non-sensitive debug traffic.
  • Use idempotency keys and deliberate retry policies for transactions.
  • Namespace cache keys by API version, tenant, locale, country, and account when those values change response meaning.
  • Decide whether offline replay is valid for each backend operation before enabling it globally.
  • Run backend-specific integration tests on Android and iOS.

Review the production checklist before shipping.

Examples #

Guides #

Supported Platforms #

Nexio 0.1.0 supports Flutter applications on Android and iOS. Web and desktop platforms are not part of the V1 compatibility contract.

Release Status #

0.1.0 is the first public release. The API is documented and tested, but applications must still validate their backend contracts, encryption envelopes, authentication decisions, retry safety, cache policy, and offline behavior.

Contributing and Security #

See CONTRIBUTING.md for development and pull-request checks. Report vulnerabilities according to SECURITY.md; never place tokens, encryption material, personal data, or exploit details in a public issue.

License #

Nexio is available under the MIT License.

0
likes
160
points
210
downloads

Documentation

API reference

Publisher

verified publishersyedmubashirali.com

Weekly Downloads

A production-grade Flutter networking runtime for Dio with environments, encryption, isolate parsing, retries, caching, offline queues, transfers, and observability.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#networking #dio #http #cache #chucker

License

MIT (license)

Dependencies

chucker_flutter, connectivity_plus, crypto, cryptography, dio, encrypt, flutter, path_provider, synchronized, uuid, xml

More

Packages that depend on nexio