dio_service 1.0.0  dio_service: ^1.0.0 copied to clipboard
dio_service: ^1.0.0 copied to clipboard
A powerful reusable Dio-based networking layer with token refresh, caching, retry logic, and unified result wrappers.
dio_service #
Production-ready, reusable networking layer built on top of Dio with:
- Strongly-typed ApiResult<T>success/failure wrapper
- Token injection via TokenProviderand automatic 401 refresh viaTokenRefresher
- Drift-backed HTTP caching through dio_cache_interceptor
- Unified get/post/put/patch/deleteAPIs with optionalqueryandheaders
- Small, testable surface via the DioServiceinterface
Install #
Add to your pubspec.yaml:
dependencies:
  dio_service: ^1.0.0
Quick start #
import 'package:dio_service/dio_service.dart';
// If needed, import the concrete client implementation
import 'package:dio_service/src/dio_service_client.dart';
Future<void> main() async {
  // Provide token hooks if your API is authenticated
  final DioService client = await DioServiceClent.create(
    baseUrl: 'https://api.example.com',
    getToken: () async => 'initialAccessToken',
    refreshToken: (expired) async {
      // Your refresh logic here
      return 'newAccessToken';
    },
  );
  // GET with query and custom headers
  final users = await client.get<List<dynamic>>(
    '/users',
    query: {'page': 1},
    headers: {'X-Trace-Id': 'abc-123'},
  );
  if (users.isSuccess) {
    print('Users: ${users.data}');
  } else {
    print('Error: ${users.error} (${users.statusCode})');
  }
}
API overview #
All methods return ApiResult<T>:
class ApiResult<T> {
  final T? data;
  final String? error;
  final int? statusCode;
  bool get isSuccess => error == null;
}
Available calls on the DioService interface:
Future<ApiResult<T>> get<T>(
  String path, {
  Map<String, dynamic>? query,
  Map<String, String>? headers,
});
Future<ApiResult<T>> post<T>(
  String path, {
  dynamic data,
  Map<String, dynamic>? query,
  Map<String, String>? headers,
});
Future<ApiResult<T>> put<T>(
  String path, {
  dynamic data,
  Map<String, dynamic>? query,
  Map<String, String>? headers,
});
Future<ApiResult<T>> patch<T>(
  String path, {
  dynamic data,
  Map<String, dynamic>? query,
  Map<String, String>? headers,
});
Future<ApiResult<T>> delete<T>(
  String path, {
  dynamic data,
  Map<String, dynamic>? query,
  Map<String, String>? headers,
});
Examples #
Basic CRUD:
final DioService api = await DioServiceClent.create(baseUrl: 'https://api.example.com');
// Create
final createRes = await api.post<Map<String, dynamic>>(
  '/items',
  data: {'name': 'Item A'},
);
// Read with query + headers
final listRes = await api.get<List<dynamic>>(
  '/items',
  query: {'page': 1},
  headers: {'X-Env': 'staging'},
);
// Update
final updateRes = await api.put<Map<String, dynamic>>(
  '/items/1',
  data: {'name': 'Item A (updated)'},
);
// Partial update
final patchRes = await api.patch<Map<String, dynamic>>(
  '/items/1',
  data: {'status': 'archived'},
);
// Delete with optional payload or query
final deleteRes = await api.delete<Map<String, dynamic>>(
  '/items/1',
  query: {'force': true},
);
// Handle results
for (final r in [createRes, listRes, updateRes, patchRes, deleteRes]) {
  if (r.isSuccess) {
    print('OK: ${r.data}');
  } else {
    print('ERR: ${r.error} (${r.statusCode})');
  }
}
Token handling (401 auto-refresh):
final DioService secureApi = await DioServiceClent.create(
  baseUrl: 'https://api.example.com',
  getToken: () async => 'access-token',
  refreshToken: (expired) async {
    // Exchange refresh token or expired token for a new access token
    return 'new-access-token';
  },
);
final res = await secureApi.get<Map<String, dynamic>>('/me');
Caching #
Caching is provided by dio_cache_interceptor with a Drift-backed store. Responses are transparently cached according to standard cache headers. A SQLite database is created in the application documents directory.
Testing #
In tests, redirect the documents directory to a temp folder and disable Drift multi-database warnings to avoid noise:
import 'dart:io';
import 'package:drift/drift.dart' show driftRuntimeOptions;
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
class FakePathProviderPlatform extends PathProviderPlatform {
  FakePathProviderPlatform(this.path);
  final String path;
  @override
  Future<String?> getApplicationDocumentsPath() async => path;
}
setUpAll(() {
  driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
});
setUp(() async {
  final dir = await Directory.systemTemp.createTemp('test');
  PathProviderPlatform.instance = FakePathProviderPlatform(dir.path);
});
License #
MIT