One safe pattern
Create the request once, store it in a Future, and let FutureBuilder draw the UI states.
Do not call the API directly inside build(). Flutter can rebuild often, and that can repeat the same request.
Flutter
Create the request once, store it in a Future, and let FutureBuilder draw the UI states.
Do not call the API directly inside build(). Flutter can rebuild often, and that can repeat the same request.
0 of 7 lessons done · proved by a submitted project with a public repository
Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.
0 of 5 done
flutter pub add httpYour app needs permission before it can fetch data on Android. Put this inside the manifest, above the application tag.
If an older tutorial says the default template already has this, do not rely on it.
<uses-permission android:name="android.permission.INTERNET" />This example uses JSONPlaceholder because it is public and simple. Replace it later with an API related to your demo app, like events, jobs, products, or tasks.
Keep parsing strict. If the response is not OK, throw an error so the UI can show a useful state.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Album {
final int id;
final String title;
Album({required this.id, required this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'] as int,
title: json['title'] as String,
);
}
}
Future<Album> fetchAlbum() async {
final uri = Uri.parse('https://jsonplaceholder.typicode.com/albums/1');
final response = await http.get(uri);
if (response.statusCode == 200) {
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}
throw Exception('Failed to load album');
}Make the Future in initState(). Then build only reads that Future.
This gives your recruiter a clean demo: spinner while loading, text when data arrives, and a visible error if something breaks.
class ApiDemoScreen extends StatefulWidget {
const ApiDemoScreen({super.key});
@override
State<ApiDemoScreen> createState() => _ApiDemoScreenState();
}
class _ApiDemoScreenState extends State<ApiDemoScreen> {
late Future<Album> albumFuture;
@override
void initState() {
super.initState();
albumFuture = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('API Demo')),
body: Center(
child: FutureBuilder<Album>(
future: albumFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (snapshot.hasData) {
return Padding(
padding: const EdgeInsets.all(16),
child: Text(snapshot.data!.title),
);
}
return const Text('No data found');
},
),
),
);
}
}Do not write future: fetchAlbum() inside FutureBuilder. It looks short, but it can run again when the widget rebuilds.
Also avoid hiding errors with empty screens. During hackathon demos, a clear error message is better than a blank app.
You can ask ChatGPT or Gemini to create a model class from sample JSON. Paste only public sample data, not private API keys.
Check the AI output for three things: correct field types, null values from the API, and whether it throws a clear error when the request fails.
Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.
0 of 5 done
Answer the quick check to finish this lesson.