🚀 DioExtended

A thin, result-based wrapper over dio that makes networking in Flutter clean, safe, and boilerplate-free.


pub version pub points license platform


DioExtended returns a consistent ApiResult<T> for every request, so error handling and JSON parsing stay simple and predictable. Optional Chucker helpers are exposed from a separate import so the core package stays platform-neutral.


📑 Table of Contents

Section Description
✨ Features What you get out of the box
📦 Installation Add the package to your project
⚡ Quick Start Up and running in 30 seconds
🌐 DioExtended The core networking client
🔐 Token Refresh Automatic 401 handling
📳 ShakeForChucker Debug by shaking your phone
❓ FAQ Common questions answered

✨ Features

Feature Description
🧩 Simplified API Every call returns a clean, typed ApiResult<T> interface.
🔄 Automatic JSON Parsing Decode responses into your models with a simple parseData function.
🔐 Built-in Token Refresh Override one method to auto-handle expired tokens and retry requests.
📳 Shake for Debugging Open the network inspector with a shake gesture — perfect for QA.
🧱 FormData Safe Correctly separates Content-Type handling for FormData vs JSON, even on retry.

📦 Installation

Add dio_extended to your pubspec.yaml:

dependencies:
  dio_extended: ^2.0.0

Then run:

flutter pub get

Note

Chucker helpers are available from package:dio_extended/diox_chucker.dart.

Important

Since 2.0.0, Chucker APIs are no longer exported from package:dio_extended/diox.dart. If you use ShakeForChucker, ShakeChuckerConfigs, or createChuckerInterceptor(), add a separate import for package:dio_extended/diox_chucker.dart.


⚡ Quick Start

import 'package:dio_extended/diox.dart';

class CrudService extends DioExtended {
  CrudService() : super(baseUrl: 'https://jsonplaceholder.typicode.com');

  Future<ApiResult<List<PostModel>>> getPosts() {
    return callApiRequest<List<PostModel>>(
      request: () => get('/posts'),
      parseData: (data) =>
          (data as List).map((e) => PostModel.fromJson(e)).toList(),
    );
  }
}

// ...

final result = await CrudService().getPosts();
if (result.isSuccess) {
  print(result.data); // 🎉 Typed list of PostModel
} else {
  print(result.message); // ⚠️ Friendly error message
}

🌐 DioExtended: Simplified Networking

DioExtended is the core of this package. It streamlines HTTP requests and returns a consistent ApiResult<T> object for all calls, making error handling and data parsing straightforward.

🔧 Initialization

Set up your API client with a base URL and default headers. Use headers for synchronous values and headersAsync when headers need async preparation.

import 'package:dio_extended/diox.dart';

// Static headers
final api = DioExtended(
  baseUrl: 'https://api.example.com',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
);

// Async headers (e.g. fetched from secure storage)
final api = DioExtended(
  baseUrl: 'https://api.example.com',
  headersAsync: _buildAuthHeaders(),
);

static Future<Map<String, String>?> _buildAuthHeaders() async {
  await Future.delayed(const Duration(seconds: 3)); // fetch simulation
  return {
    'Authorization': 'Bearer your_token_here',
    'Custom-Header': 'CustomValue',
  };
}

// Access the underlying Dio instance directly when needed
final dioInstance = api.dio;

Prefer a dedicated, independent service? Just extend DioExtended:

class CrudService extends DioExtended {
  CrudService() : super(baseUrl: 'YOUR-BASE-URL');
  // All DioExtended functions are now available here.
}
📥 GET Request Example

Provide a parseData function to map the JSON response into your model.

/// Returns an [ApiResult] with a list of [PostModel] on success.
Future<ApiResult<List<PostModel>>> getPosts() async {
  return await callApiRequest<List<PostModel>>(
    request: () => get('/posts'),
    parseData: (data) => (data as List)
        .map((itemJson) => PostModel.fromJson(itemJson))
        .toList(),
  );
}

/// Single-object variant.
Future<ApiResult<PostModel>> getPost() async {
  return await callApiRequest<PostModel>(
    request: () => get('/posts/1'),
    parseData: (data) => PostModel.fromJson(data),
  );
}

Using callApiRequest handles fetching and parsing. On the business-logic side, just check isSuccess:

final result = await _service.getPosts();
if (result.isSuccess) {
  // ✅ Your logic here — result.data is fully typed
}

🔐 Token Refresh (Optional)

To handle automatic token refresh, simply override handleTokenExpired. The library calls this callback when a request fails with a 401 status (or a custom code via tokenExpiredCode), then retries the original request.

Tip

  • The interceptor safely separates Content-Type behavior for FormData and non-FormData requests, including on retry.
  • In handleTokenExpired, return auth-related headers only (e.g. Authorization). Avoid setting a global Content-Type from refresh headers.
class CrudService extends DioExtended {
  CrudService()
      : super(
          baseUrl: 'https://jsonplaceholder.typicode.com',
          tokenExpiredCode: 401,
        );

  /// Override to fetch a new auth token when the current one expires.
  @override
  Future<dynamic> handleTokenExpired() async {
    final newHeader = await fetchNewAuth();
    // Return as a Map, e.g. {'Authorization': 'Bearer xxx'}
    return newHeader;
  }
}

📳 ShakeForChucker: Debug with a Shake

ShakeForChucker integrates with chucker_flutter to open the network inspection UI whenever you shake the device — ideal for developers and QA testers.

⚙️ Setup

Wrap your MaterialApp with ShakeForChucker, and attach ShakeChuckerConfigs.navigatorKey to your app's navigatorKey.

import 'package:flutter/material.dart';
import 'package:dio_extended/diox_chucker.dart';

void main() {
  // Initialize Chucker BEFORE runApp().
  ShakeChuckerConfigs.initialize(
    showOnRelease: true,
    showNotification: true,
  );

  runApp(
    ShakeForChucker(
      // Number of shakes needed to trigger Chucker (default: 3)
      shakeCountTriggered: 3,
      child: MaterialApp(
        title: 'DioExtended Demo',
        // Required so Chucker can show its inspector reliably.
        navigatorKey: ShakeChuckerConfigs.navigatorKey,
        home: const MyHomePage(),
      ),
    ),
  );
}

If you also want requests from DioExtended to appear in Chucker, register the optional interceptor:

import 'package:dio_extended/diox.dart';
import 'package:dio_extended/diox_chucker.dart';

final api = DioExtended(
  baseUrl: 'https://api.example.com',
  interceptors: [createChuckerInterceptor()],
);

Important

The older navigatorObservers: [ShakeChuckerConfigs.navigatorObserver] approach is now deprecated. Use navigatorKey instead — it works reliably even with nested navigators.


❓ FAQ

What does callApiRequest return on error?

It returns an ApiResult<T> with isSuccess == false and a human-readable message. No exceptions are thrown for expected network/HTTP failures, so you can branch safely on isSuccess.

Can I still access the raw Dio instance?

Yes. Every DioExtended exposes the underlying client via api.dio, so you can add interceptors, configure timeouts, or call advanced Dio APIs directly.

Does Chucker run in release builds?

Only if you opt in. Set showOnRelease: true in ShakeChuckerConfigs.initialize(...). By default Chucker is intended for debug/QA usage.


Made with ❤️ for the Flutter community.

⭐ If this package helps you, consider starring the repository!

Libraries

diox
A powerful Flutter library that enhances Dio with a simplified API response handler.
diox_chucker
models/api_result