Implémenter le networking avec Flutter
Sommaire
- Configuration et autorisations
- Exécution des requêtes et gestion des réponses
- Parsing en arrière-plan
- Workflow : exécuter des opérations réseau
- Exemples
Configuration et autorisations
Configurez l'environnement et les autorisations spécifiques à la plateforme requises pour l'accès réseau.
- Ajoutez la dépendance du package
httpvia le terminal :flutter pub add http - Importez le package dans vos fichiers Dart :
import 'package:http/http.dart' as http; - Configurez les autorisations Android en ajoutant l'autorisation Internet à
android/app/src/main/AndroidManifest.xml:<uses-permission android:name="android.permission.INTERNET" /> - Configurez les droits macOS en ajoutant la clé client réseau à
macos/Runner/DebugProfile.entitlementsetmacos/Runner/Release.entitlements:<key>com.apple.security.network.client</key> <true/>
Exécution des requêtes et gestion des réponses
Exécutez les opérations HTTP et mappez les réponses vers des objets Dart fortement typés.
- URIs : Analysez toujours les chaînes d'URL avec
Uri.parse('your_url'). - En-têtes : Injectez les en-têtes d'autorisation et de type de contenu via le paramètre
headers. UtilisezHttpHeaders.authorizationHeaderpour les tokens d'auth. - Charges utiles : Pour les requêtes POST et PUT, encodez le corps avec
jsonEncode()dedart:convert. - Validation du statut : Évaluez
response.statusCode. Considérez200 OK(GET/PUT/DELETE) et201 CREATED(POST) comme des succès. - Gestion des erreurs : Levez des exceptions explicites pour les codes de statut non réussis. Ne retournez jamais
nullen cas d'échec, car cela empêcheFutureBuilderde déclencher son état d'erreur et provoque des indicateurs de chargement infinis. - Désérialisation : Analysez la chaîne brute avec
jsonDecode(response.body)et mappez-la vers un objet Dart personnalisé en utilisant un constructeur factory (par exemple,fromJson).
Parsing en arrière-plan
Déléguez l'analyse JSON coûteuse à une Isolate séparée pour éviter les saccades de l'UI (pertes d'images).
- Importez
package:flutter/foundation.dart. - Utilisez la fonction
compute()pour exécuter la logique d'analyse dans une isolate de fond. - Assurez-vous que la fonction d'analyse passée à
compute()est une fonction de haut niveau ou une méthode statique, car les closures ou méthodes d'instance ne peuvent pas être transmises entre isolates.
Workflow : exécuter des opérations réseau
Utilisez la liste de contrôle suivante pour implémenter et valider les opérations réseau.
Progression des tâches :
- [ ] 1. Définissez le modèle Dart fortement typé avec un constructeur factory
fromJson. - [ ] 2. Implémentez la méthode de requête réseau retournant une
Future<Model>. - [ ] 3. Appliquez la logique conditionnelle selon le type d'opération :
- Si récupération de données (GET) : Ajoutez les paramètres de requête à l'URI.
- Si mutation de données (POST/PUT) : Défissez
'Content-Type': 'application/json; charset=UTF-8'et attachez le corpsjsonEncode. - Si suppression de données (DELETE) : Retournez une instance de modèle vide en cas de succès (
200 OK).
- [ ] 4. Validez le
statusCodeet levez uneExceptionen cas d'échec. - [ ] 5. Intégrez la
Futuredans l'UI avecFutureBuilder. - [ ] 6. Gérez
snapshot.hasData,snapshot.hasError, et utilisez par défaut unCircularProgressIndicator. - [ ] 7. Boucle de rétroaction : Lancez l'app → déclenchez la requête réseau → consultez la console pour les exceptions non gérées → corrigez les erreurs de parsing ou de permission.
Exemples
Implémentation haute fidélité : récupération et parsing en arrière-plan
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// 1. Fonction de parsing de haut niveau pour Isolate
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<Object?>)
.cast<Map<String, Object?>>();
return parsed.map<Photo>(Photo.fromJson).toList();
}
// 2. Exécution réseau avec parsing en arrière-plan
Future<List<Photo>> fetchPhotos() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
headers: {
HttpHeaders.authorizationHeader: 'Bearer your_token_here',
HttpHeaders.acceptHeader: 'application/json',
},
);
if (response.statusCode == 200) {
// Déléguez l'analyse coûteuse à une isolate de fond
return compute(parsePhotos, response.body);
} else {
throw Exception('Failed to load photos. Status: ${response.statusCode}');
}
}
}
// 3. Modèle fortement typé
class Photo {
final int id;
final String title;
final String thumbnailUrl;
const Photo({
required this.id,
required this.title,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
id: json['id'] as int,
title: json['title'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
// 4. Intégration UI
class PhotoGallery extends StatefulWidget {
const PhotoGallery({super.key});
@override
State<PhotoGallery> createState() => _PhotoGalleryState();
}
class _PhotoGalleryState extends State<PhotoGallery> {
late Future<List<Photo>> _futurePhotos;
@override
void initState() {
super.initState();
// Initialisez Future une seule fois pour éviter les re-requêtes lors des rebuilds
_futurePhotos = fetchPhotos();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Photo>>(
future: _futurePhotos,
builder: (context, snapshot) {
if (snapshot.hasData) {
final photos = snapshot.data!;
return ListView.builder(
itemCount: photos.length,
itemBuilder: (context, index) => ListTile(
leading: Image.network(photos[index].thumbnailUrl),
title: Text(photos[index].title),
),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
// État de chargement par défaut
return const Center(child: CircularProgressIndicator());
},
);
}
}