Flutter - Truy cập API REST

Flutter cung cấp gói http để tiêu thụ tài nguyên HTTP. http là một thư viện dựa trên Tương lai và sử dụng các tính năng await và async. Nó cung cấp nhiều phương pháp cấp cao và đơn giản hóa việc phát triển các ứng dụng di động dựa trên REST.

Các khái niệm cơ bản

Gói http cung cấp một lớp cấp cao và http để thực hiện các yêu cầu web.

  • Lớp http cung cấp chức năng để thực hiện tất cả các loại yêu cầu HTTP.

  • Các phương thức http chấp nhận một url và thông tin bổ sung thông qua Bản đồ Dart (dữ liệu đăng, tiêu đề bổ sung, v.v.). Nó yêu cầu máy chủ và thu thập phản hồi lại ở dạng không đồng bộ / chờ đợi. Ví dụ: mã bên dưới đọc dữ liệu từ url được chỉ định và in nó trong bảng điều khiển.

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

Một số phương pháp cốt lõi như sau:

  • read - Yêu cầu url được chỉ định thông qua phương thức GET và trả lại phản hồi dưới dạng Future <Chuỗi>

  • get- Yêu cầu url được chỉ định thông qua phương thức GET và trả về phản hồi là Future <Response>. Phản hồi là một lớp nắm giữ thông tin phản hồi.

  • post - Yêu cầu url được chỉ định thông qua phương thức POST bằng cách đăng dữ liệu được cung cấp và trả lại phản hồi dưới dạng Tương lai <Phản hồi>

  • put - Yêu cầu url được chỉ định thông qua phương thức PUT và trả lại phản hồi dưới dạng Tương lai <Phản hồi>

  • head - Yêu cầu url được chỉ định thông qua phương thức HEAD và trả lại phản hồi dưới dạng Tương lai <Phản hồi>

  • delete - Yêu cầu url được chỉ định thông qua phương thức DELETE và trả lại phản hồi dưới dạng Tương lai <Phản hồi>

http cũng cung cấp một lớp máy khách HTTP tiêu chuẩn hơn, máy khách. máy khách hỗ trợ kết nối liên tục. Nó sẽ hữu ích khi có nhiều yêu cầu được thực hiện đến một máy chủ cụ thể. Nó cần được đóng đúng cách bằng cách sử dụng phương pháp đóng. Nếu không, nó tương tự như lớp http. Mã mẫu như sau:

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

Truy cập API dịch vụ sản phẩm

Hãy để chúng tôi tạo một ứng dụng đơn giản để lấy dữ liệu sản phẩm từ máy chủ web và sau đó hiển thị các sản phẩm bằng ListView .

  • Tạo ứng dụng Flutter mới trong Android studio, product_rest_app .

  • Thay thế mã khởi động mặc định (main.dart) bằng mã product_nav_app của chúng tôi .

  • Sao chép thư mục nội dung từ product_nav_app sang product_rest_app và thêm nội dung bên trong tệp 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
  • Định cấu hình gói http trong tệp pubspec.yaml như hình dưới đây -

dependencies: 
   http: ^0.12.0+2
  • Ở đây, chúng tôi sẽ sử dụng phiên bản mới nhất của gói http. Android studio sẽ gửi một gói cảnh báo rằng pubspec.yaml đã được cập nhật.

  • Nhấp vào Nhận tùy chọn phụ thuộc. Android studio sẽ lấy gói từ Internet và định cấu hình đúng cách cho ứng dụng.

  • Nhập gói http trong tệp main.dart -

import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http;
  • Tạo tệp JSON mới, products.json với thông tin sản phẩm như hình dưới đây -

[ 
   { 
      "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" 
   } 
]
  • Tạo một thư mục mới, JSONWebServer và đặt tệp JSON, products.json.

  • Chạy bất kỳ máy chủ web nào với JSONWebServer làm thư mục gốc và lấy đường dẫn web của nó. Ví dụ: http://192.168.184.1:8000/products.json. Chúng tôi có thể sử dụng bất kỳ máy chủ web nào như apache, nginx, v.v.,

  • Cách dễ nhất là cài đặt ứng dụng máy chủ http-dựa trên nút. Làm theo các bước dưới đây để cài đặt và chạy ứng dụng máy chủ http-

    • Cài đặt ứng dụng Nodejs ( nodejs.org )

    • Chuyển đến thư mục JSONWebServer.

