> For the complete documentation index, see [llms.txt](/llms.txt)

# Dart

The Dart SDK is an idiomatic package emitted from the same `GlottoIR` as every other target.
Operations under a declared resource hang off an accessor — `client.pets.listPets()` — while an
operation you leave ungrouped stays a method on `Client`. Request/response shapes are Dart classes
with `fromJson`/`toJson`; binary download responses expose byte streams and bounded byte reads.

Manual pages expose typed items, full response metadata and explicit continuation. `RequestOptions` controls headers, deadlines, retries, extra parameters and cancellation. Binary operations return an owned `BinaryDownload` with bounded `readAll`, `pipe`, byte chunks, response metadata and `close`. Close the client when finished to release SDK-owned HTTP connections; injected clients remain caller-owned. See [pagination](/docs/pagination), [request retries and controls](/docs/retries), [streaming](/docs/streaming), and [file transfers](/docs/file-transfers).

## Quickstart

```bash
dart pub add petstore
```

```dart
import 'package:petstore/petstore.dart';

final client = Client(token: "<token>");
try {
  final result = await client.pets.createPet(
    PetCreate(name: "Biscuit", species: PetCreateSpecies.cat),
  );
} finally {
  client.close();
}
```

## Typed models

Each `GlottoIR` model becomes a Dart class with `fromJson`/`toJson`; operations decode and return
the typed response and accept a typed request body. Discriminated unions resolve to the right
variant.

## Typed errors

Operations throw an `ApiError` carrying the parsed error body, with an `ApiErrorKind` enum so
callers `switch (error.kind)` / compare `ApiErrorKind.notFound` rather than matching status codes.
See [Errors](/docs/errors).

## Pagination

Paginated list methods return a `Stream` that walks every page as you listen, advancing the cursor
for you. See [Pagination](/docs/pagination).

## Retries & backoff

Transient failures (`5xx`, `429`, transport errors) retry with exponential backoff and jitter,
configurable per client. See [Retries & timeouts](/docs/retries).

## SSE streaming

Server-sent-event endpoints return a `Stream` of typed events decoded from the `text/event-stream`
framing. See [Streaming](/docs/streaming) and [Authentication](/docs/authentication).

## Unknown response fields

Your API can add a response field without it being a breaking change — but a generated class has no field to put it in, and its `fromJson` factory reads only the keys it declares. The generated
models keep it instead: a field the SDK wasn't generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.

```dart
final pet = await client.pets.createPet(body);

// A field your API started returning after this SDK was generated.
final species = pet.extraFields['species'];

// Re-encoding preserves it — a read-modify-write never silently drops it.
final json = jsonEncode(pet.toJson());
```

`extraFields` is a getter returning `Map<String, dynamic>`, so nested objects and lists survive
intact, and retention is recursive.

Your existing construction still compiles: the retention parameter is optional and named.

The retained fields are read-only by design. To *send* a field your spec doesn't model yet, use the
per-call extra-body escape hatch rather than writing to the retained bag.

## File uploads

`multipart/form-data` operations build the multipart body from their fields for you; an
`application/octet-stream` operation takes a positional `List<int> body` sent raw with the right
`Content-Type`.

## Secure token storage

The client takes an optional `tokenStore` parameter. Supply one and the bearer token is read from it
per request (falling back to the static `token`); on OAuth2 client-credentials APIs the token cache
is also persisted through it, so a restarted app reuses a still-valid token instead of minting a new
one. See [Authentication](/docs/authentication).

Because Flutter is a mobile platform, the contract is asynchronous — the shape a platform-backed
store can actually implement:

```dart
abstract class TokenStore {
  Future<String?> getToken();
  // OAuth2 client-credentials APIs also get:
  // Future<void> setToken(String value);
}
```

That means you can back it with [`flutter_secure_storage`](https://pub.dev/packages/flutter_secure_storage),
which keeps the credential in the iOS Keychain or Android Keystore:

```dart
class SecureTokenStore implements TokenStore {
  final FlutterSecureStorage storage;
  SecureTokenStore(this.storage);

  @override
  Future<String?> getToken() => storage.read(key: 'api_token');
}
```

### Generating the adapter instead

Rather than writing that yourself, opt in and the SDK ships it:

```yaml
targets:
  dart:
    secureStorage: true
```

Your client then carries a `FlutterSecureStorageTokenStore`, so wiring the platform store is one
expression:

```dart
const storage = FlutterSecureStorage();
final client = Client(tokenStore: FlutterSecureStorageTokenStore(read: storage.read));
```

Pass `key:` to choose the storage entry (useful when an API has several bearer-style schemes, each
with its own store). On OAuth2 client-credentials APIs the adapter also takes `write:` — pass
`storage.write` — so the token cache is persisted.

**No storage backend is bundled and no dependency is added.** The adapter takes the store's *methods*,
not the store itself — Dart function types are structural, so `storage.read` fits whether it comes
from `flutter_secure_storage`, an encrypted store of your own, or a test double. Your `pubspec.yaml`
is unchanged either way, and leaving the flag off emits exactly the SDK you have today.

## Connectivity- and lifecycle-aware retries

Two opt-in seams stop a mobile app burning its retry budget against a radio that is down or while it
is backgrounded — the same pair the React Native, Kotlin-Android and Swift SDKs ship. Enable either,
both, or neither:

```yaml
targets:
  dart:
    connectivity: true   # pause retries while the device is offline
    lifecycle: true      # pause backoff while the app is backgrounded
```

A default Dart SDK is unchanged. With the flags on, the client takes two more optional parameters and
consults them before **every** attempt — a known-offline state waits and re-checks rather than
spending the request, and a backgrounded app pauses backoff **without consuming a retry attempt**:

```dart
abstract class ConnectivityMonitor {
  Future<bool> isOnline();
}

abstract class AppLifecycle {
  Future<bool> isForeground();
}
```

Both reads are asynchronous, because the Flutter APIs behind them are — `connectivity_plus` exposes
`Future<List<ConnectivityResult>> checkConnectivity()`. A synchronous contract would be
unimplementable by the standard package, the same way a synchronous `TokenStore` was for
`flutter_secure_storage`.

No package is bundled and nothing imports Flutter, so the SDK stays a plain Dart package. Adapters
take a closure, so wiring a real app is one expression per seam:

```dart
import 'package:connectivity_plus/connectivity_plus.dart';

final lifecycle = MutableAppLifecycle();

final client = Client(
  'https://api.example.com',
  connectivity: CallbackConnectivityMonitor(() async =>
      (await Connectivity().checkConnectivity())
          .any((r) => r != ConnectivityResult.none)),
  lifecycle: lifecycle,
);
```

Flutter reports lifecycle by callback rather than by query, so `MutableAppLifecycle` is the shape a
`WidgetsBindingObserver` pushes into:

```dart
class _AppState extends State<App> with WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    lifecycle.setForeground(state == AppLifecycleState.resumed);
  }
}
```

There is also a `CallbackAppLifecycle` if you have a lifecycle source you can poll instead.
