Skip to content

Communication HTTP et Gestion des Données

La gestion des données dans une application Flutter moderne implique plusieurs aspects cruciaux :

  • Communication client-serveur (HTTP)
  • Modèles de données, sérialisation et génération de code
  • Gestion d’état et intégration d’APIs
  • Persistance de données locales

Le Package http offre le moyen le plus simple de recevoir et d’envoyer des données vers un serveur.

Il est possible d’appeler directement les méthodes statiques get, post, put, delete, read. Il est également possible d’instancier un client Http “réutilisable”.

import 'package:http/http.dart' as http;
Future<List<Post>> _loadPosts() async {
final result = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'authorization': token},
);
return List.from(jsonDecode(result.body))
.map((e) => Post.fromMap(e))
.toList();
}

Exemple interactif sur DartPad

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MainScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Articles')),
body: FutureBuilder<List<Post>>(
future: _loadPosts(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text(
'Erreur',
style: TextStyle(color: Colors.red)
);
}
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final posts = snapshot.data!;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return ListTile(
title: Text(
post.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
post.body,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
},
);
},
),
);
}
Future<List<Post>> _loadPosts() async {
final result = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
return List.from(jsonDecode(result.body))
.map((e) => Post.fromMap(e))
.toList();
}
}

Ces méthodes permettent d’ajouter une Map pour les headers.

http.post(
Uri.parse('http://localhost:8080/reply'),
body: '{"msgId":123}',
headers: {'authorization': token},
);

Dart fournit des outils intégrés pour le traitement JSON via le package dart:convert :

import 'dart:io'; // File
import 'dart:convert'; // jsonDecode
// Exemple de traitement d'un fichier JSON
void main(List<String> arguments) async {
// 1. récupération du chemin
// 2. lecture du contenu du fichier cf File(path)
// 3. parsing json cf. jsonDecode
// 4. transformation données vers CSV
// 5. enregistrement fichier CSV
}

Pour embarquer des assets JSON, on les déclare dans le fichier pubspec.yaml :

flutter:
uses-material-design: true
assets:
- assets/logo.png
- assets/config.json
- packages/my_assets/assets/image.png

Pour utiliser cet asset :

final config = await rootBundle.loadString('assets/config.json');
class Post {
final int id;
final int userId;
final String title;
final String body;
Post({
required this.id,
required this.userId,
required this.title,
required this.body,
});
Post.fromMap(Map<String, dynamic> data)
: id = data['id'],
userId = data['userId'],
title = data['title'],
body = data['body'];
}

Freezed est un package qui :

  • simplifie la création de modèles de données immuables (génère les toString, ==, hash, copyWith…)
  • permet un mécanisme proche des Union types / swift enums
  • est compatible avec json_serializable

Freezed est basé sur build_runner, package de génération de code développé par l’équipe de Dart.

La génération de code est une technique assez courante dans le développement Dart.

dependencies:
freezed_annotation: any
dev_dependencies:
build_runner: any
freezed: any
import 'package:freezed_annotation/freezed_annotation.dart';
part 'message.freezed.dart';
part 'message.g.dart';
@freezed
class Message with _$Message {
const factory Message({
required int id,
required String userName,
required String message,
required DateTime date,
List<Reply>? replies,
}) = _Message;
factory Message.fromJson(Map<String, dynamic> json) =>
_$MessageFromJson(json);
}

Pour générer les fichiers :

Terminal window
dart run build_runner build
dart run build_runner watch

Le pattern FutureBuilder est couramment utilisé pour intégrer des APIs dans l’interface utilisateur :

FutureBuilder<List<Post>>(
future: _loadPosts(),
builder: (context, snapshot) {
// Gestion des erreurs
if (snapshot.hasError) {
return const Text(
'Erreur',
style: TextStyle(color: Colors.red)
);
}
// État de chargement
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
// Affichage des données
final posts = snapshot.data!;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return ListTile(
title: Text(post.title),
subtitle: Text(post.body),
);
},
);
},
)

Pour une gestion d’état plus avancée, considérez l’utilisation de patterns comme :

  • Bloc : stream et réactivité
  • Provider : notifiers et réactivité
  • MobX : Réactivité et ViewModel
  • Redux : global store & unidirectional data flow

Consultez Flutter architectures pour plus d’exemples.

Switch expressions pour la gestion d’état

Section titled “Switch expressions pour la gestion d’état”

Dart 3.0 introduit les switch expressions qui sont utiles pour gérer différents états de données :

final view = switch(result) {
Result.pending => ProgressView(),
Result.error => ErrorView(),
Result.data => UserList(),
};
  • Séparez la logique métier de l’interface utilisateur
  • Utilisez des services dédiés pour les appels API
  • Implémentez une gestion d’erreur robuste

Implémentez une gestion d’erreur appropriée pour les appels réseau :

Future<List<Post>> _loadPosts() async {
try {
final result = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'authorization': token},
);
if (result.statusCode == 200) {
return List.from(jsonDecode(result.body))
.map((e) => Post.fromMap(e))
.toList();
} else {
throw Exception('Failed to load posts: ${result.statusCode}');
}
} catch (e) {
throw Exception('Network error: $e');
}
}

N’oubliez pas d’inclure les headers nécessaires, notamment pour l’authentification :

final result = await http.get(
Uri.parse('https://api.example.com/data'),
headers: {
'authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);

Cette approche structurée vous permettra de créer des applications Flutter robustes et maintenables avec une intégration API efficace.