nio 0.0.3 copy "nio: ^0.0.3" to clipboard
nio: ^0.0.3 copied to clipboard

A powerful, developer-friendly networking layer built on top of Dio. Less code, typed results, auto auth refresh, retries, caching, offline queue — all in one. By Niral.

example/example.dart

// ignore_for_file: avoid_print, unused_local_variable

// ---------------------------------------------------------------
//  Nio — Complete Usage Example
// ---------------------------------------------------------------
//
// This file shows every major feature of Nio so you can copy-paste
// the parts you need into your own project.
//
// Topics covered:
//   1. Setup (NioConfig)
//   2. GET  — single object & list
//   3. POST / PUT / PATCH / DELETE
//   4. Auth & token refresh
//   5. Retry & caching
//   6. File upload & download
//   7. Result handling patterns
//   8. Custom error handlers
//   9. Testing with mocks
//  10. Offline request queue (memory + JSON file)
// ---------------------------------------------------------------

import 'dart:io';

import 'package:nio/nio.dart';

// ════════════════════════════════════════════════════════════════════
// STEP 1 — Create your model classes (normal Dart / Flutter models)
// ════════════════════════════════════════════════════════════════════

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  /// Standard fromJson factory — Nio calls this for you.
  factory User.fromJson(Map<String, dynamic> json) => User(
        id: json['id'] as int,
        name: json['name'] as String,
        email: json['email'] as String,
      );

  Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}

class Post {
  final int id;
  final String title;

  Post({required this.id, required this.title});

  factory Post.fromJson(Map<String, dynamic> json) => Post(
        id: json['id'] as int,
        title: json['title'] as String,
      );
}

// ════════════════════════════════════════════════════════════════════
// STEP 2 — Set up Nio (once, usually in main.dart or a DI container)
// ════════════════════════════════════════════════════════════════════

/// Simulated token storage (use SharedPreferences / secure storage in real apps).
String? _token = 'my-access-token';

Nio createNio() {
  return Nio(
    config: NioConfig(
      // ── Base URL ──────────────────────────────────────────────
      baseUrl: 'https://jsonplaceholder.typicode.com',

      // ── Timeouts ─────────────────────────────────────────────
      connectTimeout: const Duration(seconds: 15),
      receiveTimeout: const Duration(seconds: 15),

      // ── Global headers (sent with every request) ─────────────
      headers: {
        'X-App-Version': '1.0.0',
      },

      // ── Auth: provide your token ─────────────────────────────
      tokenProvider: () async => _token,

      // ── Auth: refresh logic (called automatically on 401) ────
      refreshToken: () async {
        // In a real app you would call your refresh-token endpoint
        // and save the new token.
        print('🔄 Refreshing token...');
        _token = 'new-refreshed-token';
      },

      // ── API response unwrapping ──────────────────────────────
      //
      // If your API wraps every response like:
      //   { "status": 200, "data": { ... }, "message": "OK" }
      //
      // Uncomment the line below so fromJson only sees the inner object:
      //
      // responseExtractor: (json) => json['data'],

      // ── Global error callback (logging / analytics) ──────────
      onError: (error) {
        print('❌ Global error: ${error.type} — ${error.message}');
      },

      // ── Optional UI callback (show Snackbar, toast, etc.) ────
      showError: (message) {
        print('🔔 Show to user: $message');
      },

      // ── Custom status code → error mapping ───────────────────
      statusErrors: {
        429: (data) => const NioError(
              type: NioErrorType.unknown,
              message: 'Too many requests — slow down!',
              statusCode: 429,
            ),
      },

      // ── Logging (only prints in debug mode) ──────────────────
      enableLogging: true,

      // ── Default options for every request ─────────────────────
      defaultOptions: const NioOptions(
        requiresAuth: true,
        showErrorMessage: true,
      ),
    ),
  );
}

// ════════════════════════════════════════════════════════════════════
// STEP 3 — Use Nio to call APIs
// ════════════════════════════════════════════════════════════════════

