Flutter Network Errors: Fail Gracefully
Treat every network call as a promise that might break. In Flutter, catch exceptions at the HTTP boundary and map them to UI states like retry widgets. The footgun is letting Dio or http exceptions bubble up, crashing the app instead of degrading gracefully.
WHY IT EXISTS: Mobile networks are inherently unreliable. A user might step into an elevator, switch from WiFi to cellular, or hit a flaky captive portal. If your Flutter app assumes every request succeeds, the result is either a crashed app from an unhandled exception or a frozen UI that leaves the user staring at a spinner. Network error handling exists to turn these inevitable failures into controlled, recoverable states.
THE MENTAL MODEL: Think of your app as a ship and the network as the ocean. You cannot calm the ocean, but you can build bulkheads. The UI layer should never feel the spray of a SocketException directly. Instead, a middle layer, the repository or service, absorbs the impact and translates it into something the presentation layer understands: a sealed Result type with states like Success, Offline, or ServerError. This separation means your widgets worry about what to show, not how to parse a stack trace.
HOW IT WORKS: In practice, you wrap your HTTP calls, whether using the http package, Dio, or GraphQL, in a try-catch block inside a dedicated service class. Catch platform-level exceptions like SocketException and TimeoutException first. Then inspect HTTP status codes: 4xx errors usually mean client mistakes or auth issues, while 5xx signals server trouble. Map each failure category to a domain-specific error enum. Return this enum inside a Result wrapper to your BLoC, Riverpod notifier, or UI. Never return raw Response objects or exceptions to the widget layer.
WHEN TO USE IT: Use this pattern on every network boundary in production apps. It is essential when building offline-first experiences, retry mechanisms, or any screen that displays remote data. If you are showing a feed, a checkout flow, or a search result, the user needs a graceful fallback when the signal drops.
WHEN NOT TO USE IT: Do not wrap every function indiscriminately. Local database writes, file I/O on device storage, or pure Dart computations should use different error models. Also avoid swallowing errors silently in development; you need visibility into failures during debugging, so log aggressively even if you suppress the crash in release.
ONE CANONICAL EXAMPLE: A news app fetches headlines with Dio. The repository catches a SocketException when the user enters a tunnel and maps it to a NetworkFailure.offline state. The Riverpod provider watching this request emits an AsyncError with that domain error. The UI switches from a loading shimmer to an offline banner with a Retry button. The user taps Retry, the provider refetches, and the list populates without ever restarting the app.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.