简体中文 | 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.

Libraries

koi_network
Koi Network 企业级网络请求库。 Koi Network is an enterprise-grade networking library.