Flutter - Accediendo a la API REST

Flutter proporciona un paquete http para consumir recursos HTTP. http es una biblioteca basada en el futuro y utiliza funciones de espera y asincrónica. Proporciona muchos métodos de alto nivel y simplifica el desarrollo de aplicaciones móviles basadas en REST.

Conceptos básicos

El paquete http proporciona una clase de alto nivel y http para realizar solicitudes web.

  • La clase http proporciona funcionalidad para realizar todo tipo de solicitudes HTTP.

  • Los métodos http aceptan una URL e información adicional a través de Dart Map (datos de publicación, encabezados adicionales, etc.). Solicita al servidor y recopila la respuesta en un patrón asincrónico / en espera. Por ejemplo, el siguiente código lee los datos de la URL especificada y los imprime en la consola.

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

Algunos de los métodos principales son los siguientes:

  • read - Solicite la URL especificada a través del método GET y devuelva la respuesta como Future <String>

  • get- Solicite la URL especificada a través del método GET y devuelva la respuesta como Future <Response>. La respuesta es una clase que contiene la información de respuesta.

  • post - Solicite la URL especificada a través del método POST publicando los datos proporcionados y devuelva la respuesta como Future <Response>

  • put - Solicite la URL especificada a través del método PUT y devuelva la respuesta como Future <Response>

  • head - Solicite la URL especificada a través del método HEAD y devuelva la respuesta como Future <Response>

  • delete - Solicite la URL especificada a través del método DELETE y devuelva la respuesta como Future <Response>

http también proporciona una clase de cliente HTTP más estándar, client. el cliente admite una conexión persistente. Será útil cuando se realicen muchas solicitudes a un servidor en particular. Debe cerrarse correctamente utilizando el método de cierre. De lo contrario, es similar a la clase http. El código de muestra es el siguiente:

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

Acceso a la API de servicio de producto

Creemos una aplicación simple para obtener datos de productos de un servidor web y luego mostrar los productos usando ListView .

  • Cree una nueva aplicación Flutter en Android Studio, product_rest_app .

  • Reemplace el código de inicio predeterminado (main.dart) con nuestro código product_nav_app .

  • Copie la carpeta de activos de product_nav_app a product_rest_app y agregue activos dentro del archivo 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
  • Configure el paquete http en el archivo pubspec.yaml como se muestra a continuación:

dependencies: 
   http: ^0.12.0+2
  • Aquí, usaremos la última versión del paquete http. Android Studio enviará una alerta de paquete de que pubspec.yaml está actualizado.

  • Haga clic en la opción Obtener dependencias. Android Studio obtendrá el paquete de Internet y lo configurará correctamente para la aplicación.

  • Importar paquete http en el archivo main.dart -

import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http;
  • Cree un nuevo archivo JSON, products.json con la información del producto como se muestra a continuación:

[ 
   { 
      "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" 
   } 
]
  • Cree una nueva carpeta, JSONWebServer y coloque el archivo JSON, products.json.

  • Ejecute cualquier servidor web con JSONWebServer como directorio raíz y obtenga su ruta web. Por ejemplo, http://192.168.184.1:8000/products.json. Podemos utilizar cualquier servidor web como apache, nginx, etc.

  • La forma más sencilla es instalar una aplicación de servidor http basada en nodos. Siga los pasos que se indican a continuación para instalar y ejecutar la aplicación del servidor http

    • Instalar la aplicación Nodejs ( nodejs.org )

    • Vaya a la carpeta JSONWebServer.

cd /path/to/JSONWebServer
  • Instale el paquete del servidor http usando npm.

npm install -g http-server
  • Ahora, ejecute el servidor.

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
  • Cree un nuevo archivo, Product.dart en la carpeta lib y mueva la clase Product a él.

  • Escriba un constructor de fábrica en la clase Product, Product.fromMap para convertir el mapa de datos mapeados en el objeto Producto. Normalmente, el archivo JSON se convertirá en un objeto Dart Map y luego, se convertirá en un objeto relevante (Producto).

factory Product.fromJson(Map<String, dynamic> data) {
   return Product(
      data['name'],
      data['description'], 
      data['price'],
      data['image'],
   );
}
  • El código completo del Product.dart es el siguiente:

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'], 
      );
   }
}
  • Escriba dos métodos, parseProducts y fetchProducts, en la clase principal para obtener y cargar la información del producto desde el servidor web en el objeto 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');
   } 
}
  • Tenga en cuenta los siguientes puntos aquí:

    • Future se utiliza para cargar la información del producto de forma diferida. La carga diferida es un concepto para aplazar la ejecución del código hasta que sea necesario.

    • http.get se utiliza para obtener los datos de Internet.

    • json.decode se utiliza para decodificar los datos JSON en el objeto Dart Map. Una vez que se decodifiquen los datos JSON, se convertirán en List <Product> usando fromMap de la clase Product.

    • En la clase MyApp, agregue una nueva variable miembro, productos de tipo Future <Product> e inclúyalo en el constructor.

class MyApp extends StatelessWidget { 
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   ...
  • En la clase MyHomePage, agregue nuevos productos de variables miembro de tipo Future <Product> e inclúyalos en el constructor. Además, elimine la variable de elementos y su método relevante, llamada al método getProducts. Colocando la variable de productos en constructor. Permitirá obtener los productos de Internet solo una vez cuando se inicie la aplicación.

class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<ListList<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   ...
  • Cambie la opción de inicio (MyHomePage) en el método de compilación del widget MyApp para adaptarse a los cambios anteriores -

home: MyHomePage(title: 'Product Navigation demo home page', products: products),
  • Cambie la función principal para incluir argumentos Future <Product> -

void main() => runApp(MyApp(fetchProduct()));
  • Cree un nuevo widget, ProductBoxList para crear la lista de productos en la página de inicio.

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]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
}

Tenga en cuenta que usamos el mismo concepto utilizado en la aplicación de navegación para enumerar el producto, excepto que está diseñado como un widget separado al pasar productos (objeto) de tipo Lista <Producto>.

  • Finalmente, modifique el método de compilación del widget MyHomePage para obtener la información del producto usando la opción Future en lugar de la llamada al método 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()); 
            }, 
         ), 
      )
   ); 
}
  • Aquí tenga en cuenta que usamos el widget FutureBuilder para renderizar el widget. FutureBuilder intentará recuperar los datos de su propiedad futura (de tipo Future <List <Product>>). Si la propiedad futura devuelve datos, representará el widget utilizando ProductBoxList; de lo contrario, arrojará un error.

  • El código completo del main.dart es el siguiente:

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(), 
                           ], 
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   } 
}

Finalmente ejecute la aplicación para ver el resultado. Será igual que nuestro ejemplo de navegación, excepto que los datos son de Internet en lugar de datos locales y estáticos que se ingresaron mientras se codificaba la aplicación.