Skip to content
Back to archipelago.banuacoder.com

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 HttpClient in datasources
  • Seeing how bearer tokens, 401 recovery and cancellation work

Prerequisites ​

New projects already include this brick with the networkLibrary you chose.

Step 1: Generate the Network SDK ​

bash
archipelago generate network_sdk

The prompts are:

  • appName: your app name, for example MyApp
  • networkLibrary: dio (default) or http
  • isForMonorepo: leave this as true

Or use a config file:

json
{
  "appName": "MyApp",
  "networkLibrary": "dio",
  "isForMonorepo": true
}
bash
archipelago generate network_sdk --config network_config.json

Step 2: Understand the Generated Structure ​

The contract is network_sdk_api. Exactly one implementation package is generated.

text
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.dart

Step 3: Set the Base URL ​

The generated RegisterModule reads the base URL from your environment files:

dart
@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:

dart
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:

dart
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:

  1. The client asks the provider to renew the session.
  2. If the provider answers true, the client replays the request once.
  3. 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:

dart
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():

dart
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 ​

CapabilityDiohttp
InterceptorsBuilt-in chainClient-level auth handling
Request cancellationHttpCancelToken maps to dio CancelTokenHttpCancelToken maps to AbortableRequest
Package sizeLargerSmaller

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.

dart
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 ​