Upgrading to 1.0
1.0 is the first release under the versioning policy. Coming from 0.2.x, it bundles the error model, retries, auth, type policies, the persistence migration and the offline mutation queue — and settles the public surface, which renamed or removed a handful of APIs outright (no deprecation shims this once).
-
Upgrade every package together — they share a major version now:
dependencies:sling_gql: ^1.0.0sling_gql_sqflite: ^1.0.0 # if you persist the cachesling_gql_link: ^1.0.0 # if you use gql_linksling_gql_hooks: ^1.0.0 # if you use flutter_hooksdev_dependencies:sling_gql_gen: ^1.0.0sling_gql_test: ^1.0.0 -
Regenerate with the 1.0 generator. Code generated by 0.1.x still compiles, but the new file carries
slingSchema.fields(what lets the SQLite store migrate instead of wiping on the next schema change), and itsclient.mutate(...)/client.subscribe(...)forward the new options (errorPolicy,timeout,retry,offline,onQueued,debugLabel). If your build script passes--part-of-import, rename it--runtime-import. -
Fix the compile errors with the tables below, then read the behaviour changes — two of them (default retries,
dispose) change what a working app does without a compile error.
Renamed or removed in 1.0
Section titled “Renamed or removed in 1.0”| 0.2.x | 1.0 |
|---|---|
SubscriptionState.retry() |
SubscriptionState.reconnect() (retry means a RetryPolicy everywhere else). |
SlingScope(mutationRoot: Mutation.root) |
SlingScope(schema: slingSchema), or nothing: a client built with schema: slingSchema supplies the mutation and subscription roots. |
QueryScope(client, onChanged: …) |
client.createScope(onChanged: …) (a scope built directly was never registered with the client). |
GraphQLError('…') (sling_gql_test) |
MockGraphQLError('…') — gql_exec has a GraphQLError too. |
sling_gql_gen --part-of-import |
--runtime-import. |
import 'package:sling_gql_gen/sling_gql_gen.dart' for sanitizeIdentifier, scalarDartType, ScalarRegistry, … |
Not exported any more; the library is generate, generatedCodeHash, IntrospectionSchema (+ its Gql* / TypeRef types), ScalarMapping, introspectionQuery. |
SlingLinkException (sling_gql_link) |
The typed SlingExceptions: slingExceptionFromLink maps a LinkException to SlingHttpException, SlingNetworkException, … |
PaginationController.cursors / .pageCount |
Gone: the cache holds the pages (see pagination); the controller only has pendingCursor. |
SlingException(message, graphqlErrors:, statusCode:) |
Sealed, no public constructor; see errors. |
Now analyzer warnings (not errors), because they are runtime internals:
Accessor.scalar/object/list/enumValue/on/whenType/writeand the other helpers are@protected: call them only from generated (or hand-written)Accessorsubclasses. Read through the generated getters everywhere else.Selection’s tree-building members (child,objectChild,mergeFrom,cacheKey, …),PrintedOperation.from/toCacheKeysandListRule.evaluateare@internal.Selection.field/alias/args/childrenandPrintedOperation.document/variablesstay public (onOperation,requests).
Errors are typed
Section titled “Errors are typed”SlingException is a sealed hierarchy; every error you see (QueryState.error,
MutationState.error, SubscriptionState.error, PaginatedState.error,
SlingRequest.error, what resolve / mutate throw) is one of:
SlingNetworkException, SlingTimeoutException, SlingHttpException,
SlingGraphQLException, SlingAuthException, SlingCancelledException,
SlingTransportException.
final codes = e.graphqlErrors.map((m) => (m['extensions'] as Map?)?['code']);
// 1.0final codes = e.errors.map((err) => err.code); // List<SlingGraphQLError>final message = switch (state.error) { null => null, SlingNetworkException() => 'You are offline', SlingHttpException(:final statusCode) => 'Server error $statusCode', SlingGraphQLException(:final errors) => errors.first.message, final other => other.message,};What a transport (or a gql_link chain) throws is wrapped, never passed through raw:
SlingTransportException.cause keeps the original. MutationBuilder’s mutate no
longer swallows exceptions that are not SlingExceptions (thrown by your own body or
optimistic callback).
errorPolicy (client-wide, or per QueryBuilder / resolve / mutate) decides what
a response with both data and errors does. ErrorPolicy.none — the default — is the
0.2.x behaviour: errored paths are pruned and the scope fails. all keeps the data and
the error; ignore keeps the data and hides the error. See
Loading states & errors.
Behaviour changes
Section titled “Behaviour changes”- Queries are retried by default.
SlingClient(retry: RetryPolicy())is the default: three attempts in all on network errors, timeouts and HTTP 5xx, with exponential backoff and jitter. Scopes stay loading meanwhile; only the final failure becomes their (sticky) error. Passretry: RetryPolicy.nonefor 0.2.x’s single attempt. Mutations are still sent once unless the call passesretry:. SlingClient.dispose()no longer closes anhttpClient:you passed in — it is yours; close it when you are done with it. The client still closes the one it creates when you pass none.- Timeouts and auth are built in.
SlingClient(timeout:)(and per call) aborts an attempt with aSlingTimeoutException; a disposed widget’s request is aborted.SlingClient(auth: SlingAuth(headers: …, refresh: …))adds credentials per attempt and refreshes once on a 401 /UNAUTHENTICATED— the token handling many apps wrote in atransport:wrapper. A wrapper keeps working; it now sees one call per attempt.
Pagination is one merged list
Section titled “Pagination is one merged list”0.2.x kept each page of a connection as its own cache entry and PaginationController
remembered every cursor. 1.0 merges the pages in the cache with a type policy:
SlingClient<Query>( endpoint: uri, schema: slingSchema, typePolicies: { Query: TypePolicy(fields: {'launches': RelayStylePagination()}), },);PaginatedQueryBuilder requires it for its field (it asserts otherwise), reads the
first page — which is the merged list — and fetches only the next page on
loadMore(). PaginatedState.refetch() starts the list over from page one; the
controller only tracks the page being loaded (pendingCursor). PaginatedQueryBuilder
also takes QueryBuilder’s options now (fetchPolicy, maxAge, errorPolicy,
timeout, debugLabel, scheduler), and PaginatedState has isSkeleton, isStale
and revalidate().
CacheScope.list((q) => q.launches(first: 20).nodes) addresses the merged list when the
field has RelayStylePagination. Custom policies (FieldPolicy(keyArgs:, merge:)) are
described in Caching → type policies.
sling_gql_sqflite: format 2 and migrations
Section titled “sling_gql_sqflite: format 2 and migrations”- The store’s tables are at
sqfliteFormatVersion2. The firstSqflitePersistence.openafter upgrading wipes a 0.1.x store once (the app starts cold, then persists as before). - After that, a regenerated schema (new
slingSchema.hash) migrates the store alongslingSchema.fields— fields whose signature did not change are kept — instead of wiping it. That needs code generated by sling_gql_gen 1.0. - A corrupt file is reported to
onError, deleted and recreated (loaded.recovered) instead of failingopen. A secondopenof a path already open in the isolate throws; across isolates the lastopenwins (superseded,SqfliteSupersededException). codec:(SqfliteCodec) encrypts or otherwise transforms every row.- If you implemented
Cacheyourself (unsupported, but possible in 0.2.x): it must implementcompact(upTo:), andCacheDelta.changedno longer holds the operation roots outside a full delta — readchangedFields/removedFields, or usedelta.applyTo(json).
Offline mutations (new)
Section titled “Offline mutations (new)”client.mutate(..., offline: true) (and MutationBuilder’s / useSlingMutation’s
mutate) queues a call the server cannot be reached for, keeps its optimistic writes,
shows it as MutationState.isQueued, and replays it in order. To survive a restart, give
the client the store’s queue, and clear it on sign-out:
final persistence = await SqflitePersistence.open('cache.db', schema: slingSchema);final client = SlingClient<Query>( endpoint: uri, schema: slingSchema, cache: persistence.cache, mutationQueue: persistence.mutationQueue,);
// Sign-out: roll back and drop what is queued, then empty the store.await client.clearMutationQueue();await persistence.clear();See Mutations → offline for replay rules and
onQueuedMutationFailed.
Tests (sling_gql_test)
Section titled “Tests (sling_gql_test)”MockGraphQLServer gained failNext(MockFailure.status(503) / .graphQL('…', code:) / .network()), request headers on MockRequest, and MockGraphQLError (renamed from
GraphQLError) takes code:. pumpUntilSettled and disposeAfterTest are unchanged.