With graphql_flutter, how do you receive GraphQL subscriptions in a Flutter app, and what must you configure on the WebSocketLink?
answer
- subscriptions need their own link
- Link.split on request.isSubscription
- default subProtocol is old graphql-ws
- initialPayload carries auth
- SocketClientConfig: autoReconnect, 5 s delay
basics
~20 sSplit subscriptions to a WebSocketLink with Link.split((r) => r.isSubscription, wsLink, httpChain), match subProtocol to the server (the default is the old graphql-ws protocol), send auth in SocketClientConfig.initialPayload, and render with the Subscription widget or useSubscription.
solid answer
~40 sSubscriptions do not go over HTTP, so the link must route them: `Link.split((request) => request.isSubscription, wsLink, authLink.concat(httpLink))`; otherwise the HTTP link swallows them. `WebSocketLink(url, config: SocketClientConfig(...), subProtocol: ...)` needs two checks. Its `subProtocol` defaults to `GraphQLProtocol.graphqlWs`, the old protocol the package marks as unmaintained, so a server built on the newer protocol needs `GraphQLProtocol.graphqlTransportWs`. And `AuthLink` sits on the HTTP branch, so credentials for the socket go in `initialPayload`, which may be a function returning the payload. `SocketClientConfig` defaults to `autoReconnect: true` with 5 seconds between attempts and a 30-second `inactivityTimeout`. In the UI, `Subscription(options: SubscriptionOptions(document: ...), builder: (result) => ...)` gives only the latest event; `ResultAccumulator` or `onSubscriptionResult` collects them.
code
dart · 22 linesfinal wsLink = WebSocketLink(
'wss://api.bookclub.example/graphql',
subProtocol: GraphQLProtocol.graphqlTransportWs,
config: SocketClientConfig(
initialPayload: () async => {'authorization': 'Bearer ${await tokenStore.read()}'},
),
);
final link = Link.split(
(request) => request.isSubscription,
wsLink,
authLink.concat(httpLink),
);
Widget newReviews() => Subscription(
options: SubscriptionOptions(document: reviewAdded, variables: {'bookId': bookId}),
builder: (result) {
if (result.hasException) return const SizedBox.shrink();
final review = result.data?['reviewAdded'];
return review == null ? const SizedBox.shrink() : NewReviewBanner(review: review);
},
);go deeper
Recall that subscriptions need a WebSocketLink, chosen with Link.split, and are rendered with the Subscription widget or useSubscription.
Explain the subProtocol default and when to change it, how the socket is authenticated, and what SocketClientConfig does on connection loss.
Make subscriptions robust: reconnect behaviour, token refresh through initialPayload, writing events into the cache, and verifying the protocol against the server.
Decide when subscriptions are worth their connection cost compared with polling or push notifications for the product's freshness needs.
## Routing subscriptions In `graphql_flutter` every operation passes through the client's **link chain**. Queries and mutations end at an `HttpLink`; a **subscription** needs a long-lived connection, which `WebSocketLink` provides. The two are combined with **`Link.split`**, which sends each request down one of two branches: ```dart final link = Link.split( (request) => request.isSubscription, wsLink, authLink.concat(httpLink), ); ``` The package README is explicit that the split is required: without it, the HTTP link receives the subscription and swallows it. ## Configuring WebSocketLink `WebSocketLink(url, {config, subProtocol})` has three knobs that matter in practice: | Setting | Default | Why it matters | |---|---|---| | `subProtocol` | `GraphQLProtocol.graphqlWs` (`graphql-ws`) | must match the server's protocol | | `config.initialPayload` | none | where auth for the socket goes | | `config.autoReconnect` | `true` | reconnects after a lost connection | | `config.delayBetweenReconnectionAttempts` | 5 seconds | spacing between reconnect attempts | | `config.inactivityTimeout` | 30 seconds | closes the socket if no keep-alive arrives | | `config.queryAndMutationTimeout` | 10 seconds | for operations sent over the socket | **The protocol default is the classic trap.** Two GraphQL-over-WebSocket protocols are in use, and the package's constants name them: `GraphQLProtocol.graphqlWs`, documented as the **old, no-longer-maintained** protocol, and `GraphQLProtocol.graphqlTransportWs`, the newer one implemented by the widely used `graphql-ws` server library. The default is the old one, so against a server that speaks only the new protocol, the socket connects and subscriptions never deliver. How the protocols differ on the wire is a transport topic; on the client the fix is one argument. **Authentication.** `AuthLink` adds an HTTP header, and in the usual chain it sits only on the HTTP branch of the split. The socket's credentials go in **`initialPayload`**, sent in the connection-init message. It can be a value or a function, including an `async` one, so it can read the current token each time the socket (re)connects. ## Rendering events - **`Subscription` widget**: `Subscription(options: SubscriptionOptions(document: ...), builder: (QueryResult result) => ...)`. The builder receives **only the most recent** result; check `result.hasException` and `result.isLoading` as with queries. - **`useSubscription(options, onSubscriptionResult: ...)`** is the hook version for `HookWidget`s. - **`onSubscriptionResult`** runs for every event and receives the client, which makes it the place to write the event into the cache or trigger side effects. - **`ResultAccumulator`** is a helper widget that collects successive results into a list; its docs warn that it is stateful and loses what it collected if its state is disposed. The subscription hook also listens to `connectivity_plus`: when the device goes from offline to Wi-Fi or mobile it re-subscribes, on Android after a DNS lookup confirms real connectivity. ## A book-club example A "new reviews" banner on a book page subscribes to `reviewAdded(bookId:)`. The `Subscription` widget shows the latest review; `onSubscriptionResult` writes each event into the cached reviews list so the `Query` below updates too. Because the server uses the newer protocol, the link sets `subProtocol: GraphQLProtocol.graphqlTransportWs`, and `initialPayload` returns `{'authorization': 'Bearer <token>'}` from the auth service. ## Connection loss and app lifecycle `SocketClientConfig` gives more control than the defaults: - **`onConnectionLost(code, reason)`** is called when the socket closes and returns a `Future<Duration?>`, letting the app pick the delay before the next attempt, for example a growing backoff instead of the fixed 5 seconds; - **`toggleConnection`** takes a `Stream<ToggleConnectionState>`; emitting `disconnect` pauses reconnection and closes the socket, and `connect` resumes it, which suits closing the socket while the app is in the background and reopening it on return; - **`connectFn`** replaces the default connect call, for example to add headers on `dart:io` platforms, with the caveat in its docs that a channel you listen to yourself must be wrapped with `forGraphQL()`. After a reconnect, events published while the socket was down are not replayed, so a screen that must be complete refetches its `Query` as well. ## Pitfalls - Forgetting the split: subscriptions silently never arrive. - Wrong `subProtocol`: the socket opens, events never come. - Auth only in `AuthLink`: the socket connects unauthenticated and the server rejects the subscription. - Treating the builder's `result` as a history: it is the latest event only.
- Why doesn't AuthLink authenticate the subscription socket?`AuthLink` sets an HTTP header on requests that pass through it, and in the usual setup it is chained in front of `HttpLink` on the non-subscription branch of `Link.split`. The socket's credentials go in `SocketClientConfig.initialPayload`, which is sent when the connection is initialised.
- What does the Subscription builder's result contain after several events?Only the most recent event. To show a history, wrap the output in `ResultAccumulator`, or use `onSubscriptionResult` to append each event to state or to the cache, remembering that `ResultAccumulator` loses its list if its state is disposed.
saying these in an interview costs you the question
- HttpLink can carry subscriptions if the server supports them
- WebSocketLink defaults to the newer graphql-transport-ws protocol
- AuthLink headers are applied to the WebSocket connection automatically
- The Subscription builder receives every event as a list
- WebSocketLink never reconnects on its own