FlintClient
FlintClient Guide
A production-ready Dart HTTP client with retries, caching, cancellation, structured errors, lifecycle hooks, and strict or lenient parsing.
pub.dev GitHub [Examples](/examples)
On This Page
[Basics](#basics) [HTTP Methods](#methods) [Files](#files) [WebSocket](#websocket) [Error Handling](#error-handling) [Parse Modes](#parse-modes) [Observability](#observability)
What Is FlintClient Used For?
FlintClient is used to call APIs from Dart/Flutter apps: GET data, send POST/PUT/PATCH/QUERY/DELETE requests, upload files, handle timeouts, retry failures, cache responses, and cancel in-flight requests.
- Mobile app calling backend endpoints
- Dashboard fetching reports from APIs
- CLI tools that consume web services
- Apps that need retry + cache + cancellation built-in
Why Use FlintClient Instead of Another Package?
If you need only basic requests, many packages can work. Use FlintClient when you want advanced behavior already integrated and consistent.
- Built-in cache layer with TTL control
- Idempotency-aware retries with backoff and Retry-After support
- Cancellation support with dedicated cancellation error kind
- Structured error model (
timeout,network,http,parse,cancelled)
- Request lifecycle hooks for observability and correlation IDs
- Strict vs lenient parse modes with serializer chains
Step-by-Step (Beginner Friendly)
Step 1: Open terminal in your Dart/Flutter project folder.
Step 2: Install from pub.dev:
Step 3: Confirm dependency in pubspec.yaml:
Tip: your exact version may be different. Use the version shown on pub.dev.
Step 4: Import and create a client:
Step 5: Make your first request:
Basic Requests
HTTP QUERY
HTTP QUERY is defined by RFC 10008. It is safe and idempotent like GET, but it can include request content like POST. Use it for complex searches and filters that should not mutate server state.
queryParameters are URI query string values. body is QUERY request content and uses FlintClient's normal JSON, text, form, timeout, interceptor, retry, cache, and response parsing pipeline. QUERY is treated as idempotent for retry configuration.
Compatibility warning: some proxies, browsers, servers, and API tools may not support QUERY yet. RFC: https://www.rfc-editor.org/rfc/rfc10008.html.
File Download + Upload
Error Handling
By default, FlintClient returns an error response object for failed requests (4xx/5xx). This means your request resolves with response.isError == true and details in response.error.
Access Raw Backend Error Payload
Use FlintError.data to read the exact backend payload. It can be a Map, List, String, or null (empty body).
Throw Instead of Returning Error Response
If you prefer exception flow, enable throwIfError on the client.
Practical Examples You Can Use Today
1) Login Request
2) Product List With Cache
final client = FlintClient( baseUrl: 'https://api.example.com', defaultRetryConfig: RetryConfig( maxAttempts: 3, delay: const Duration(milliseconds: 250), maxRetryTime: const Duration(seconds: 2), honorRetryAfter: true, ), defaultCacheConfig: const CacheConfig(maxAge: Duration(minutes: 2)), );
final token = CancelToken(); final pending = client.get('/reports/slow', cancelToken: token);
token.cancel('user aborted'); final response = await pending; if (response.isError) { print(response.error?.kind); // FlintErrorKind.cancelled }
final response = await client.request>( 'POST', '/users', options: RequestOptions>( body: {'name': 'Ada'}, headers: {'Content-Type': 'application/json'}, parseMode: ResponseParseMode.lenient, cancelToken: CancelToken(), ), );
final ws = client.ws('/chat'); await ws.connect();
ws.on('message', (data) => print(data)); ws.emit('message', {'text': 'Hello from client'});
final client = FlintClient( baseUrl: 'http://localhost:8080', headers: {'Authorization': 'Bearer your-token'}, );
final ws = client.ws('/ws'); await ws.connect();
final ws = FlintWebSocketClient( 'ws://localhost:8080/ws', sendTokenAsQuery: true, queryTokenKey: 'token', tokenProvider: () async => await loadTokenFromStorage(), );
await ws.connect();
final ws = FlintWebSocketClient( 'ws://localhost:8080/ws', autoAuthEvent: true, authEventName: 'auth', authPayload: {'token': 'your-token'}, );
await ws.connect();
dart run example/lib/websocketauthexample.dart
import 'dart:async'; import 'dart:convert'; import 'dart:io';
import 'package:flintclient/flintclient.dart';
Future main() async { final server = await _startMockWsServer(); final httpBaseUrl = 'http://localhost:${server.port}'; final wsUrl = 'ws://localhost:${server.port}/ws';
await headerAuthExample(httpBaseUrl); await queryAuthExample(wsUrl); await _authEventExample(wsUrl);
await server.close(force: true); }
Future _headerAuthExample(String httpBaseUrl) async { final client = FlintClient( baseUrl: httpBaseUrl, headers: {'Authorization': 'Bearer header-token-123'}, debug: true, );
final ws = client.ws('/ws', params: {'example': 'header'}); ws.on('connect', (_) => print('Connected with header token')); ws.on('ack', (data) => print('Server ack: $data'));
await ws.connect(); ws.emit('message', {'text': 'hello from header auth'});
await Future.delayed(const Duration(milliseconds: 300)); ws.dispose(); client.dispose(); }
Future _queryAuthExample(String wsUrl) async { final ws = FlintWebSocketClient( wsUrl, params: {'example': 'query'}, sendTokenAsQuery: true, queryTokenKey: 'token', tokenProvider: () async => 'query-token-456', debug: true, );
ws.on('connect', (_) => print('Connected with query token')); ws.on('ack', (data) => print('Server ack: $data'));
await ws.connect(); ws.emit('message', {'text': 'hello from query auth'});
await Future.delayed(const Duration(milliseconds: 300)); ws.dispose(); }
Future _authEventExample(String wsUrl) async { final ws = FlintWebSocketClient( wsUrl, params: {'example': 'event'}, autoAuthEvent: true, authEventName: 'auth', authPayload: {'token': 'event-token-789'}, debug: true, );
ws.on('connect', (_) => print('Connected, auth event will auto-send')); ws.on('authed', (data) => print('Auth accepted: $data')); ws.on('ack', (data) => print('Server ack: $data'));
await ws.connect(); ws.emit('message', {'text': 'hello after auth event'});
await Future.delayed(const Duration(milliseconds: 300)); ws.dispose(); }
Future _startMockWsServer() async { final server = await HttpServer.bind('localhost', 0);
server.listen((request) async { if (request.uri.path != '/ws') { request.response ..statusCode = 404 ..write('Not found') ..close(); return; }
final authHeader = request.headers.value(HttpHeaders.authorizationHeader); final tokenFromQuery = request.uri.queryParameters['token']; final exampleType = request.uri.queryParameters['example'] ?? 'unknown';
final socket = await WebSocketTransformer.upgrade(request); socket.add( jsonEncode({ 'event': 'ack', 'data': { 'example': exampleType, 'authHeader': authHeader, 'tokenFromQuery': tokenFromQuery, }, }), );
socket.listen((raw) { try { final msg = jsonDecode(raw.toString()) as Map; final event = msg['event']?.toString() ?? ''; final data = msg['data'];
if (event == 'auth') { socket.add(jsonEncode({'event': 'authed', 'data': data})); return; } if (event == 'ping') { socket.add(jsonEncode({'event': 'pong'})); return; }
socket.add( jsonEncode({ 'event': 'message', 'data': {'echo': data}, }), ); } catch (_) {} }); });
return server; }
// Global default final strictClient = FlintClient( baseUrl: 'https://api.example.com', defaultParseMode: ResponseParseMode.strict, );
// Per-request override final response = await strictClient.get( '/stats/value', parseMode: ResponseParseMode.lenient, );
final client = FlintClient( baseUrl: 'https://api.example.com', lifecycleHooks: RequestLifecycleHooks( onRequestStart: (ctx) => print('START ${ctx.correlationId}'), onRetry: (ctx, err, delay) => print('RETRY ${ctx.attempt} in $delay'), onCacheHit: (ctx, key, _) => print('CACHE HIT $key'), onError: (ctx, err, willRetry) => print('ERROR ${err.kind} willRetry=$willRetry'), onRequestEnd: (ctx, response, error) => print('END status=${response?.statusCode} duration=${ctx.totalDuration}'), ), contextualRequestInterceptor: (request, ctx) async { request.headers.set('X-Correlation-Id', ctx.correlationId); }, );
dart run example/lib/fullobservabilitymock_example.dart
dart run example/lib/httpmethodsanddownloadexample.dart
One language powering Full-Stack Web, Cross-Platform Clients, Native AI, and Connected Robotics.