Infrastructure SDKs
How to set up a Dio or http client in a Flutter app
Free Reference for network_sdk: every variable, option and package it generates. Command: archipelago generate network_sdk
Generate the Network SDK with Dio or http, set the base URL per environment, call HttpClient from datasources, and see how 401 recovery works.
What you'll learn
- Generating the Network SDK with a Dio or http backend
- Understanding the API/Impl split for HTTP clients
- Setting the base URL from your environment files
- Using
HttpClientin datasources - Seeing how bearer tokens, 401 recovery and cancellation work
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
New projects already include this brick with the networkLibrary you chose.
Step 1: Generate the Network SDK
archipelago generate network_sdkThe prompts are:
- appName: your app name, for example
MyApp - networkLibrary:
dio(default) orhttp - isForMonorepo: leave this as
true
Or use a config file:
{
"appName": "MyApp",
"networkLibrary": "dio",
"isForMonorepo": true
}archipelago generate network_sdk --config network_config.jsonStep 2: Understand the Generated Structure
The contract is network_sdk_api. Exactly one implementation package is generated.
infrastructure/
├── network_sdk_api/ # HttpClient, requests, responses, exceptions, AuthTokenProvider
│ └── lib/src/
│ ├── auth/
│ ├── client/http_client.dart
│ ├── exception/
│ ├── interceptor/
│ ├── request/
│ └── response/
└── network_sdk_dio/ # Or network_sdk_http
└── lib/src/
├── client/ # DioHttpClient and its adapter
├── config/dio_config.dart
├── interceptor/auth_interceptor.dart
└── di/register_module.dartStep 3: Set the Base URL
The generated RegisterModule reads the base URL from your environment files:
@LazySingleton(as: Dio)
DioHttpClient httpClient() => DioHttpClient.init(
config: DioConfig(baseUrl: AppEnv.apiBaseUrl),
authTokenProvider: resolveAuthTokenProvider,
);Set API_BASE_URL in the .env.<flavor> files in shared/app_config. Each flavor then gets its own URL. See shared_app_config.
DioConfig also sets the connect, receive and send timeouts (30 seconds each), the content type, redirects and default headers.
Step 4: Use the Client in Datasources
Feature datasources depend on network_sdk_api only. Take HttpClient by constructor:
class PaymentRemoteDatasource {
PaymentRemoteDatasource(this._client);
final HttpClient _client;
Future<List<PaymentModel>> getPayments() async {
final response = await _client.get('/payments');
return (response.data as List)
.map((e) => PaymentModel.fromJson(e as Map<String, dynamic>))
.toList();
}
Future<PaymentModel> createPayment(CreatePaymentRequest request) async {
final response = await _client.post('/payments', body: request.toJson());
return PaymentModel.fromJson(response.data as Map<String, dynamic>);
}
}In a feature, forward the HttpClient type into the feature container with one line in <feature>_imports.dart. See Dual-GetIt.
Step 5: Auth Tokens and 401 Recovery
You write no auth interceptor. The clients attach the session bearer and recover from a 401 by themselves. The link is one port in network_sdk_api:
abstract interface class AuthTokenProvider {
Future<String?> get accessToken;
Future<bool> onUnauthorized({required int statusCode, String? endpoint});
}auth_sdk registers the implementation in its global module. With nothing registered, requests behave as before. On a 401:
- The client asks the provider to renew the session.
- If the provider answers
true, the client replays the request once. - If it answers
false, the original error comes through.
A 403 does not start a renewal. Pass renewOn: {401, 403} if your backend uses 403 for expired tokens.
Mark requests that create or renew a session as exempt, so a wrong password never starts a refresh:
await httpClient.post(
'/auth/login',
body: credentials,
options: AuthExemptRequest.options,
);If your app registers its own Dio or HttpClient, it opts out of this. Pass authTokenProvider: resolveAuthTokenProvider to DioHttpClient.init or DartHttpClient, or add AuthInterceptor yourself.
Step 6: Cancel Requests
Pass an HttpCancelToken in RequestOptions(cancelToken: ...). Call cancel() to stop the request. The call then throws HttpCancelledException. Cancelling after the request completed does nothing. The Cubit or Bloc that starts the request owns the token and cancels it in close():
final _cancelToken = HttpCancelToken();
@override
Future<void> close() {
_cancelToken.cancel('closed');
return super.close();
}See network_sdk: Cancelling requests for the Cubit, Bloc, Riverpod and Provider patterns.
Step 7: Choose Dio or http
| Capability | Dio | http |
|---|---|---|
| Interceptors | Built-in chain | Client-level auth handling |
| Request cancellation | HttpCancelToken maps to dio CancelToken | HttpCancelToken maps to AbortableRequest |
| Package size | Larger | Smaller |
Use dio for apps with complex needs. Use http for lightweight apps.
Step 8: Handle Errors
The API package has typed exceptions: HttpException is the base. The subtypes are HttpNetworkException, HttpTimeoutException, HttpRequestException, HttpParseException and HttpCancelledException.
try {
final response = await client.get('/users');
} on HttpCancelledException {
// The screen closed. Show nothing.
} on HttpTimeoutException {
// Show a retry prompt.
} on HttpException catch (e) {
// Log e.message to monitoring.
}Named Clients
NetworkInjectorKey holds fixed names such as authClient and cdnClient. The brick registers none of them. Use them if your app registers more than one client. app_debugger_network can then attach to each.
Next Steps
- Set up authentication to use token-based requests
- Add monitoring to track network failures