api_network_kit
A batteries-included, no-codegen Dio wrapper for Flutter and Dart. It adds typed response parsing, resilient retry, automatic token refresh, persistent / ETag-aware caching, pluggable pagination, and Web/WASM-ready connectivity checks on top of Dio — with first-class testability.
Why api_network_kit?
The runtime-wrapper segment wins on developer experience, not breadth. This
package is positioned against raw Dio (which lacks the opinionated layer) and
against retrofit/chopper (which demand code-generation and build_runner).
Differentiators
- Zero code-generation — works instantly, no
build_runner. - Automatic 401 token-refresh-and-retry out of the box.
- Persistent + ETag-aware caching that survives restarts (via the
CacheStoreinterface). - Typed exceptions that preserve server error bodies — 422 field-validation errors are reachable.
- Pluggable pagination for any API shape (offset or cursor based).
- Web/WASM-ready with optional, pluggable connectivity checks.
Features
- ✅ Generic Response Parsing — automatic JSON parsing to your models,
including JSON-encoded
Stringbodies. - ✅ Comprehensive Error Handling — typed exceptions for all HTTP status
codes that preserve the server response body (
data,errors,statusCode). - ✅ Automatic Retry — configurable retry with exponential backoff for transient errors.
- ✅ Automatic Token Refresh — inject a
TokenRefresherto refresh expired tokens and retry the original request once on 401. - ✅ Connectivity Verification — pluggable
ConnectivityCheckerwith a no-op default for pure-Dart / Web / WASM. - ✅ Injectable Logging — instance-based
ApiLogger, silencable per-client. - ✅ Token Management — dynamic authorization header update and removal.
- ✅ Pluggable Pagination —
OffsetPaginationStrategyandCursorPaginationStrategywith configurable field keys. - ✅ Response Caching —
CacheStoreinterface with in-memoryApiCache(TTL + ETag), plus request deduplication for concurrent identical GETs. - ✅ Flexible Interceptors — dynamic hooks for request, response, and error modification.
- ✅ File Upload/Download — simple methods with progress callbacks.
Installation
Add this dependency to your project's pubspec.yaml:
dependencies:
api_network_kit: ^1.1.0
Then run:
flutter pub get
Quick Start
Basic Setup
import 'package:api_network_kit/api_network_kit.dart';
final api = ApiKit(baseUrl: 'https://api.example.com');
GET Request with Automatic Parsing
class User {
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
final int id;
final String name;
final String email;
}
final user = await api.get<User>(
'/users/1',
parser: User.fromJson,
);
Error Handling with Server Error Bodies
try {
final user = await api.get<User>('/users/1', parser: User.fromJson);
} on UnauthorizedException {
print('Please log in again.');
} on UnprocessableEntityException catch (e) {
// Field-level validation errors are now reachable:
print(e.errors); // e.g. {'email': ['Already taken']}
} on NoInternetException {
print('Please check your internet connection.');
} on Exception catch (e) {
print('An error occurred: $e');
}
Advanced Configurations
Automatic Token Refresh & Retry
final api = ApiKit(
baseUrl: 'https://api.example.com',
token: initialToken,
tokenRefresher: (error, options) async {
// Call your refresh endpoint and return the new token.
final newToken = await refreshToken();
return newToken;
},
);
On a 401, the interceptor calls tokenRefresher, updates the stored token, and
retries the original request once. Infinite loops are prevented via a
per-request flag.
Configurable Pagination
// Offset-based with custom field keys:
final result = await api.getPaginated<User>(
'/users',
parser: User.fromJson,
paginationConfig: const PaginationConfig(
itemsKey: 'data',
currentPageKey: 'page',
totalPagesKey: 'pages',
totalItemsKey: 'count',
),
);
// Cursor-based:
final result = await api.getPaginated<User>(
'/users',
parser: User.fromJson,
paginationStrategy: const CursorPaginationStrategy(),
);
print(result.nextCursor); // cursor for the next page
Caching with ETag & Deduplication
final api = ApiKit(
baseUrl: 'https://api.example.com',
useCache: true,
cacheMaxAge: 300, // 5 minutes
);
// Concurrent identical GETs share a single in-flight request automatically.
Web/WASM-Ready Connectivity
// Pure-Dart / Web: no-op checker is the default — no native plugin required.
final api = ApiKit(baseUrl: 'https://api.example.com');
// Flutter: inject a connectivity_plus-backed checker if desired.
final api = ApiKit(
baseUrl: 'https://api.example.com',
connectivityChecker: MyConnectivityChecker(),
);
Injectable Logger
final silentLogger = ApiLogger(level: LogLevel.none);
final api = ApiKit(
baseUrl: 'https://api.example.com',
logger: silentLogger,
);
cURL Request Logging
When making HTTP requests, the default logger automatically logs the equivalent cURL command to the console (at info level). This allows developers to copy the request and paste it directly into Postman, Insomnia, or a command-line terminal to test and debug endpoints easily.
Example output:
[ℹ️ INFO 15:54:10] --> POST https://api.example.com/login
[ℹ️ INFO 15:54:10] cURL:
curl --location 'https://api.example.com/login' --request POST \
--header 'Content-Type: application/json' \
--data-raw '{"username":"admin","password":"password"}'
Cache & Retry Settings
final api = ApiKit(
baseUrl: 'https://api.example.com',
token: 'your-initial-auth-token',
maxRetries: 3,
retryDelayBase: 1000, // base delay in ms for exponential backoff
checkConnectivity: true,
useCache: true,
cacheMaxAge: 300, // cache expiration in seconds (5 minutes)
connectTimeout: 30000,
receiveTimeout: 30000,
sendTimeout: 30000,
);
Dynamic Token Updates
api.updateToken('new-user-token');
api.clearToken();
Interceptors
api.addInterceptor(
onRequest: (options) async {
options.headers['X-Custom-Header'] = 'custom-value';
return options;
},
onResponse: (response) async {
print('Response status: ${response.statusCode}');
return response;
},
onError: (error) async {
print('Request failed: ${error.message}');
return error;
},
);
Platform Support
api_network_kit is verified compatible with all six Flutter-supported
targets. The library contains no platform-specific imports (dart:io,
dart:html, Platform.*, kIsWeb) and is not a federated plugin — it is
pure Dart built on Dio, which supports all platforms natively.
| Platform | Compilation | Core methods | File methods | Status |
|---|---|---|---|---|
| Android | ✅ | ✅ | ✅ | Fully supported |
| iOS | ✅ | ✅ | ✅ | Fully supported |
| Web | ✅ | ✅ | ⚠️ Throws | Supported with caveat |
| WASM | ✅ | ✅ | ⚠️ Throws | Supported with caveat |
| Linux | ✅ | ✅ | ✅ | Fully supported |
| macOS | ✅ | ✅ | ✅ | Fully supported |
| Windows | ✅ | ✅ | ✅ | Fully supported |
How Web/WASM compatibility was achieved
In v1.0.x, the package depended on connectivity_plus — a native plugin that
blocked Web and WASM compilation. In v1.1.0, connectivity checking was
decoupled behind the ConnectivityChecker
interface with a NoOpConnectivityChecker
default that always reports a connection. connectivity_plus was moved to
dev_dependencies, so pure-Dart, Web, and WASM consumers no longer pull a
native plugin. Flutter consumers who want real connectivity checks can inject a
connectivity_plus-backed ConnectivityChecker implementation.
Runtime limitation: uploadFile() and downloadFile() on Web/WASM
The uploadFile() and
downloadFile() methods rely on
Dio's MultipartFile.fromFile() and Dio.download() respectively, which
access the filesystem via dart:io under the hood. These two methods will
throw at runtime on Flutter Web and WASM when called. They do not block
compilation and do not affect any other method (get, post, put, delete,
getPaginated, getMap, getList, getRaw, etc. are fully platform-
agnostic).
On Web, use MultipartFile.fromBytes() with post() for uploads instead of
uploadFile().
API Reference
Requests
get<T>()/getPaginated<T>()getMap()/getList()/getRaw()post<T>()/postMap()/postList()put<T>()/patch<T>()/delete<T>()uploadFile<T>()/uploadFiles<T>()/downloadFile()
Exceptions
All exceptions extend ApiBaseException and carry statusCode, data, and
errors from the server response body.
NetworkException,ServerException,NoInternetException,TimeoutException,ApiExceptionBadRequestException(400),UnauthorizedException(401),ForbiddenException(403),NotFoundException(404),ConflictException(409),UnprocessableEntityException(422),TooManyRequestsException(429)
Pagination
PaginationStrategy(interface)OffsetPaginationStrategy,CursorPaginationStrategyPaginationConfig(configurable field keys)
Caching
CacheStore(interface)ApiCache(in-memory, TTL + ETag)RequestDeduplicator
Connectivity
ConnectivityChecker(interface)NoOpConnectivityChecker(default)
For a complete set of features, check the example directory for detailed implementations.
Additional Documentation
CHANGELOG.md— version history and release notes.
Libraries
- api_network_kit
- packages/api_kit/api_kit
- packages/api_kit/core/api_client
- packages/api_kit/core/api_exception
- packages/api_kit/core/api_interceptor
- packages/api_kit/core/cache
- packages/api_kit/core/cache_store
- packages/api_kit/core/connectivity_checker
- packages/api_kit/core/flexible_interceptor
- packages/api_kit/core/logger
- packages/api_kit/core/paginated_response
- packages/api_kit/core/pagination_config
- packages/api_kit/core/token_refresher
- packages/api_kit/utils/response_parser