Reader Stacks

Calling an API in Flutter With the http Package

Flutter's official http package covers most API needs directly — the part worth getting right is handling the async/await flow and checking the status code before trusting the response body.

The http package, published by the Dart team itself, is the standard, officially-supported way to make network requests in Flutter — simpler than the lower-level dio package for basic REST calls, and the right default starting point for most apps.

1. Installation

flutter pub add http

2. A basic GET request

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<Product>> fetchProducts() async {
  final response = await http.get(Uri.parse('https://api.example.com/products'));

  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body);
    return data.map((json) => Product.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load products (status ${response.statusCode})');
  }
}

Checking response.statusCode explicitly before trusting response.body matters — http.get() resolves normally (it doesn't throw) even for a 404 or 500 response; only a genuine network-level failure (no connection, DNS failure) throws an exception on its own. Skipping the status check and jsonDecode-ing an error response's body directly is a common source of confusing crashes.

3. A model class with fromJson

class Product {
  final int id;
  final String name;
  final double price;

  Product({required this.id, required this.name, required this.price});

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'],
      name: json['name'],
      price: (json['price'] as num).toDouble(),
    );
  }
}

A fromJson factory constructor is the standard Dart pattern for turning a decoded JSON map into a typed object — casting numeric fields explicitly (as num).toDouble() here) matters because JSON numbers can decode as either int or double in Dart depending on the actual value, and a field expected to always be a double needs that explicit conversion to avoid a runtime type error on values that happen to arrive as whole numbers.

4. POST with a JSON body

Future<void> createProduct(String name, double price) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/products'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'name': name, 'price': price}),
  );

  if (response.statusCode != 201) {
    throw Exception('Failed to create product');
  }
}

Unlike some higher-level HTTP clients, http.post() doesn't automatically JSON-encode a map passed as the body — the Content-Type header and jsonEncode() call both need to be set explicitly, or the server receives the body as a plain, unparseable string rather than valid JSON.

5. Using FutureBuilder to display the result in the UI

FutureBuilder<List<Product>>(
  future: fetchProducts(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    final products = snapshot.data!;
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) => ListTile(title: Text(products[index].name)),
    );
  },
)

FutureBuilder is the standard Flutter widget for rendering different UI states (loading, error, success) based on a Future's current status — it re-renders automatically as the future's state changes, without needing manual setState() calls tied to the network request.

6. Setting a timeout

final response = await http
    .get(Uri.parse('https://api.example.com/products'))
    .timeout(const Duration(seconds: 10));

Without an explicit timeout, a hung or extremely slow connection leaves the request pending indefinitely from the app's perspective — wrapping the call with .timeout() and handling the resulting TimeoutException gives the UI a defined, bounded point at which to show an error instead of an indefinite loading spinner.

Topics: APIs & Integrations