β‘ Sonic
An HTTP Client with a fluent interface and improved type support.
Blazingly simple, production-ready networking on top of Dio β with smart caching, retries, dedup, and observability.
β¨ Features
- β¨ Fluent interface and typed decoders (cached per type)
- π Retries with backoff, jitter, and Retry-After; 429-aware
- π§ Caching: in-memory TTL+LRU, SWR, ETag/Last-Modified/Expires
- π€ Request deduplication for identical in-flight GETs
- π§βπ» Per-host rate limiting (token bucket) with priority
- π‘οΈ Circuit breaker per host with event hooks
- π§© Templates for reusable request presets
- π Observability: detailed response.extra metrics
- π€ Fluent uploads (multipart, fields/files)
- π Pagination utilities (Link headers, cursors, adapters, Paged
- π§ Per-request API versioning with
{baseUrl}placeholders
π¦ Install
Use your preferred tool:
# Dart
dart pub add sonic
# Flutter
flutter pub add sonic
π Getting Started
See the Quickstart in the Wiki: Getting Started
π§ Usage
Minimal example:
// Create the client (initialize() is called automatically in this ctor)
final sonic = Sonic.initialize(
baseConfiguration: const BaseConfiguration(baseUrl: 'https://api.example.com'),
);
// Typed GET with a decoder
final res = await sonic
.create<User>(url: '/users/1')
.withMethod(HttpMethod.get)
.withDecoder((j) => User.fromJson(j as Map<String, dynamic>))
.execute();
if (res.isSuccess) {
final user = res.data; // User
}
π Wiki
- π Getting Started
- ποΈ Caching
- π Retries
- π€ Deduplication
- π Observability
- π§© Decoding (Strict, Guards, Adapters)
- π Auth Refresh
- π€ Uploads
- π Response Metrics
- π§ API Versioning
- β FAQ
- π Cookbook
Deep dives are available in the wiki:
- Pagination
- Circuit breaker and health probe
- Templates
- Stage timers and response metrics
- Rate limiting and priorities
π§ API Versioning
If you work with multiple API versions from the same client, configure versioned base URLs and select the version per request:
final sonic = Sonic.initialize(
baseConfiguration: const BaseConfiguration(
baseUrl: 'https://api.example.com',
apiVersions: {
// You can avoid repetition with the {baseUrl} placeholder
'v1': '{baseUrl}/v1',
'v2': '{baseUrl}/v2',
},
),
);
// This request goes to https://api.example.com/v2/users
final res = await sonic
.create<User>(url: '/users')
.withApiVersion('v2')
.withMethod(HttpMethod.get)
.withDecoder((j) => User.fromJson(j as Map<String, dynamic>))
.execute();
When no version is selected (or the key is unknown), the default baseUrl is used.
Supported placeholders inside apiVersions values: {baseUrl}, {origin}, {scheme}, {host}, {port}, {basePath}, {version}.
Advanced: You can also resolve {env:NAME} via optional flags in BaseConfiguration:
envPlaceholdersFromDartDefines: readsString.fromEnvironment(NAME)envPlaceholdersFromProcessEnv: readsPlatform.environment[NAME]
See the Wiki: API Versioning
π Notes
- You can bypass type parsing by using
withRawRequest()and typing asdynamic. - Decoders are cached per type; after first registration, you typically donβt need to pass them again.
- URLs can be relative (resolved against baseUrl) or absolute.
- If
debugModeis true, aLogInterceptoris added to the internal Dio client to log requests/responses. - You can have multiple
Sonicinstances, but a single shared instance via DI is recommended for most apps. - For uploads, use multipart
FormDataandMultipartFile(see the Uploads wiki page).
π§ͺ Example
π Check a runnable example in example/sonic_example.dart.
π License
π MIT Β© Arun Prakash. See LICENSE.
π Contributions
Contributions are always welcome!
Libraries
- sonic
- Sonic is a HTTP client made on top of Dio to have better support for types and provide a fluent interface for the calls.