With web_socket_channel on mobile, what does IOWebSocketChannel.connect's pingInterval do, and why is it not available through WebSocketChannel.connect?
answer
- dart:io only, not the web
- pingInterval defaults to null
- no pong in time: closed, goingAway
- headers, connectTimeout, customClient
- connect() takes only uri and protocols
basics
~20 spingInterval makes the dart:io socket ping at that interval and close the connection when no pong arrives in time; it defaults to null, no pings. It is io-only because the portable connect must also run on the web.
solid answer
~40 s`IOWebSocketChannel` is the `dart:io` implementation, so it exists on Android, iOS and desktop but not on the web. Its `connect` factory adds options the portable `WebSocketChannel.connect(uri, {protocols})` does not have: `headers`, `pingInterval`, `connectTimeout` and `customClient`. With `pingInterval` set, the underlying `dart:io` `WebSocket` sends a ping at that interval; if the pong does not come back within the interval, the socket is treated as disconnected and closed with the `goingAway` code, so your stream's `onDone` fires and reconnect logic runs. The default is `null`: no pings, and a silently dead mobile connection can look open for a long time. On the web the browser owns the socket and exposes no such control, which is why the option is not on the cross-platform API.
code
dart · 8 linesimport 'package:web_socket_channel/io.dart';
final channel = IOWebSocketChannel.connect(
Uri.parse('wss://scores.example.com/live'),
pingInterval: const Duration(seconds: 20),
connectTimeout: const Duration(seconds: 10),
headers: {'Authorization': 'Bearer $accessToken'},
);go deeper
Recall that IOWebSocketChannel is the mobile and desktop implementation and that pingInterval is off unless you set it.
Explain what happens on a missed pong, which options only the io implementation offers, and why the portable API cannot offer them.
Choose ping intervals and heartbeats for mobile networks, weighing detection speed against battery, and structure a factory for web plus mobile.
Decide whether liveness is detected by transport pings, application heartbeats or both, and agree on it with the backend so timeouts line up.
## Two ways to connect `web_socket_channel` offers a portable entry point and platform-specific ones: | API | Platforms | Options | |---|---|---| | `WebSocketChannel.connect(uri, {protocols})` | all, including web | `protocols` only | | `IOWebSocketChannel.connect(url, {...})` | `dart:io` platforms: Android, iOS, desktop | `protocols`, `headers`, `pingInterval`, `connectTimeout`, `customClient` | | `HtmlWebSocketChannel` | web only | wraps a browser `WebSocket` | `IOWebSocketChannel` lives in `package:web_socket_channel/io.dart` and imports `dart:io`, so importing it breaks a web build. Apps that target the web as well either use the portable API or pick the implementation behind a conditional import. ## What pingInterval does From the package documentation: - **`pingInterval`** sets how often the `dart:io` `WebSocket` sends a ping; - if a ping is **not answered by a pong within that interval**, the socket is assumed disconnected and the connection is **closed with the `goingAway` code**; - it **defaults to `null`**, which disables pings entirely. For the app, the effect is that a dead connection becomes a normal end of stream: `onDone` runs and the reconnect path takes over, instead of the screen waiting indefinitely for messages that will never come. What ping and pong frames are, and why they reveal a half-open connection, is protocol material; the Flutter-side question is whether to turn the feature on and at what interval. ## Why mobile needs it more Mobile networks change underneath an app: a phone moves from Wi-Fi to cellular, passes through a tunnel, or a carrier NAT silently drops an idle mapping. None of these necessarily produces an error on the device, so without pings a live-scores feed can show a stale score as if nothing were wrong. Typical choices: 1. **Enable `pingInterval`** in the tens of seconds for feeds that must notice drops quickly. 2. **Add an application heartbeat** (a message the server sends every few seconds) when you also need to detect a stalled server process, or when the same code must run on the web. 3. **Balance battery**: every ping wakes the radio, so very short intervals cost power on a device that is otherwise idle. ## The other io-only options - **`headers`**: extra HTTP headers on the upgrade request, such as an authorization header. The portable API has no way to set them, because browsers do not allow it. - **`connectTimeout`**: bounds the initial connect; without it the package applies no timeout of its own, and when exceeded, `ready` fails with a `TimeoutException`. - **`customClient`**: supplies your own `dart:io` `HttpClient`, for example one configured with a custom certificate policy or proxy. ## Tuning for a live-scores feed A scores feed is quiet for minutes and then bursts during a goal, which is the worst pattern for detecting drops: the silence looks the same whether the connection is healthy or dead. A reasonable setup: - set `pingInterval` so a dead connection is noticed well before a user would doubt the score, for example 15-30 seconds; - make sure the server and any proxies in between allow pings and do not close idle connections sooner than that interval; - set `connectTimeout` to a value that fits the reconnect loop, so a hung handshake turns into a normal retry; - show the connection state in the UI, so a reconnect is visible rather than a silently frozen score. When the app is in the background, pings do not keep the socket alive: the OS may suspend the process, and the lifecycle-driven close and reconnect take over. ## Choosing in a cross-platform app If the app ships to the web too, the usual structure is a small factory: on `dart:io` platforms create an `IOWebSocketChannel` with `pingInterval` and `connectTimeout`; on the web use `WebSocketChannel.connect` and rely on an application heartbeat. Everything above that factory sees only `WebSocketChannel`, so screens and repositories do not care which one was built.
- What happens to your stream subscription when a ping goes unanswered?The `dart:io` socket closes the connection with the `goingAway` code, the channel's stream finishes, and your subscription's `onDone` runs. From there it is the same path as any other drop: read `closeCode`, update the UI and schedule a reconnect.
- Why can't the portable WebSocketChannel.connect accept headers?Because it must also work on the web, where the browser's WebSocket API gives page code no way to set request headers on the upgrade. Only `IOWebSocketChannel.connect`, which uses `dart:io`, can add them.
saying these in an interview costs you the question
- Pings are on by default, so dropped mobile connections are always detected
- IOWebSocketChannel works on Flutter web as long as you use wss
- pingInterval also reconnects automatically after a missed pong
- WebSocketChannel.connect accepts a headers map on every platform
- connectTimeout also limits how long each later message may take to send