jh_network_core
A small, business-agnostic Dio client builder for applications that need shared network defaults with repository-level overrides.
The package focuses on client assembly rather than API definitions. It works with plain Dio, Retrofit, or any library that accepts a Dio instance.
Features
- Dynamic base URL and request headers
- Shared configuration with repository-level overrides
- Configurable header merge precedence
- Provider-specific response decoding
- Business response validation
- Network error classification and custom messages
- Logging hooks without a logging framework dependency
- Custom Dio interceptor support
- Pure Dart implementation with no Flutter dependency
Installation
dependencies:
jh_network_core: ^0.1.0
Basic usage
import 'package:jh_network_core/jh_network_core.dart';
final factory = NetworkClientFactory(
NetworkClientConfig(
baseUrlProvider: () => environment.apiBaseUrl,
headersProvider: (request) async => {
'accept-language': localeProvider(),
'authorization': await tokenProvider(),
},
networkErrorMessageProvider: (kind, error) => switch (kind) {
NetworkErrorKind.noConnection => 'No internet connection.',
NetworkErrorKind.timeout => 'The request timed out.',
_ => null,
},
),
);
final dio = factory.create();
Providers are evaluated for every request, so environment, locale, and authentication changes do not require rebuilding the client.
Repository-level overrides
Create clients with shared application defaults and local differences:
final loginDio = factory.create(
overrides: NetworkClientOverrides(
baseUrlProvider: () => environment.loginBaseUrl,
headersProvider: (_) => {'x-api-module': 'login'},
receiveTimeout: const Duration(seconds: 30),
),
);
final sportDio = factory.create(
overrides: NetworkClientOverrides(
baseUrlProvider: () => environment.sportBaseUrl,
responseDataDecoder: decodeSportResponse,
businessResponseValidator: validateSportResponse,
),
);
Configuration precedence is:
- Shared
NetworkClientConfig - Repository
NetworkClientOverrides - Per-request Dio options
Shared and repository headers are merged by key. Repository headers override shared headers. Per-request headers override the merged headers by default.
To force configured headers to override per-request values:
const NetworkClientOverrides(
headerMergePolicy: HeaderMergePolicy.commonOverridesRequest,
);
Business responses
HTTP 200 does not always mean business success. Convert unsuccessful business responses into DioException values:
final config = NetworkClientConfig(
baseUrlProvider: () => 'https://api.example.com',
businessResponseValidator: (response) {
final data = response.data;
return data is Map<String, dynamic> && data['code'] == 0;
},
businessErrorMessageProvider: (response) {
final data = response.data;
return data is Map<String, dynamic> ? data['message'] as String? : null;
},
);
Response decoding
Different providers can return different envelopes. Decode the response before business validation:
NetworkClientOverrides(
responseDataDecoder: (data, request) => decodeProviderEnvelope(data),
businessResponseValidator: (response) {
final envelope = response.data as ProviderEnvelope;
return envelope.success;
},
);
Retrofit
Pass the configured Dio instance to a Retrofit-generated API:
final dio = factory.create(overrides: sportOverrides);
final api = SportApi(dio);
jh_network_core intentionally does not depend on Retrofit, state management, dependency injection, localization, or a logging framework.
Error messages
The package classifies transport and HTTP failures as NetworkErrorKind. Applications provide user-facing messages, keeping localization outside the networking package.
Additional information
Issues and feature requests are tracked on GitHub. Contributions are welcome through pull requests.
This package is distributed under the MIT License.
Libraries
- jh_network_core
- Configurable, business-agnostic Dio client infrastructure.