Intermediate · Networking, storage and Firebase · topic 31 of 50 · 35 min
HTTP requests and FutureBuilder
Explanation
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.
flutter pub add httpimport '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}');
}final res = await http.post(
Uri.parse('https://api.example.com/orders'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'item': 'Masala dosa', 'qty': 2}),
);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
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)),
);
},
);
}
}Android: the INTERNET permission
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application ...>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.
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
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.
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)
}
}Practice (0/3 done)
Why should FutureBuilder's future be created in initState rather than in build?
An app loads data in debug mode but not in the Android release build. What is a likely cause?
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- JSON modelsNetworking, storage and Firebase · same module
- Local storageNetworking, storage and Firebase · same module
- Firebase in FlutterNetworking, storage and Firebase · same module
- Networking with RetrofitKotlin and Android App Development · Saving data and calling APIs
- REST APIs and JSONComputer Networking (CCNA level) · Automation, SDN and cloud networking
- Calling other APIs safely with the HTTP clientGo Programming (backend and cloud) · Standard library essentials
Questions people ask about this
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.
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.
What must the body of a JSON POST be?Related
Answer: A string made with JSON.stringify
fetch sends text; stringify the object.
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
- 3 more topics in “Networking, storage and Firebase” before its exam: JSON models, Local storage, Firebase in Flutter.
- Module 8 exam: Networking, storage and Firebase · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Animations and performance. 50 topics left in the course before the final exam.