Infrastructure SDKs
network_sdk Free
- Tier
- Freeversion 2.4.4
- Command
archipelago generate network_sdk- Generates
infrastructure/network_sdk_apiinfrastructure/network_sdk_dio(if selected)infrastructure/network_sdk_http(if selected)
- Depends on
- shared_app_configgenerate these first
HTTP client abstraction with Dio or http implementation.
Variables
| Variable | Type | Default | Description |
|---|---|---|---|
| appName | string | — | The name of your application |
| networkLibrary | enum (dio, http) | dio | The HTTP client library to use |
| isForMonorepo | boolean | true | Whether this is being generated as part of a monorepo |
Usage
Interactive
archipelago generate network_sdkNon-interactive (CI)
archipelago generate network_sdk --config my_config.jsonConfig Template
{
"@appName": "The name of your application",
"appName": "MyApp",
"@networkLibrary": "The HTTP client library to use (dio | http)",
"networkLibrary": "dio",
"@isForMonorepo": "Whether this is being generated as part of a monorepo",
"isForMonorepo": true
}Generated Structure
Always generated:
infrastructure/
└── network_sdk_api/
├── lib/
│ ├── network_sdk_api.dart
│ └── src/
│ ├── auth/
│ │ ├── auth_request_markers.dart
│ │ └── auth_token_provider.dart
│ ├── client/
│ │ └── http_client.dart
│ ├── di/
│ │ └── network_injector_key.dart
│ ├── enums/
│ │ └── http_method.dart
│ ├── exception/
│ │ ├── http_cancelled_exception.dart
│ │ ├── http_exception.dart
│ │ ├── http_network_exception.dart
│ │ ├── http_parse_exception.dart
│ │ ├── http_request_exception.dart
│ │ └── http_timeout_exception.dart
│ ├── interceptor/
│ │ ├── http_interceptor.dart
│ │ └── network_interceptor_registry.dart
│ ├── request/
│ │ ├── http_request.dart
│ │ └── request_options.dart
│ └── response/
│ └── http_response.dart
├── pubspec.yaml
└── README.mdWhen networkLibrary is dio:
infrastructure/
└── network_sdk_dio/
├── lib/
│ ├── network_sdk_dio.dart
│ └── src/
│ ├── client/
│ │ ├── dio_http_client.dart
│ │ └── dio_http_client_adapter.dart
│ ├── config/
│ │ └── dio_config.dart
│ ├── di/
│ │ ├── injector.dart
│ │ ├── injector.module.dart
│ │ └── register_module.dart
│ ├── conversions/
│ │ └── dio_conversions.dart
│ └── interceptor/
│ ├── auth_interceptor.dart
│ └── dio_interceptor_adapter.dart
├── pubspec.yaml
└── README.mdWhen networkLibrary is http:
infrastructure/
└── network_sdk_http/
├── lib/
│ ├── network_sdk_http.dart
│ └── src/
│ ├── client/
│ │ └── dart_http_client.dart
│ ├── config/
│ │ └── http_config.dart
│ └── di/
│ ├── injector.dart
│ ├── injector.module.dart
│ └── register_module.dart
├── pubspec.yaml
└── README.mdSwitch the HTTP client
A generated app wires one client: Dio or package:http. Both register as the HttpClient from network_sdk_api, so code that uses only network_sdk_api does not change when you switch. Brick 2.4.0 and later does the switch in one step. Run the brick again and pick the other networkLibrary:
archipelago generate network_sdk # answer networkLibrary: http
# or, in CI, with networkLibrary set in the config file
archipelago generate network_sdk --config network_sdk.jsonThe post_gen hook then does these steps:
- It generates
infrastructure/network_sdk_http/. - It removes
network_sdk_diofromapps/<app>/pubspec.yaml. It also removes its import andExternalModule(NetworkSdkDioPackageModule)fromapps/<app>/lib/di/injector.dart. - It adds
network_sdk_httpandExternalModule(NetworkSdkHttpPackageModule). - It sets
infrastructure.network_library: httpinarchipelago.yaml.app_debugger_networkreads this value.
The old package stays in infrastructure/network_sdk_dio/, because you may have changed it. The hook prints the command to delete it. Delete it only if nothing else depends on it. Then regenerate the DI code:
melos bootstrap
melos run buildThe switch from http to dio works the same way. If you run the brick again with the client that is already wired, nothing changes. If app_debugger_network is installed, the hook tells you to run it again, because its Network tab inspects one client only.
Code that imports package:dio or package:http directly, or that gets a Dio from GetIt, is not part of the switch. Move that code to HttpClient before you switch.
Named client keys
network_sdk_api exports NetworkInjectorKey — a fixed set of DI instance names (defaultClient, authClient, cdnClient, analyticsClient, plus allClientNames) intended for apps that register more than one Dio/ HttpClient (e.g. @Named(NetworkInjectorKey.authClient)). Its stated purpose is letting app_debugger_network enumerate every registered client and attach an Alice adapter to each without hardcoding names — the brick itself does not register any named client; using the keys is opt-in.
Debug inspector integration
NetworkInterceptorRegistry (in network_sdk_api) is the port a package that cannot see the app's client uses to hand it a vendor-native interceptor. app_debugger_network registers Alice's adapter there. DioHttpClient adopts a Dio Interceptor and DartHttpClient adopts a void Function(http.Response) observer, whether the client is built before or after the registration. Production builds register nothing.
Auth integration
The HTTP clients attach the app's session bearer and recover from a 401 on their own — no hand-written interceptor. The seam is one interface owned by network_sdk_api:
abstract interface class AuthTokenProvider {
Future<String?> get accessToken;
Future<bool> onUnauthorized({required int statusCode, String? endpoint});
}The transport depends on this port, never on auth_api, so a project can install a network SDK with no auth feature. resolveAuthTokenProvider() looks the port up in GetIt lazily, per request, and optionally: with nothing registered it answers null and every request behaves exactly as it did before the port existed.
auth_sdk >= 2.1.0 registers the implementation (AuthSessionTokenProvider) in AuthImplGlobalModule. Installing both bricks is all the wiring required.
What happens on a 401
AuthInterceptor(dio) /DartHttpClient(http) asks the provider to renew the session.- If it answers
true, the request is replayed once, re-entering the interceptor chain so it picks up the rotated bearer. - If it answers
false, the original failure surfaces untouched.
403 is deliberately not treated as a lapsed session — it means authenticated but not permitted, and renewing a token cannot change that. Pass renewOn: {401, 403} for backends that use 403 for expiry.
Opting a request out
Requests that establish or renew a session must not enter this path: a 401 from a sign-in endpoint means "wrong password", and refreshing on it would find no refresh token, fail terminally, and sign the user out. Mark them:
// Dio
await dio.post<Map<String, dynamic>>(
'/auth/login',
data: credentials,
options: Options(extra: AuthExemptRequest.extra),
);
// Vendor-agnostic HttpClient port
await httpClient.post('/auth/login', body: credentials,
options: AuthExemptRequest.options);An exempt request carries no bearer and never triggers a renewal.
Overriding the client
An app that registers its own Dio (or its own HttpClient) replaces the generated registration and therefore opts out of the whole lifecycle unless it passes authTokenProvider: resolveAuthTokenProvider to DioHttpClient.init / DartHttpClient — or adds AuthInterceptor itself.
Cancelling requests
network_sdk_api (brick >= 2.4.0) exports HttpCancelToken. Pass it in RequestOptions.cancelToken. When you call cancel(), every request that uses the token stops and throws HttpCancelledException:
| When you cancel | Result |
|---|---|
| Before the request starts | Throws HttpCancelledException. Nothing is sent. |
| While the request is in flight | The transport aborts the request (dio CancelToken, http AbortableRequest). Throws HttpCancelledException. |
| After the request completed | Nothing happens. |
Both clients support cancellation. A token stays cancelled, so create a new token for new work. A settled request detaches from its token, so one token can serve many requests over a long lifetime.
The usual owner is a Cubit or Bloc. It keeps one token, passes the token down to the datasource, and cancels it in close():
class OrdersCubit(this._repository) extends Cubit<OrdersState> {
this : super(const OrdersState.initial());
final OrdersRepository _repository;
final _cancelToken = HttpCancelToken();
Future<void> load() async {
emit(const OrdersState.loading());
try {
final orders = await _repository.fetch(cancelToken: _cancelToken);
emit(OrdersState.loaded(orders));
} on HttpCancelledException {
return; // Closed while loading: nothing to show.
} on HttpException catch (e) {
if (!isClosed) emit(OrdersState.error(e.message));
}
}
@override
Future<void> close() {
_cancelToken.cancel('OrdersCubit closed');
return super.close();
}
}
// Datasource
Future<HttpResponse> fetch({HttpCancelToken? cancelToken}) =>
_client.get('/orders', options: RequestOptions(cancelToken: cancelToken));- A Cubit throws
StateErrorwhen it emits afterclose(). Return onHttpCancelledException. After any otherawait, checkisClosedbefore you emit. - A Bloc ignores emits from a handler after
close(). Cancel the token so that the request also stops. - If a use case turns exceptions into a
Failure, the Cubit cannot seeHttpCancelledException. CheckisClosedafter theawaitinstead, asLoginCubitinauth_sdkdoes. - For a Riverpod notifier, call
ref.onDispose(_cancelToken.cancel). For a ProviderChangeNotifier, cancel the token indispose().
Changelog
Run archipelago brick info network_sdk to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.