Flutter - Accéder à l'API REST

Flutter fournit un package http pour consommer des ressources HTTP. http est une bibliothèque basée sur le futur et utilise les fonctionnalités await et async. Il fournit de nombreuses méthodes de haut niveau et simplifie le développement d'applications mobiles basées sur REST.

Concepts de base

Le package http fournit une classe de haut niveau et http pour faire des requêtes Web.

  • La classe http fournit des fonctionnalités pour exécuter tous les types de requêtes HTTP.

  • Les méthodes http acceptent une URL et des informations supplémentaires via Dart Map (données de publication, en-têtes supplémentaires, etc.). Il demande au serveur et recueille la réponse dans le modèle async / wait. Par exemple, le code ci-dessous lit les données à partir de l'url spécifiée et les imprime dans la console.

print(await http.read('https://flutter.dev/'));

Certaines des méthodes de base sont les suivantes -

  • read - Demander l'url spécifiée via la méthode GET et renvoyer la réponse en tant que Future <String>

  • get- Demandez l'url spécifiée via la méthode GET et renvoyez la réponse en tant que Future <Response>. La réponse est une classe contenant les informations de réponse.

  • post - Demander l'url spécifiée via la méthode POST en publiant les données fournies et renvoyer la réponse en tant que Future <Response>

  • put - Demander l'url spécifiée via la méthode PUT et renvoyer la réponse en tant que Future <Response>

  • head - Demander l'url spécifiée via la méthode HEAD et renvoyer la réponse en tant que Future <Response>

  • delete - Demander l'url spécifiée via la méthode DELETE et renvoyer la réponse en tant que Future <Response>

http fournit également une classe de client HTTP plus standard, client. le client prend en charge la connexion persistante. Cela sera utile lorsque beaucoup de demande sera faite à un serveur particulier. Il doit être fermé correctement en utilisant la méthode close. Sinon, il est similaire à la classe http. L'exemple de code est le suivant -

var client = new http.Client(); 
try { 
   print(await client.get('https://flutter.dev/')); 
} 
finally { 
   client.close(); 
}

Accéder à l'API du service produit

Créons une application simple pour obtenir des données produit à partir d'un serveur Web, puis montrons les produits à l'aide de ListView .

  • Créez une nouvelle application Flutter dans le studio Android, product_rest_app .

  • Remplacez le code de démarrage par défaut (main.dart) par notre code product_nav_app .

  • Copiez le dossier des actifs de product_nav_app vers product_rest_app et ajoutez des actifs dans le fichier pubspec.yaml.

flutter: 
   assets: 
      - assets/appimages/floppy.png 
      - assets/appimages/iphone.png 
      - assets/appimages/laptop.png 
      - assets/appimages/pendrive.png 
      - assets/appimages/pixel.png 
      - assets/appimages/tablet.png
  • Configurez le package http dans le fichier pubspec.yaml comme indiqué ci-dessous -

dependencies: 
   http: ^0.12.0+2
  • Ici, nous utiliserons la dernière version du package http. Le studio Android enverra une alerte de package indiquant que pubspec.yaml est mis à jour.

  • Cliquez sur l'option Obtenir les dépendances. Le studio Android obtiendra le package sur Internet et le configurera correctement pour l'application.

  • Importer le package http dans le fichier main.dart -

import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http;
  • Créez un nouveau fichier JSON, products.json avec les informations sur le produit comme indiqué ci-dessous -

[ 
   { 
      "name": "iPhone", 
      "description": "iPhone is the stylist phone ever", 
      "price": 1000, 
      "image": "iphone.png" 
   }, 
   { 
      "name": "Pixel", 
      "description": "Pixel is the most feature phone ever", 
      "price": 800, 
      "image": "pixel.png"
   }, 
   { 
      "name": "Laptop", 
      "description": "Laptop is most productive development tool", 
      "price": 2000, 
      "image": "laptop.png" 
   }, 
   { 
      "name": "Tablet", 
      "description": "Tablet is the most useful device ever for meeting", 
      "price": 1500, 
      "image": "tablet.png" 
   }, 
   { 
      "name": "Pendrive", 
      "description": "Pendrive is useful storage medium", 
      "price": 100, 
      "image": "pendrive.png" 
   }, 
   { 
      "name": "Floppy Drive", 
      "description": "Floppy drive is useful rescue storage medium", 
      "price": 20, 
      "image": "floppy.png" 
   } 
]
  • Créez un nouveau dossier, JSONWebServer et placez le fichier JSON, products.json.

  • Exécutez n'importe quel serveur Web avec JSONWebServer comme répertoire racine et obtenez son chemin Web. Par exemple, http://192.168.184.1:8000/products.json. Nous pouvons utiliser n'importe quel serveur Web comme apache, nginx etc.,

  • Le moyen le plus simple consiste à installer une application serveur http basée sur un nœud. Suivez les étapes ci-dessous pour installer et exécuter l'application serveur http

    • Installer l'application Nodejs ( nodejs.org )

    • Accédez au dossier JSONWebServer.

