koi_network 0.0.5 copy "koi_network: ^0.0.5" to clipboard
koi_network: ^0.0.5 copied to clipboard

Enterprise-grade network library built on Dio with configurable response parsing, request encoding, token refresh, retry, caching, and adapter-based architecture.

简体中文 | English

Koi Network #

pub package License: MIT

A flexible Dio-based networking library for Dart and Flutter projects.

koi_network provides a reusable network layer with adapter-based integration, configurable response parsing, request execution helpers, token refresh, retry, caching, and multi-module Dio management.

Why Koi Network #

Many projects need the same network capabilities, but do not want to bind the network layer to a specific UI framework, state management solution, or backend response format.

koi_network solves that by separating infrastructure from project-specific logic:

  • Adapter-based auth, loading, error handling, platform, logging, parsing, dynamic headers, and request encoding
  • Works with custom response envelopes such as {code, msg, data} or other backend formats
  • Built-in request execution patterns for normal, silent, quick, batch, and retry flows
  • Proactive and reactive token refresh support
  • Optional retry and cache support through Dio middleware
  • Support for both raw Dio responses and already typed API wrappers

Installation #

Add the package to your pubspec.yaml:

dependencies:
  koi_network: ^0.0.5

Then install dependencies:

dart pub get

If you use Flutter, flutter pub get also works.

Minimal Setup #

The smallest working setup is:

  1. Register adapters
  2. Initialize the network layer
  3. Get a Dio instance and make requests
import 'package:koi_network/koi_network.dart';

Future<void> setupNetwork() async {
  KoiNetworkAdapters.register(
    authAdapter: KoiDefaultAuthAdapter(),
    errorHandlerAdapter: KoiDefaultErrorHandlerAdapter(),
    loadingAdapter: KoiDefaultLoadingAdapter(),
    platformAdapter: KoiDefaultPlatformAdapter(),
    loggerAdapter: KoiDefaultLoggerAdapter(),
  );

  await KoiNetworkInitializer.initialize(
    baseUrl: 'https://api.example.com',
    environment: 'development',
  );
}

After initialization:

final dio = KoiNetworkServiceManager.instance.mainDio;

final profile = await KoiRequestExecutor.execute<Map<String, dynamic>>(
  request: () => dio.get('/user/profile'),
);

Dynamic Headers #

Use headerBuilders when a project needs to inject request-specific headers without hard-coding business rules inside the network package:

await KoiNetworkInitializer.initialize(
  baseUrl: 'https://api.example.com',
  environment: 'production',
  headerBuilders: [
    (options) async => {'X-Tenant': 'school-a'},
  ],
);

Custom Adapters #

In real projects, you usually replace the default adapters with application implementations.

TLS Defaults #

KoiNetworkConfig.create() and KoiNetworkConfig.production() validate SSL certificates by default. Development and testing configs keep certificate validation disabled for local or self-signed services.

If a migration must temporarily keep the old insecure behavior, opt in explicitly:

await KoiNetworkInitializer.initialize(
  baseUrl: 'https://api.example.com',
  environment: 'production',
  validateCertificate: false,
);

Example auth adapter with JWT support:

import 'package:dio/dio.dart';
import 'package:koi_network/koi_network.dart';

class MyAuthAdapter extends KoiAuthAdapter with KoiJwtTokenMixin {
  String? _token;
  String? _refreshToken;

  @override
  String? getToken() => _token;

  @override
  String? getRefreshToken() => _refreshToken;

  @override
  Future<bool> refresh() async {
    try {
      final dio = KoiDioFactory.createTokenDio(null);
      final response = await dio.post(
        '/auth/refresh',
        data: {'refresh_token': getRefreshToken()},
      );

      final accessToken = response.data['access_token'] as String?;
      if (accessToken == null || accessToken.isEmpty) {
        return false;
      }

      await saveToken(accessToken);
      return true;
    } catch (_) {
      return false;
    }
  }

  @override
  Future<void> saveToken(String token) async {
    _token = token;
  }

  @override
  Future<void> saveRefreshToken(String refreshToken) async {
    _refreshToken = refreshToken;
  }

  @override
  Future<void> clearToken() async {
    _token = null;
    _refreshToken = null;
  }
}

class MyErrorHandler extends KoiErrorHandlerAdapter {
  @override
  void showError(String message) {
    print('Error: $message');
  }

  @override
  Future<bool> handleAuthError({int? statusCode, String? message}) async {
    // For example: clear session and redirect to login
    return true;
  }

  @override
  String formatErrorMessage(DioException error) {
    return error.message ?? error.toString();
  }
}

Request Execution #

KoiRequestExecutor is the main entry point for standard Dio requests.

Parse JSON into a model #

final user = await KoiRequestExecutor.execute<User>(
  request: () => dio.get('/user/profile'),
  fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);

Silent request #

final settings = await KoiRequestExecutor.executeSilent<Map<String, dynamic>>(
  request: () => dio.get('/settings'),
);

Quick request #

final notifications = await KoiRequestExecutor.executeQuick<List<dynamic>>(
  request: () => dio.get('/notifications'),
);

Batch request #

final results = await KoiRequestExecutor.executeBatch<Map<String, dynamic>>(
  [
    () => dio.get('/user/profile'),
    () => dio.get('/user/permissions'),
    () => dio.get('/dashboard'),
  ],
  options: const BatchRequestOptions(
    concurrent: true,
    showLoading: true,
  ),
);

Retry at the application layer #

