Skip to content

Intermediate · Networking, storage and Firebase · topic 31 of 50 · 35 min

HTTP requests and FutureBuilder

Explanation

Most apps load data from a web API over HTTP. With the http package, await http.get(Uri.parse(url)) returns a Response with a statusCode and body; check the status, then jsonDecode the body. Show the result with FutureBuilder, creating the Future once in initState (never in build), and handle loading, error and data. Android release builds need the INTERNET permission in the main AndroidManifest.xml.

APIs and HTTP

An API (application programming interface) on the web is a set of URLs that return data, usually as JSON. HTTP is the protocol: the app sends a request (GET to read, POST to create, PUT/PATCH to update, DELETE to remove) and gets a response with a status code and a body. 200 means OK, 201 created, 400 bad request, 401 not logged in, 404 not found, and 500 a server error.

bash
flutter pub add http
The http package is published by the Dart team.
dart
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<List<dynamic>> fetchPosts() async {
  final response = await http.get(Uri.parse('https://api.example.com/posts?limit=10'));
  if (response.statusCode == 200) {
    return jsonDecode(response.body) as List<dynamic>;
  }
  throw Exception('Server returned ${response.statusCode}');
}
Always check statusCode before decoding. Uri.parse turns the text URL into a Uri object.
dart
final res = await http.post(
  Uri.parse('https://api.example.com/orders'),
  headers: {'Content-Type': 'application/json'},
  body: jsonEncode({'item': 'Masala dosa', 'qty': 2}),
);
POST sends JSON in the body; jsonEncode turns a Map into JSON text.

In real apps, keep HTTP code out of widgets. Put it in a small repository or service class (for example ProductRepository.fetchProducts()) that returns model objects. Screens then call the repository, which makes the code easier to test with a fake repository and lets you change the API without touching every screen.

Showing the result: FutureBuilder

dart
class PostsScreen extends StatefulWidget {
  const PostsScreen({super.key});
  @override
  State<PostsScreen> createState() => _PostsScreenState();
}

class _PostsScreenState extends State<PostsScreen> {
  late final Future<List<dynamic>> _posts;

  @override
  void initState() {
    super.initState();
    _posts = fetchPosts();                       // create the Future ONCE
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<dynamic>>(
      future: _posts,
      builder: (context, snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return const Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Center(child: Text('Could not load: ${snapshot.error}'));
        }
        final posts = snapshot.data!;
        return ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, i) => ListTile(title: Text(posts[i]['title'] as String)),
        );
      },
    );
  }
}
FutureBuilder rebuilds when the Future completes; the snapshot tells you its state.
Careful: Never write FutureBuilder(future: fetchPosts(), ...) directly in build. build can run many times, and each run would start a new network request, causing repeated loading and wasted data. Create the Future in initState (or in your state management layer) and pass the stored Future.

Android: the INTERNET permission

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
android/app/src/main/AndroidManifest.xml. Debug builds already include it, so the bug often appears only in release builds.

Use HTTPS URLs: Android and iOS block plain HTTP by default in release apps. Never put secret API keys inside the app; anyone can extract them. Call your own server, which keeps secrets safe.

dio as an alternative

The dio package is a popular alternative with interceptors (for adding tokens and logging to every request), timeouts, cancellation and file uploads with progress. Many larger apps use it; http is perfectly fine for learning and simple apps.

Think of it like this: An HTTP request is like sending a courier to a warehouse with an order slip. The status code is the courier's message at the door ("delivered", "address not found", "warehouse closed"); the body is the parcel. Always read the message before opening the parcel.

Common mistakes

  • Creating the Future inside build: repeated requests and flickering.
  • Not checking statusCode, then trying to decode an HTML error page as JSON.
  • Missing INTERNET permission: works in debug, fails in release.
  • No error UI: the spinner keeps spinning forever when the network fails.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

HTTP requests and FutureBuilder · 1/6

GET | POST | PUT | DELETE

Apps load data from web APIs over HTTP. GET reads, POST creates.

Prefer a video by a teacher? Find free videos on “flutter http get request futurebuilder json api tutorial” on YouTube ↗

In real life: A PIN code lookup

A delivery address form looks up the city when the user types a 6-digit PIN code. The app calls its own backend (GET /pincodes/560001) with http, checks for 200, decodes the JSON and fills in the city and state; on 404 it shows "PIN code not found"; on a network error it shows "Check your internet connection". No secret keys live in the app.

dart
Future<String?> cityForPin(String pin) async {
  try {
    final r = await http.get(Uri.parse('https://api.example.in/pincodes/$pin'));
    if (r.statusCode == 200) return (jsonDecode(r.body) as Map<String, dynamic>)['city'] as String;
    if (r.statusCode == 404) return null;
    throw Exception('Server error ${r.statusCode}');
  } on SocketException {
    throw Exception('No internet connection');      // import 'dart:io' (not available on web)
  }
}
Different failures get different messages.

Practice (0/3 done)

Task 1 · Question

Why should FutureBuilder's future be created in initState rather than in build?

Task 2 · Question

An app loads data in debug mode but not in the Android release build. What is a likely cause?

Task 3 · Practice

Using a free public test API that returns JSON, build a screen that loads a list with http in initState, shows a spinner, an error message with a Retry button, and the list with ListView.builder. Retry should create a new Future with setState.

Quick quiz

Question 1

1.What does HTTP status 404 mean?

Question 2

2.Which function turns JSON text into Dart maps and lists?

Question 3

3.Which snapshot property tells you the Future failed?

Question 4

4.Which package offers interceptors, timeouts and upload progress?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does HTTP status 404 mean?

Answer: Not found

The requested resource does not exist.

Which function turns JSON text into Dart maps and lists?

Answer: jsonDecode

jsonDecode parses JSON text.

Which snapshot property tells you the Future failed?

Answer: hasError

Check hasError and show a message.

Which package offers interceptors, timeouts and upload progress?

Answer: dio

dio is a feature-rich HTTP client.

Since Android 9, plain http:// requests are…Related

Answer: Blocked by default (cleartext not permitted)

Cleartext HTTP is blocked by default; use HTTPS.

From Networking with Retrofit

Which HTTP method reads data without changing it?Related

Answer: GET

GET is the read in CRUD. POST creates, PUT updates and DELETE removes.

From REST APIs and JSON

Why reuse one http.Client instead of creating one per request?Related

Answer: It keeps a pool of open connections that can be reused

The client's Transport pools keep-alive connections.

From Calling other APIs safely with the HTTP client

What must the body of a JSON POST be?Related

Answer: A string made with JSON.stringify

fetch sends text; stringify the object.

From fetch: talking to servers

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Networking, storage and Firebase” before its exam: JSON models, Local storage, Firebase in Flutter.
  2. Module 8 exam: Networking, storage and Firebase · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Animations and performance. 50 topics left in the course before the final exam.

← Previous: Bloc, Cubit and choosing an approach