cd /path/to/JSONWebServer
  • Installez le package du serveur http à l'aide de npm.

npm install -g http-server
  • Maintenant, exécutez le serveur.

http-server . -p 8000 

Starting up http-server, serving . 
Available on: 
   http://192.168.99.1:8000
   http://127.0.0.1:8000 
   Hit CTRL-C to stop the server
  • Créez un nouveau fichier, Product.dart dans le dossier lib et déplacez-y la classe Product.

  • Ecrivez un constructeur d'usine dans la classe Product, Product.fromMap pour convertir les données mappées Map en objet Product. Normalement, le fichier JSON sera converti en objet Dart Map, puis converti en objet pertinent (produit).

factory Product.fromJson(Map<String, dynamic> data) {
   return Product(
      data['name'],
      data['description'], 
      data['price'],
      data['image'],
   );
}
  • Le code complet de Product.dart est le suivant -

class Product {
   final String name; 
   final String description;
   final int price;
   final String image; 
   
   Product(this.name, this.description, this.price, this.image); 
   factory Product.fromMap(Map<String, dynamic> json) { 
      return Product( 
         json['name'], 
         json['description'], 
         json['price'], 
         json['image'], 
      );
   }
}
  • Écrivez deux méthodes - parseProducts et fetchProducts - dans la classe principale pour récupérer et charger les informations produit du serveur Web dans l'objet List <Product>.

List<Product> parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); 
   return parsed.map<Product>((json) =>Product.fromJson(json)).toList(); 
} 
Future<List<Product>> fetchProducts() async { 
   final response = await http.get('http://192.168.1.2:8000/products.json'); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception('Unable to fetch products from the REST API');
   } 
}
  • Notez les points suivants ici -

    • Future est utilisé pour charger paresseusement les informations sur le produit. Le chargement différé est un concept pour différer l'exécution du code jusqu'à ce que cela soit nécessaire.

    • http.get est utilisé pour récupérer les données sur Internet.

    • json.decode est utilisé pour décoder les données JSON dans l'objet Dart Map. Une fois les données JSON décodées, elles seront converties en List <Product> à l'aide de fromMap de la classe Product.

    • Dans la classe MyApp, ajoutez une nouvelle variable membre, des produits de type Future <Product> et incluez-les dans le constructeur.

class MyApp extends StatelessWidget { 
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   ...
  • Dans la classe MyHomePage, ajoutez de nouveaux produits de variables membres de type Future <Product> et incluez-les dans le constructeur. En outre, supprimez la variable items et sa méthode appropriée, appel de la méthode getProducts. Placement de la variable produits dans le constructeur. Il permettra de récupérer les produits sur Internet une seule fois lors du premier démarrage de l'application.

class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<ListList<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   ...
  • Modifiez l'option d'accueil (MyHomePage) dans la méthode de construction du widget MyApp pour tenir compte des modifications ci-dessus -

home: MyHomePage(title: 'Product Navigation demo home page', products: products),
  • Modifiez la fonction principale pour inclure les arguments Future <Product> -

void main() => runApp(MyApp(fetchProduct()));
  • Créez un nouveau widget, ProductBoxList pour créer la liste de produits dans la page d'accueil.

class ProductBoxList extends StatelessWidget { 
   final List<Product> items;
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length,
         itemBuilder: (context, index) {
            return GestureDetector(
               child: ProductBox(item: items[index]), 
               onTap: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) =gt; ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
}

Notez que nous avons utilisé le même concept que celui utilisé dans l'application Navigation pour lister le produit, sauf qu'il est conçu comme un widget séparé en passant des produits (objet) de type List <Product>.

  • Enfin, modifiez la méthode de construction du widget MyHomePage pour obtenir les informations sur le produit en utilisant l'option Future au lieu de l'appel de méthode normal.

Widget build(BuildContext context) { 
   return Scaffold(
      appBar: AppBar(title: Text("Product Navigation")),
      body: Center(
         child: FutureBuilder<List<Product>>(
            future: products, builder: (context, snapshot) {
               if (snapshot.hasError) print(snapshot.error); 
               return snapshot.hasData ? ProductBoxList(items: snapshot.data)
               
               // return the ListView widget : 
               Center(child: CircularProgressIndicator()); 
            }, 
         ), 
      )
   ); 
}
  • Notez ici que nous avons utilisé le widget FutureBuilder pour rendre le widget. FutureBuilder essaiera de récupérer les données de sa future propriété (de type Future <List <Product>>). Si la future propriété renvoie des données, elle rendra le widget à l'aide de ProductBoxList, sinon lève une erreur.