cd /path/to/JSONWebServer
  • Cài đặt gói máy chủ http bằng npm.

npm install -g http-server
  • Bây giờ, chạy máy chủ.

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
  • Tạo một tệp mới, Product.dart trong thư mục lib và chuyển lớp Sản phẩm vào đó.

  • Viết một hàm tạo nhà máy trong lớp Sản phẩm, Product.fromMap để chuyển đổi Bản đồ dữ liệu được ánh xạ thành đối tượng Sản phẩm. Thông thường, tệp JSON sẽ được chuyển đổi thành đối tượng Dart Map và sau đó, chuyển đổi thành đối tượng có liên quan (Sản phẩm).

factory Product.fromJson(Map<String, dynamic> data) {
   return Product(
      data['name'],
      data['description'], 
      data['price'],
      data['image'],
   );
}
  • Mã hoàn chỉnh của Product.dart như sau:

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'], 
      );
   }
}
  • Viết hai phương thức - parseProducts và fetchProducts - trong lớp chính để tìm nạp và tải thông tin sản phẩm từ máy chủ web vào đối tượng 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');
   } 
}
  • Lưu ý những điểm sau ở đây -

    • Tương lai được sử dụng để lười tải thông tin sản phẩm. Tải chậm là một khái niệm để trì hoãn việc thực thi mã cho đến khi cần thiết.

    • http.get được sử dụng để tìm nạp dữ liệu từ Internet.

    • json.decode được sử dụng để giải mã dữ liệu JSON thành đối tượng Bản đồ phi tiêu. Khi dữ liệu JSON được giải mã, nó sẽ được chuyển đổi thành Danh sách <Sản phẩm> bằng cách sử dụng fromMap của lớp Sản phẩm.

    • Trong lớp MyApp, thêm biến thành viên mới, các sản phẩm kiểu Future <Sản phẩm> và đưa nó vào hàm khởi tạo.

class MyApp extends StatelessWidget { 
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   ...
  • Trong lớp MyHomePage, thêm các sản phẩm biến thành viên mới kiểu Future <Sản phẩm> và đưa nó vào hàm tạo. Ngoài ra, hãy xóa biến items và phương thức liên quan của nó, gọi phương thức getProducts. Đặt biến sản phẩm trong hàm khởi tạo. Nó sẽ chỉ cho phép lấy các sản phẩm từ Internet một lần khi ứng dụng được khởi động lần đầu tiên.

class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<ListList<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   ...
  • Thay đổi tùy chọn trang chủ (MyHomePage) trong phương pháp xây dựng của tiện ích MyApp để phù hợp với những thay đổi ở trên -

home: MyHomePage(title: 'Product Navigation demo home page', products: products),
  • Thay đổi hàm chính để bao gồm các đối số <Sản phẩm> trong tương lai -

void main() => runApp(MyApp(fetchProduct()));
  • Tạo một widget mới, ProductBoxList để xây dựng danh sách sản phẩm trong trang chủ.

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

Lưu ý rằng chúng tôi đã sử dụng khái niệm tương tự được sử dụng trong ứng dụng Điều hướng để liệt kê sản phẩm ngoại trừ nó được thiết kế như một tiện ích con riêng biệt bằng cách chuyển các sản phẩm (đối tượng) của loại Danh sách <Sản phẩm>.

  • Cuối cùng, sửa đổi phương pháp xây dựng của tiện ích MyHomePage để lấy thông tin sản phẩm bằng cách sử dụng tùy chọn Tương lai thay vì gọi phương thức bình thường.

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()); 
            }, 
         ), 
      )
   ); 
}
  • Ở đây lưu ý rằng chúng tôi đã sử dụng tiện ích FutureBuilder để hiển thị tiện ích. FutureBuilder sẽ cố gắng tìm nạp dữ liệu từ thuộc tính tương lai của nó (thuộc loại Future <Danh sách <Sản phẩm>>). Nếu thuộc tính trong tương lai trả về dữ liệu, nó sẽ hiển thị tiện ích bằng cách sử dụng ProductBoxList, nếu không sẽ tạo ra lỗi.

  • Mã hoàn chỉnh của main.dart như sau:

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

Cuối cùng là chạy ứng dụng để xem kết quả. Nó sẽ giống như ví dụ Điều hướng của chúng tôi ngoại trừ dữ liệu từ Internet thay vì dữ liệu cục bộ, tĩnh được nhập trong khi mã hóa ứng dụng.