Networking & REST
Irbisa · cheatsheetSeptember 13, 2026

Networking & REST

http, Dio, REST APIs, JSON serialization, interceptors

Middle Developer20 itemscompressed for a skim
  1. 01

    Which HTTP client and JSON-serialization approach would you pick for a Flutter app, and why?

    Medium

    Past a handful of endpoints the default client is dio, because interceptors, cancellation, multipart and base-URL handling come built in.

    // ── Model with json_serializable ──────────────────
    @JsonSerializable()
    class User {
      final int id;
      final String name;
      @JsonKey(name: 'email_address')
    …
  2. 02

    How do you implement pagination and infinite scroll in Flutter?

    Medium

    Infinite scroll is a paging cursor plus a scroll trigger that fires once, near the bottom of the list.

    class ProductListCubit extends Cubit<ProductListState> {
      final ProductRepository _repo;
      static const _pageSize = 20;
    
      int _page = 1;
      bool _hasMore = true;
    …
  3. 03

    How do you implement request/response interceptors in Flutter?

    Medium

    An interceptor runs on every request, response and error, so cross-cutting concerns live in one place instead of at every call site.

    // ── Using Dio with interceptors ────────────
    import 'package:dio/dio.dart';
    
    class ApiClient {
      late final Dio dio;
    …
  4. 04

    How do you upload files (multipart/form-data) in Flutter?

    Medium

    File uploads use multipart/form-data, which packages each field — including binary file content — into a single request body.

    import 'package:dio/dio.dart';
    import 'package:image_picker/image_picker.dart';
    
    final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
    
    // ── Pick + upload an image with progress ──
    …
  5. 05

    How do you cache HTTP responses in a Flutter app?

    Medium

    Caching cuts network traffic, makes repeat views feel instant, and buys you limited offline use.

    // ── 1) HTTP cache with dio_cache_interceptor ──
    import 'package:dio/dio.dart';
    import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
    import 'package:http_cache_hive_store/http_cache_hive_store.dart'; // hive_ce-backed store
    
    Future<Dio> buildCachedDio() async {
    …
  6. 06

    How do you connect to a WebSocket and handle reconnection?

    Medium

    WebSockets give you a long-lived bidirectional channel — push from server, send from client, no polling.

    import 'dart:async';
    import 'package:web_socket_channel/web_socket_channel.dart';
    
    enum WsState { disconnected, connecting, connected }
    
    class ChatClient {
    …
  7. 07

    How do you map DioException types into typed domain errors?

    Medium

    Catching DioException everywhere bleeds the HTTP layer into your business logic.

    // ── Domain hierarchy ──
    sealed class AppError implements Exception { final String message; const AppError(this.message); }
    class NetworkError      extends AppError { const NetworkError([super.m = 'No network']); }
    class TimeoutError      extends AppError { const TimeoutError([super.m = 'Timeout']); }
    class ServerError       extends AppError { final int statusCode; const ServerError(this.statusCode, [super.m = 'Server error']); }
    class BadRequestError   extends AppError {
    …
  8. 08

    How do you design an offline-first sync engine for a Flutter app?

    Hard

    Offline-first means the app works without a network and syncs in the background.

    // ── Tables ──
    // notes        (id, body, updated_at, server_id, dirty)
    // pending_ops  (id, op, table, row_id, payload, attempts, last_error, idempotency_key)
    
    import 'package:uuid/uuid.dart';
    final _uuid = const Uuid();
    …
  9. 09

    When and how do you use GraphQL in a Flutter app?

    Medium

    GraphQL is a single endpoint where the client describes the data it wants in a typed query language.

    // pubspec: graphql_flutter: ^5.x.x
    import 'package:graphql_flutter/graphql_flutter.dart';
    
    Future<GraphQLClient> buildClient() async {
      final HttpLink httpLink = HttpLink('https://api.example.com/graphql');
    …
  10. 10

    The user leaves a screen while its request is still in flight. How do you cancel it?

    Easy

    Give the screen its own CancelToken, pass it to every request the screen starts, and cancel it from dispose.

    class _FeedPageState extends State<FeedPage> {
      final _cancel = CancelToken();
    
      Future<void> _load() async {
        try {
          final res = await dio.get('/feed', cancelToken: _cancel);
    …
  11. 11

    How do you download a large file with a progress bar and resume it after the connection drops?

    Medium

    Stream the response straight to a temporary file, report progress from the received-bytes callback, and resume by asking the server for the remaining byte range.

    Future<File> downloadWithResume(String url, String finalPath) async {
      final part = File('$finalPath.part');
      final already = await part.exists() ? await part.length() : 0;
    
      final res = await dio.get<ResponseBody>(
        url,
    …
  12. 12

    The backend adds a field, renames an enum value and makes a string nullable. How does your app survive that?

    Hard

    Treat the payload as untrusted input and make the data layer the only place where a contract change is allowed to fail.

    // ── Typed readers instead of blind casts ──
    String str(Object? v, {String fallback = ''}) => v is String ? v : fallback;
    int? intOrNull(Object? v) =>
        v is int ? v : (v is num ? v.toInt() : (v is String ? int.tryParse(v) : null));
    
    enum OrderStatus { pending, shipped, delivered, unknown }
    …
  13. 13

    The app sits on a spinner for two minutes on a bad connection before anything fails — which Dio timeouts fix that, and what does each one cover?

    Easy

    Dio sets no timeouts at all by default, so a stalled request lives as long as the OS socket lets it.

    final dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: const Duration(seconds: 8),   // TCP + TLS handshake
      sendTimeout: const Duration(seconds: 15),     // only requests with a body
      receiveTimeout: const Duration(seconds: 20),  // gap between byte events
    ));
    …
  14. 14

    Search returns nonsense as soon as someone types "C# & Java" into the box — what is wrong with the way that request URL is built?

    Easy

    The term is being interpolated into a URL string, so # starts a fragment and & starts a new parameter — the server never sees what the user typed.

    const term = 'C# & Java';
    
    // ❌ interpolation: '#' truncates the query, '&' invents a parameter
    final bad = Uri.parse('https://api.example.com/search?q=$term');
    // https://api.example.com/search?q=C#%20&%20Java   -> the server sees q=C
    …
  15. 15

    One call in twenty fails on a flaky mobile network — how do you add retries without hammering the server or charging a user twice?

    Medium

    Retry only what is safe to repeat, back off exponentially with jitter, and let the server's Retry-After override your own schedule.

    class RetryInterceptor extends Interceptor {
      RetryInterceptor(this._dio, {this.maxAttempts = 3});
    
      final Dio _dio;
      final int maxAttempts;
      final _rand = Random();
    …
  16. 16

    A 3 MB JSON list lands and the app drops frames for half a second — what has Dio already moved off the main isolate, and what is still yours to move?

    Medium

    Dio already decodes a large JSON body in a background isolate; the jank you are left with is your own fromJson mapping running on the UI isolate.

    // ❌ Dio decoded off-thread, then you map 10k maps on the UI isolate
    final res = await dio.get('/feed');
    final posts = (res.data as List)
        .map((j) => Post.fromJson(j as Map<String, dynamic>))
        .toList();
    …
  17. 17

    Five screens fire requests at once with an expired access token, and the user lands back on the login screen — what did the refresh interceptor get wrong?

    Medium

    All five 401s started their own refresh: the first one rotated the refresh token, and the other four presented a token the server had just revoked.

    class AuthInterceptor extends Interceptor {
      AuthInterceptor(this._dio, this._store);
    
      final Dio _dio;                 // the app client, used to replay
      final TokenStore _store;
      final Dio _plain = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
    …
  18. 18

    A teammate builds a fresh Dio inside every repository method and latency is up a couple of hundred milliseconds per call — what is the app paying for?

    Medium

    A new client means a new connection pool, so every call repeats the DNS lookup, the TCP handshake and the TLS handshake instead of reusing a socket that is already open.

    class UserRepo {
      // ❌ a new pool, new handshakes, no keep-alive — on every single call
      Future<User> load(String id) async {
        final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
        return User.fromJson((await dio.get('/users/$id')).data);
      }
    …
  19. 19

    A screen needs details for 200 items, and Future.wait over 200 requests either trips the rate limit or loses 199 good results to one failure — what do you do instead?

    Hard

    Bound how many requests are in flight and collect a per-item outcome, so the pool is never flooded and one failure cannot discard the successes.

    // ❌ 200 requests at once, and one failure loses all 200 results
    final all = await Future.wait(ids.map(api.detail));
    
    // ✅ bounded fan-out that keeps a per-item outcome
    typedef Outcome = ({String id, Detail? value, Object? error});
    …
  20. 20

    The chat endpoint streams tokens as server-sent events, you wire it up with Dio, and the stream dies after ten seconds — what is going on and how do you parse it properly?

    Hard

    receiveTimeout measures the gap between byte events, not the length of the response, so a stream that idles between heartbeats trips it — disable it for this request and parse the body line by line instead of chunk by chunk.

    class SseEvent {
      const SseEvent(this.event, this.data, this.id);
      final String event;
      final String data;
      final String? id;
    }
    …