final criticalData = await KoiRequestExecutor.executeWithRetry<MyData>(
  request: () => dio.get('/critical-data'),
  fromJson: (json) => MyData.fromJson(json as Map<String, dynamic>),
  maxRetries: 3,
  delay: const Duration(seconds: 2),
);

Use the Mixin #

For controllers or business classes that repeatedly issue requests, KoiNetworkRequestMixin provides a simpler API.

import 'package:dio/dio.dart';
import 'package:koi_network/koi_network.dart';

class UserController with KoiNetworkRequestMixin {
  UserController(this._dio);

  final Dio _dio;

  Future<void> loadProfile() async {
    await universalRequest<Map<String, dynamic>>(
      request: () => _dio.get('/user/profile'),
      onSuccess: (data) => print('Profile: $data'),
    );
  }
}

Common helpers:

  • universalRequest
  • silentRequest
  • quickRequest
  • batchRequest
  • retryRequest

Typed Response Support #

If your API layer already returns typed response wrappers, implement KoiTypedResponse<T> and use KoiTypedRequestExecutor.

class BaseResult<T> implements KoiTypedResponse<T> {
  BaseResult({
    required this.code,
    required this.message,
    required this.data,
  });

  @override
  final int? code;

  @override
  final String? message;

  @override
  final T? data;

  @override
  bool get isSuccess => code == 200 || code == 0;
}
final user = await KoiTypedRequestExecutor.execute<User>(
  request: () => userApi.getProfile(),
);

Token Refresh #

koi_network supports two refresh paths:

  • Proactive refresh before token expiration
  • Reactive refresh after authentication failures

Recommended JWT setup:

  • implement KoiAuthAdapter
  • mix in KoiJwtTokenMixin
  • use KoiDioFactory.createTokenDio(null) inside refresh()
  • add login and refresh endpoints to tokenRefreshWhiteList

Example:

await KoiNetworkInitializer.initialize(
  baseUrl: 'https://api.example.com',
  enableProactiveTokenRefresh: true,
  tokenRefreshWhiteList: ['/auth/login', '/auth/refresh'],
);

Multi-Module Support #

You can initialize more than one backend module in the same app.

await KoiNetworkInitializer.initialize(
  baseUrl: 'https://api-common.example.com',
  key: 'main',
);

await KoiNetworkInitializer.initialize(
  baseUrl: 'https://api-orders.example.com',
  key: 'orders',
);

final ordersDio =
    KoiNetworkServiceManager.instance.getModuleDio('orders');

Using with Retrofit / Swagger #

koi_network is designed as infrastructure — it does not generate API clients. For type-safe endpoint definitions, pair it with retrofit and optionally a Swagger/OpenAPI code generator.

Swagger/OpenAPI doc
       ↓  (code generation)
┌──────────────────────┐
│  API Client Layer    │  ← LoginApi, OrderApi (Retrofit annotations)
│  ApiClient aggregate │  ← groups all APIs into one entry point
├──────────────────────┤
│  koi_network         │  ← Dio management, interceptors, token refresh,
│                      │     KoiTypedRequestExecutor
├──────────────────────┤
│  Dio                 │  ← HTTP transport
└──────────────────────┘

Step 1: Define a Retrofit API #

import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';

part 'user_api.g.dart';

@RestApi()
abstract class UserApi {
  factory UserApi(Dio dio, {String? baseUrl}) = _UserApi;

  @GET('/api/v1/user/profile')
  Future<BaseResult<UserProfile>> getProfile();

  @POST('/api/v1/user/update')
  Future<BaseResult<bool>> updateProfile(@Body() UpdateProfileRequest req);
}

Step 2: Create an API client aggregate #

class MyApiClient {
  MyApiClient(Dio dio)
      : user = UserApi(dio),
        order = OrderApi(dio);

  final UserApi user;
  final OrderApi order;
}

Step 3: Wire it up with koi_network #

final dio = KoiNetworkServiceManager.instance.mainDio;
final api = MyApiClient(dio);

// Use KoiTypedRequestExecutor for automatic error handling
final profile = await KoiTypedRequestExecutor.execute<UserProfile>(
  request: () => api.user.getProfile(),
);

Swagger / OpenAPI code generation #

If your backend provides a Swagger doc, use a code generator to auto-create the Retrofit API classes and model files:

# Example with swagger_generator_flutter
dart run swagger_generator_flutter generate --all
flutter pub run build_runner build --delete-conflicting-outputs

Generated files typically output to:

  • lib/api/ — Retrofit API interfaces
  • lib/api_models/ — Request/response model classes

Tip: koi_network stays completely decoupled from the generator. You can switch generators, hand-write APIs, or mix both — the Dio instance and request executors work the same way.

Main Public APIs #

  • KoiNetworkAdapters
  • KoiNetworkInitializer
  • KoiNetworkServiceManager
  • KoiRequestExecutor
  • KoiTypedRequestExecutor
  • KoiNetworkRequestMixin
  • KoiNetworkConfig
  • KoiAuthAdapter
  • KoiResponseParser
  • KoiRequestEncoder

Documentation #

License #

MIT. See LICENSE.

0
likes
160
points
201
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Enterprise-grade network library built on Dio with configurable response parsing, request encoding, token refresh, retry, caching, and adapter-based architecture.

Repository (GitHub)
View/report issues

Topics

#dio #http #networking #interceptors #api

License

MIT (license)

Dependencies

dio, dio_cache_interceptor, dio_smart_retry, json_annotation, pretty_dio_logger

More

Packages that depend on koi_network