简体中文 | English
Koi Network
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:
- Register adapters
- Initialize the network layer
- 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:
universalRequestsilentRequestquickRequestbatchRequestretryRequest
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)insiderefresh() - 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.
Recommended architecture
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 interfaceslib/api_models/— Request/response model classes
Tip:
koi_networkstays 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
KoiNetworkAdaptersKoiNetworkInitializerKoiNetworkServiceManagerKoiRequestExecutorKoiTypedRequestExecutorKoiNetworkRequestMixinKoiNetworkConfigKoiAuthAdapterKoiResponseParserKoiRequestEncoder
Documentation
License
MIT. See LICENSE.
Libraries
- koi_network
- Koi Network 企业级网络请求库。 Koi Network is an enterprise-grade networking library.