⚑ Sonic

An HTTP Client with a fluent interface and improved type support.

pub version pub points license MIT

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

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: reads String.fromEnvironment(NAME)
  • envPlaceholdersFromProcessEnv: reads Platform.environment[NAME]

See the Wiki: API Versioning

πŸ“ Notes

  • You can bypass type parsing by using withRawRequest() and typing as dynamic.
  • 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 debugMode is true, a LogInterceptor is added to the internal Dio client to log requests/responses.
  • You can have multiple Sonic instances, but a single shared instance via DI is recommended for most apps.
  • For uploads, use multipart FormData and MultipartFile (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.