Dans ce guide, nous allons apprendre à récupérer des données depuis internet. Contrairement à Firebase où nous utilisions des Streams (flux en temps réel), ici nous allons faire des requêtes HTTP classiques (GET) pour récupérer une liste d'articles.
L'objectif : Afficher une liste de titres et de descriptions provenant de https://jsonplaceholder.typicode.com/posts.
Prérequis
- Flutter SDK installé.
- Un éditeur de code.
Étape 1 : Création du projet
Créez un nouveau projet vierge :
flutter create mon_app_api
cd mon_app_api
Étape 2 : Ajouter le package http
Flutter ne contient pas d'outils HTTP avancés par défaut pour garder le cœur léger. Nous devons ajouter le package officiel http.
Dans votre terminal :
flutter pub add http
(Cela ajoute la ligne http: ^1.x.x dans votre fichier pubspec.yaml).
Étape 3 : Créer le Modèle de Données (Parsing)
Quand l'API nous répond, elle envoie du texte au format JSON.
Exemple de ce que l'API renvoie :
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat...",
"body": "quia et suscipit suscipit..."
}
Pour travailler proprement en Dart, nous devons convertir ce JSON en un Objet Dart. C'est ce qu'on appelle un Modèle.
Créez un fichier lib/post.dart (ou mettez ce code dans main.dart pour l'instant) :
class Post {
final int id;
final String title;
final String body;
const Post({
required this.id,
required this.title,
required this.body,
});
// Cette méthode "factory" sert à transformer le JSON en objet Post
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
Pourquoi faire ça ? Cela nous permet d'avoir l'autocomplétion (post.title) et d'éviter les fautes de frappe qu'on aurait en utilisant directement json['title'] partout.
Étape 4 : La requête HTTP
Maintenant, écrivons la fonction qui va chercher les données.
Dans lib/main.dart, importez les paquets nécessaires en haut du fichier :
import 'dart:convert'; // Pour transformer le texte en JSON
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // On alias le package pour plus de clarté
// import 'post.dart'; // Si vous avez mis la classe Post dans un autre fichier
Ensuite, créez la fonction de récupération :
// Future signifie que la donnée n'est pas là tout de suite (c'est asynchrone)
Future<List<Post>> fetchPosts() async {
// 1. On lance la requête GET
final response = await http.get(Uri.parse('[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)'));
// 2. On vérifie si le serveur a répondu "OK" (code 200)
if (response.statusCode == 200) {
// 3. On décode le corps de la réponse (String -> List dynamique)
List<dynamic> body = jsonDecode(response.body);
// 4. On transforme cette liste dynamique en liste de "Post"
List<Post> posts = body.map((dynamic item) => Post.fromJson(item)).toList();
return posts;
} else {
// Si le serveur échoue
throw Exception('Échec du chargement des posts');
}
}
Étape 5 : L'interface Utilisateur (FutureBuilder)
Pour afficher des données qui arrivent "dans le futur", Flutter dispose d'un widget parfait : FutureBuilder.
Il gère automatiquement 3 états :
- Waiting : La requête est en cours (on affiche un chargement).
- Error : La requête a échoué (on affiche l'erreur).
- Data : La requête a réussi (on affiche la liste).
Voici le code complet de lib/main.dart :
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// --- 1. LE MODÈLE ---
class Post {
final int id;
final String title;
final String body;
const Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
// --- 2. LA FONCTION API ---
Future<List<Post>> fetchPosts() async {
final response = await http.get(Uri.parse('[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)'));
if (response.statusCode == 200) {
List<dynamic> body = jsonDecode(response.body);
return body.map((dynamic item) => Post.fromJson(item)).toList();
} else {
throw Exception('Erreur de chargement');
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter API Demo',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const PostListPage(),
);
}
}
class PostListPage extends StatefulWidget {
const PostListPage({super.key});
@override
State<PostListPage> createState() => _PostListPageState();
}
class _PostListPageState extends State<PostListPage> {
// On stocke le Future dans une variable d'état pour éviter
// de refaire la requête à chaque fois que l'écran se redessine.
late Future<List<Post>> futurePosts;
@override
void initState() {
super.initState();
futurePosts = fetchPosts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Blog API')),
body: Center(
// --- 3. LE WIDGET FUTUREBUILDER ---
child: FutureBuilder<List<Post>>(
future: futurePosts,
builder: (context, snapshot) {
// CAS 1 : On a des données
if (snapshot.hasData) {
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final post = snapshot.data![index];
return ListTile(
leading: CircleAvatar(child: Text('${post.id}')),
title: Text(
post.title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
post.body,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
},
);
}
// CAS 2 : On a une erreur
else if (snapshot.hasError) {
return Text("Erreur: ${snapshot.error}");
}
// CAS 3 : Chargement en cours (par défaut)
return const CircularProgressIndicator();
},
),
),
);
}
}
Étape 6 : Test
Lancez l'application :
flutter run
Ce qui se passe :
- Au démarrage (
initState), l'application lance la requête versjsonplaceholder. - Le
FutureBuildervoit que la requête est en cours -> il affiche le CircularProgressIndicator. - Une fois la réponse reçue (environ 1 seconde), le
FutureBuilderse redessine -> il affiche la ListView avec les données.
Points Clés à retenir
- http.get : Pour lire des données.
- jsonDecode : Pour transformer le texte reçu en structure utilisable (Map ou List).
- fromJson : Une bonne pratique pour convertir proprement le JSON en objets Dart typés.
- FutureBuilder : Le widget indispensable pour gérer l'affichage de données asynchrones sans se prendre la tête avec des
setStatecomplexes.
C'est la base de toute communication avec un backend classique (Node.js, PHP, Python, Java, etc.).