  • Le code complet du main.dart est le suivant -

import 'package:flutter/material.dart'; 
import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http; 
import 'Product.dart'; 

void main() => runApp(MyApp(products: fetchProducts())); 

List<Product> parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); 
   return parsed.map<Product>((json) => Product.fromMap(json)).toList(); 
} 
Future<List<Product>> fetchProducts() async { 
   final response = await http.get('http://192.168.1.2:8000/products.json'); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception('Unable to fetch products from the REST API'); 
   } 
}
class MyApp extends StatelessWidget {
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo', 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title: 'Product Navigation demo home page', products: products), 
      ); 
   }
}
class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<List<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   
   // final items = Product.getProducts();
   @override 
   Widget build(BuildContext context) { 
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")), 
         body: Center(
            child: FutureBuilder<List<Product>>(
               future: products, builder: (context, snapshot) {
                  if (snapshot.hasError) print(snapshot.error); 
                  return snapshot.hasData ? ProductBoxList(items: snapshot.data) 
                  
                  // return the ListView widget : 
                  Center(child: CircularProgressIndicator()); 
               },
            ),
         )
      );
   }
}
class ProductBoxList extends StatelessWidget {
   final List<Product> items; 
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length, 
         itemBuilder: (context, index) { 
            return GestureDetector( 
               child: ProductBox(item: items[index]), 
               onTap: () { 
                  Navigator.push(
                     context, MaterialPageRoute( 
                        builder: (context) => ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
} 
class ProductPage extends StatelessWidget { 
   ProductPage({Key key, this.item}) : super(key: key); 
   final Product item; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.item.name),), 
         body: Center( 
            child: Container(
               padding: EdgeInsets.all(0), 
               child: Column( 
                  mainAxisAlignment: MainAxisAlignment.start, 
                  crossAxisAlignment: CrossAxisAlignment.start, 
                  children: <Widget>[
                     Image.asset("assets/appimages/" + this.item.image), 
                     Expanded( 
                        child: Container( 
                           padding: EdgeInsets.all(5), 
                           child: Column( 
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                              children: <Widget>[ 
                                 Text(this.item.name, style: 
                                    TextStyle(fontWeight: FontWeight.bold)), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 RatingBox(), 
                              ], 
                           )
                        )
                     ) 
                  ]
               ), 
            ), 
         ), 
      ); 
   } 
}
class RatingBox extends StatefulWidget { 
   @override 
   _RatingBoxState createState() =>_RatingBoxState(); 
} 
class _RatingBoxState extends State<RatingBox> { 
   int _rating = 0; 
   void _setRatingAsOne() {
      setState(() { 
         _rating = 1; 
      }); 
   }
   void _setRatingAsTwo() {
      setState(() {
         _rating = 2; 
      }); 
   }
   void _setRatingAsThree() { 
      setState(() {
         _rating = 3; 
      }); 
   }
   Widget build(BuildContext context) {
      double _size = 20; 
      print(_rating); 
      return Row(
         mainAxisAlignment: MainAxisAlignment.end, 
         crossAxisAlignment: CrossAxisAlignment.end, 
         mainAxisSize: MainAxisSize.max, 
         
         children: <Widget>[
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton( 
                  icon: (
                     _rating >= 1 
                     ? Icon(Icons.star, ize: _size,) 
                     : Icon(Icons.star_border, size: _size,)
                  ), 
                  color: Colors.red[500], onPressed: _setRatingAsOne, iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (
                     _rating >= 2 
                     ? Icon(Icons.star, size: _size,) 
                     : Icon(Icons.star_border, size: _size, )
                  ), 
                  color: Colors.red[500], 
                  onPressed: _setRatingAsTwo, 
                  iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (
                     _rating >= 3 ? 
                     Icon(Icons.star, size: _size,)
                     : Icon(Icons.star_border, size: _size,)
                  ), 
                  color: Colors.red[500], 
                  onPressed: _setRatingAsThree, 
                  iconSize: _size, 
               ), 
            ), 
         ], 
      ); 
   } 
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 140, 
         child: Card(
            child: Row( 
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.item.name, style:TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.item.description), 
                              Text("Price: " + this.item.price.toString()), 
                              RatingBox(), 
                           ], 
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   } 
}

Enfin, exécutez l'application pour voir le résultat. Ce sera le même que notre exemple de navigation, sauf que les données proviennent d'Internet au lieu de données locales et statiques saisies lors du codage de l'application.