iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

HTTP / dio

The `package:http` library is the cross-platform HTTP client Flutter ships. It handles JSON APIs, multipart uploads, custom headers, and cancellation. For anything more — interceptors, retry, refresh-token flow — graduate to `dio`. Keep the network layer in a service and let widgets consume domain objects, not Response.

A typed HTTP client, retry, multipart, and cancellation

EXAMPLE
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;

// 1) Typed model so the rest of the app does not see JSON
class Order {
  final String id; final String customer; final int totalCents; final String status;
  Order({required this.id, required this.customer, required this.totalCents, required this.status});
  factory Order.fromJson(Map<String, dynamic> j) =>
    Order(id: j['id'], customer: j['customer'], totalCents: j['total_cents'], status: j['status']);
}

// 2) A small service with timeout, retries, and structured errors
class ApiClient {
  ApiClient({http.Client? client, this.base = 'https://api.example.com'}) :
    _c = client ?? http.Client();
  final http.Client _c;
  final String base;
  String? bearer;

  Map<String, String> _headers([Map<String, String>? extra]) => {
    'accept': 'application/json',
    if (bearer != null) 'authorization': 'Bearer $bearer',
    ...?extra,
  };

  Future<T> _retry<T>(Future<T> Function() op,
                      {int max = 3, Duration delay = const Duration(milliseconds: 250)}) async {
    var attempt = 0;
    while (true) {
      try { return await op().timeout(const Duration(seconds: 8)); }
      on TimeoutException catch (_) { /* fall through to retry */ }
      on SocketException catch (_)  { /* fall through to retry */ }
      attempt++;
      if (attempt >= max) rethrow;
      await Future.delayed(delay * (1 << attempt));   // 0.5s, 1s, 2s
    }
  }

  Future<List<Order>> listOrders(String customerId) async {
    final uri = Uri.parse('$base/orders').replace(queryParameters: {'customer': customerId});
    final res = await _retry(() => _c.get(uri, headers: _headers()));
    if (res.statusCode != 200) throw _toError(res);
    final list = (jsonDecode(res.body) as List).cast<Map<String, dynamic>>();
    return list.map(Order.fromJson).toList();
  }

  Future<Order> placeOrder(Order order) async {
    final uri = Uri.parse('$base/orders');
    final res = await _retry(() => _c.post(uri,
      headers: _headers({'content-type': 'application/json'}),
      body: jsonEncode({'customer': order.customer, 'total_cents': order.totalCents}),
    ));
    if (res.statusCode != 201) throw _toError(res);
    return Order.fromJson(jsonDecode(res.body));
  }

  Future<String> uploadReceipt(File file) async {
    final req = http.MultipartRequest('POST', Uri.parse('$base/receipts'))
      ..headers.addAll(_headers())
      ..fields['note'] = 'mobile-upload'
      ..files.add(await http.MultipartFile.fromPath('file', file.path));
    final res = await http.Response.fromStream(await req.send());
    if (res.statusCode != 201) throw _toError(res);
    return jsonDecode(res.body)['url'];
  }

  Future<void> close() async => _c.close();
  ApiError _toError(http.Response r) => ApiError(r.statusCode, r.body);
}

class ApiError implements Exception {
  ApiError(this.status, this.body);
  final int status; final String body;
  @override String toString() => 'ApiError($status): $body';
}

// 3) Use it from a screen — service returns domain objects, widget renders them
final api = ApiClient()..bearer = 'token-here';

Future<void> demo() async {
  final orders = await api.listOrders('u1');
  for (final o in orders) {
    print('${o.id}: ${o.customer} ${o.totalCents}');
  }
  await api.close();
}

Why it matters

Hide HTTP details behind a service. The rest of the app should never see `http.Response`, `jsonDecode`, or status codes — that boundary is where retries, telemetry, and refresh-token flows belong. Without it, every widget grows its own copy of the same error handling.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import 'package:http/http.dart' as http;
final r = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
final post = jsonDecode(r.body);
Try it Yourself »

Discussion

Loading…