flutter-use-http-package

Utilisez le package `http` pour exécuter des requêtes GET, POST, PUT ou DELETE. À utiliser lorsque vous devez récupérer des données depuis une API REST ou en envoyer.

npx skills add https://github.com/flutter/skills --skill flutter-use-http-package

Implémenter le networking avec Flutter

Sommaire

Configuration et autorisations

Configurez l'environnement et les autorisations spécifiques à la plateforme requises pour l'accès réseau.

  1. Ajoutez la dépendance du package http via le terminal :
    flutter pub add http
  2. Importez le package dans vos fichiers Dart :
    import 'package:http/http.dart' as http;
  3. Configurez les autorisations Android en ajoutant l'autorisation Internet à android/app/src/main/AndroidManifest.xml :
    <uses-permission android:name="android.permission.INTERNET" />
  4. Configurez les droits macOS en ajoutant la clé client réseau à macos/Runner/DebugProfile.entitlements et macos/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. Utilisez HttpHeaders.authorizationHeader pour les tokens d'auth.
  • Charges utiles : Pour les requêtes POST et PUT, encodez le corps avec jsonEncode() de dart:convert.
  • Validation du statut : Évaluez response.statusCode. Considérez 200 OK (GET/PUT/DELETE) et 201 CREATED (POST) comme des succès.
  • Gestion des erreurs : Levez des exceptions explicites pour les codes de statut non réussis. Ne retournez jamais null en cas d'échec, car cela empêche FutureBuilder de 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 corps jsonEncode.
    • Si suppression de données (DELETE) : Retournez une instance de modèle vide en cas de succès (200 OK).
  • [ ] 4. Validez le statusCode et levez une Exception en cas d'échec.
  • [ ] 5. Intégrez la Future dans l'UI avec FutureBuilder.
  • [ ] 6. Gérez snapshot.hasData, snapshot.hasError, et utilisez par défaut un CircularProgressIndicator.
  • [ ] 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());
      },
    );
  }
}

Skills similaires