Future<void> main() async {
  final nio = createNio();

  // ── GET single object ────────────────────────────────────────────
  print('\n=== GET single user ===');
  final userResult = await nio.get<User>(
    '/users/1',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
  );

  userResult.when(
    success: (user) => print('✅ Got user: ${user.name} (${user.email})'),
    failure: (err) => print('❌ Failed: ${err.userMessage}'),
  );

  // ── GET list of objects ──────────────────────────────────────────
  print('\n=== GET list of users ===');
  final usersResult = await nio.getList<User>(
    '/users',
    fromJson: User.fromJson,
  );

  usersResult.when(
    success: (users) => print('✅ Got ${users.length} users'),
    failure: (err) => print('❌ Failed: ${err.message}'),
  );

  // ── GET with query parameters ────────────────────────────────────
  print('\n=== GET with query params ===');
  final postsResult = await nio.getList<Post>(
    '/posts',
    fromJson: Post.fromJson,
    queryParameters: {'userId': 1},
  );

  postsResult.when(
    success: (posts) => print('✅ User 1 has ${posts.length} posts'),
    failure: (err) => print('❌ ${err.message}'),
  );

  // ── POST — create a resource ─────────────────────────────────────
  print('\n=== POST create post ===');
  final createResult = await nio.post<Post>(
    '/posts',
    body: {'title': 'Hello from Nio', 'body': 'This is easy!', 'userId': 1},
    fromJson: (json) => Post.fromJson(json as Map<String, dynamic>),
  );

  createResult.when(
    success: (post) => print('✅ Created post #${post.id}: ${post.title}'),
    failure: (err) => print('❌ ${err.message}'),
  );

  // ── PUT — update a resource ──────────────────────────────────────
  print('\n=== PUT update post ===');
  final updateResult = await nio.put<Post>(
    '/posts/1',
    body: {'id': 1, 'title': 'Updated title', 'body': 'Updated', 'userId': 1},
    fromJson: (json) => Post.fromJson(json as Map<String, dynamic>),
  );

  updateResult.when(
    success: (post) => print('✅ Updated: ${post.title}'),
    failure: (err) => print('❌ ${err.message}'),
  );

  // ── DELETE ────────────────────────────────────────────────────────
  print('\n=== DELETE post ===');
  final deleteResult = await nio.delete('/posts/1');

  if (deleteResult.isSuccess) {
    print('✅ Deleted successfully');
  }

  // ── With retry (retries up to 2 times on network/5xx errors) ─────
  print('\n=== GET with retry ===');
  final retryResult = await nio.get<User>(
    '/users/1',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
    options: const NioOptions(
      requiresAuth: true,
      maxRetries: 2,
    ),
  );

  print('Retry result: ${retryResult.isSuccess ? "success" : "failed"}');

  // ── With caching ─────────────────────────────────────────────────
  print('\n=== GET with caching ===');
  final cachedResult = await nio.get<User>(
    '/users/1',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
    options: const NioOptions(
      cache: true,
      cacheTtl: Duration(minutes: 10),
    ),
  );

  // Second call will be served from cache (no network request).
  final cachedAgain = await nio.get<User>(
    '/users/1',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
    options: const NioOptions(cache: true),
  );

  print('Cached result: ${cachedAgain.isSuccess ? "from cache!" : "failed"}');

  // ── Without auth (public endpoint) ───────────────────────────────
  print('\n=== Public endpoint (no auth) ===');
  final publicResult = await nio.get(
    '/posts/1',
    options: const NioOptions(requiresAuth: false),
  );

  print('Public result: ${publicResult.isSuccess}');

  // ── Cancel a request ─────────────────────────────────────────────
  print('\n=== Cancel request ===');
  final cancelToken = CancelToken();
  final cancelFuture = nio.get<User>(
    '/users/1',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
    options: NioOptions(cancelToken: cancelToken),
  );
  cancelToken.cancel('User navigated away');
  final cancelResult = await cancelFuture;

  cancelResult.when(
    success: (_) => print('Got data'),
    failure: (err) => print('Cancelled: ${err.type}'),
  );

  // ── Advanced result handling ─────────────────────────────────────
  print('\n=== Advanced result patterns ===');

  // dataOrNull — returns null on failure
  final name = userResult.dataOrNull?.name;
  print('Name (nullable): $name');

  // dataOrThrow — throws NioError on failure
  try {
    final user = userResult.dataOrThrow;
    print('Name (throws): ${user.name}');
  } on NioError catch (e) {
    print('Error: ${e.userMessage}');
  }

  // map — transform the success data
  final nameResult = userResult.map((user) => user.name);
  print('Mapped: ${nameResult.dataOrNull}');

  // maybeWhen — handle only what you care about
  userResult.maybeWhen(
    success: (user) => print('Got ${user.name}'),
    orElse: () => print('Something else happened'),
  );

  // ── Testing with mocks ──────────────────────────────────────────
  print('\n=== Mock support ===');

  nio.mock('/test/user', data: {'id': 99, 'name': 'Mock User', 'email': 'mock@test.com'});

  final mockResult = await nio.get<User>(
    '/test/user',
    fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
  );

  mockResult.when(
    success: (user) => print('✅ Mock user: ${user.name}'),
    failure: (err) => print('❌ ${err.message}'),
  );

  nio.clearMocks();

  // ── Offline queue — custom storage (no SharedPreferences) ───────
  // Use MemoryOfflineQueueStorage for tests / short-lived sessions, or
  // createOfflineFileQueueStorage('/path/queue.json') on VM / native Flutter.
  // Each queued row stores [PendingOfflineRequest.apiBaseUrl] so replay hits
  // the same host you used when the request was created.
  print('\n=== Offline queue (connection error) ===');
  final offlineMem = MemoryOfflineQueueStorage();
  final deadHostNio = Nio(
    config: NioConfig(
      baseUrl: 'http://127.0.0.1:1',
      connectTimeout: const Duration(milliseconds: 400),
      receiveTimeout: const Duration(milliseconds: 400),
      offlineQueue: OfflineQueueSettings(
        storage: offlineMem,
        defaultQueueWhenOffline: true,
        onRequestQueued: (req) => print('📥 Queued: ${req.method} ${req.path}'),
      ),
    ),
  );
  final offlineResult = await deadHostNio.post(
    '/posts',
    body: {'title': 'Saved while offline', 'userId': 1},
  );
  offlineResult.when(
    success: (_) {},
    failure: (err) {
      if (err.type == NioErrorType.queuedOffline) {
        print('✅ ${err.userMessage}');
      } else {
        print('❌ ${err.message}');
      }
    },
  );
  print('Pending in memory: ${(await offlineMem.loadAll()).length}');

  // Flush sends each item to its saved apiBaseUrl. Here we seed a request that
  // points at JSONPlaceholder so you can see a successful replay on Wi‑Fi.
  print('\n=== Offline queue flush (sample) ===');
  final flushStore = MemoryOfflineQueueStorage();
  await flushStore.saveAll([
    PendingOfflineRequest(
      apiBaseUrl: 'https://jsonplaceholder.typicode.com',
      id: 'example-seed',
      method: 'POST',
      path: '/posts',
      queryParameters: null,
      body: {'title': 'Replayed after offline', 'body': '— Niral', 'userId': 1},
      extraHeaders: null,
      requiresAuth: false,
      createdAtMillis: DateTime.now().millisecondsSinceEpoch,
    ),
  ]);
  final onlineNio = Nio(
    config: NioConfig(
      baseUrl: 'https://jsonplaceholder.typicode.com',
      offlineQueue: OfflineQueueSettings(
        storage: flushStore,
        defaultQueueWhenOffline: false,
      ),
    ),
  );
  final flushed = await onlineNio.flushOfflineQueue();
  print('Flush: ${flushed.succeeded} ok, ${flushed.failed} failed, ${flushed.remaining} left');

  // Optional: persist queue yourself with a plain JSON file (Flutter: pick a path
  // under the app documents directory instead of systemTemp).
  final filePath = '${Directory.systemTemp.path}/nio_example_offline.json';
  final fileBacked = createOfflineFileQueueStorage(filePath);
  await fileBacked.saveAll([
    PendingOfflineRequest(
      apiBaseUrl: 'https://jsonplaceholder.typicode.com',
      id: 'demo-1',
      method: 'POST',
      path: '/posts',
      queryParameters: null,
      body: {'title': 'File queue demo', 'body': 'Niral', 'userId': 1},
      extraHeaders: null,
      requiresAuth: false,
      createdAtMillis: DateTime.now().millisecondsSinceEpoch,
    ),
  ]);
  print('Wrote demo queue to: $filePath');

  print('\n🎉 All examples completed!');
}
1
likes
160
points
17
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A powerful, developer-friendly networking layer built on top of Dio. Less code, typed results, auto auth refresh, retries, caching, offline queue — all in one. By Niral.

Repository (GitHub)
View/report issues

Topics

#http #network #dio

License

MIT (license)

Dependencies

dio

More

Packages that depend on nio