Transport
Every request — batched queries and mutations alike — goes through a single function:
typedef Transport = Future<http.Response> Function(http.Request request);By default the client sends the request with its http.Client. Pass your own
transport to intercept it. The request you receive is a finalized POST with
content-type: application/json and the client’s static headers already
applied; you can mutate its headers before sending.
final client = SlingClient<Query>( endpoint: Uri.parse('https://api.example.com/graphql'), rootFactory: Query.root, headers: const {'x-app-version': '1.2.0'}, // static, applied before transport transport: (request) async { request.headers['authorization'] = 'Bearer ${await auth.token()}'; return http.Response.fromStream(await http.Client().send(request)); },);Recipes
Section titled “Recipes”Keep one http.Client around instead of creating one per request:
final _http = http.Client();Future<http.Response> send(http.Request r) async => http.Response.fromStream(await _http.send(r));Timeout
Section titled “Timeout”transport: (request) => send(request).timeout(const Duration(seconds: 10)),A TimeoutException surfaces as QueryState.error for every scope in the
batch (sticky until refetch(), see
Loading states & errors).
An http.Request can be sent once, so copy it before each attempt:
http.Request copy(http.Request r) => http.Request(r.method, r.url) ..headers.addAll(r.headers) ..bodyBytes = r.bodyBytes;
transport: (request) async { for (var attempt = 0;; attempt++) { final res = await send(copy(request)); if (res.statusCode < 500 || attempt == 2) return res; await Future.delayed(Duration(milliseconds: 200 * (1 << attempt))); }},Retry inside the transport is invisible to widgets: the scope only sees the final response.
Token refresh on 401
Section titled “Token refresh on 401”transport: (request) async { request.headers['authorization'] = 'Bearer ${auth.current}'; final res = await send(copy(request)); if (res.statusCode != 401) return res; await auth.refresh(); request.headers['authorization'] = 'Bearer ${auth.current}'; return send(copy(request));},Logging
Section titled “Logging”onOperation already reports every printed document; use the transport when
you want status codes and timing too:
transport: (request) async { final sw = Stopwatch()..start(); final res = await send(request); log('${res.statusCode} ${sw.elapsedMilliseconds}ms ${request.body.length}B'); return res;},What the client does with the response
Section titled “What the client does with the response”statusCode >= 400→SlingException('HTTP <code>')for the whole batch.- A JSON body without
data→SlingExceptionwith the first GraphQL error. errorsalongsidedata→ the errored paths are pruned before caching and the error is attached to the scopes that asked for them (see Loading states & errors).