Flutter - доступ к REST API
Flutter предоставляет пакет http для использования ресурсов HTTP. http - это библиотека будущего, в которой используются функции await и async. Он предоставляет множество высокоуровневых методов и упрощает разработку мобильных приложений на основе REST.
Базовые концепции
Пакет http предоставляет класс высокого уровня и http для выполнения веб-запросов.
Класс http предоставляет функциональные возможности для выполнения всех типов HTTP-запросов.
HTTP-методы принимают URL-адрес и дополнительную информацию через Dart Map (данные публикации, дополнительные заголовки и т. д.). Он запрашивает сервер и собирает ответ в шаблоне async / await. Например, приведенный ниже код считывает данные с указанного URL-адреса и выводит их на консоль.
print(await http.read('https://flutter.dev/'));
Некоторые из основных методов следующие:
read - Запросить указанный URL-адрес с помощью метода GET и вернуть ответ как Future <String>
get- Запросить указанный URL-адрес через метод GET и вернуть ответ как Future <Response>. Response - это класс, содержащий информацию об ответе.
post - Запросить указанный URL-адрес через метод POST, отправив предоставленные данные, и вернуть ответ как Future <Response>
put - Запросить указанный URL-адрес через метод PUT и вернуть ответ как Future <Response>
head - Запросить указанный URL-адрес через метод HEAD и вернуть ответ как Future <Response>
delete - Запросить указанный URL-адрес с помощью метода DELETE и вернуть ответ как Future <Response>
http также предоставляет более стандартный класс клиента HTTP, client. клиент поддерживает постоянное соединение. Это будет полезно, когда к определенному серверу нужно сделать много запросов. Его необходимо правильно закрыть методом close. В остальном он похож на класс http. Пример кода выглядит следующим образом -
var client = new http.Client();
try {
print(await client.get('https://flutter.dev/'));
}
finally {
client.close();
}
Доступ к сервисному API продукта
Давайте создадим простое приложение для получения данных о продуктах с веб-сервера, а затем покажем продукты с помощью ListView .
Создайте новое приложение Flutter в студии Android, product_rest_app .
Замените код запуска по умолчанию (main.dart) нашим кодом product_nav_app .
Скопируйте папку ресурсов из product_nav_app в product_rest_app и добавьте ресурсы в файл 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
Настройте пакет http в файле pubspec.yaml, как показано ниже -
dependencies:
http: ^0.12.0+2
Здесь мы будем использовать последнюю версию пакета http. Студия Android отправит пакетное предупреждение об обновлении pubspec.yaml.
Щелкните параметр Получить зависимости. Студия Android получит пакет из Интернета и правильно настроит его для приложения.
Импортировать http-пакет в файл main.dart -
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Создайте новый файл JSON, products.json с информацией о продукте, как показано ниже -
[
{
"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"
}
]
Создайте новую папку JSONWebServer и поместите файл JSON, products.json.
Запустите любой веб-сервер с JSONWebServer в качестве корневого каталога и получите его веб-путь. Например, http://192.168.184.1:8000/products.json. Мы можем использовать любой веб-сервер, например, apache, nginx и т. Д.,
Самый простой способ - установить приложение http-сервера на основе узла. Следуйте инструкциям ниже, чтобы установить и запустить приложение http-server.
Установите приложение Nodejs ( nodejs.org )
Перейдите в папку JSONWebServer.
cd /path/to/JSONWebServer
Установите пакет http-server с помощью npm.
npm install -g http-server
Теперь запустим сервер.
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
Создайте новый файл Product.dart в папке lib и переместите в него класс Product.
Напишите конструктор фабрики в классе Product, Product.fromMap, чтобы преобразовать сопоставленные данные Map в объект Product. Обычно файл JSON преобразуется в объект Dart Map, а затем преобразуется в соответствующий объект (продукт).
factory Product.fromJson(Map<String, dynamic> data) {
return Product(
data['name'],
data['description'],
data['price'],
data['image'],
);
}
Полный код Product.dart выглядит следующим образом:
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'],
);
}
}
Напишите два метода - parseProducts и fetchProducts - в основном классе для извлечения и загрузки информации о продукте с веб-сервера в объект 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');
}
}
Обратите внимание на следующие моменты здесь -
Future используется для ленивой загрузки информации о продукте. Ленивая загрузка - это концепция, позволяющая отложить выполнение кода до тех пор, пока это необходимо.
http.get используется для получения данных из Интернета.
json.decode используется для декодирования данных JSON в объект Dart Map. После декодирования данных JSON они будут преобразованы в List <Product> с использованием fromMap класса Product.
В классе MyApp добавьте новую переменную-член, продукты типа Future <Product> и включите ее в конструктор.
class MyApp extends StatelessWidget {
final Future<List<Product>> products;
MyApp({Key key, this.products}) : super(key: key);
...
В классе MyHomePage добавьте новые продукты-переменные-члены типа Future <Product> и включите их в конструктор. Также удалите переменную items и соответствующий ей метод, вызов метода getProducts. Размещение переменной products в конструкторе. Это позволит получить товары из Интернета только один раз при первом запуске приложения.
class MyHomePage extends StatelessWidget {
final String title;
final Future<ListList<Product>> products;
MyHomePage({Key key, this.title, this.products}) : super(key: key);
...
Измените параметр домашней страницы (MyHomePage) в методе сборки виджета MyApp, чтобы учесть вышеуказанные изменения -
home: MyHomePage(title: 'Product Navigation demo home page', products: products),
Измените основную функцию, чтобы включить аргументы Future <Product> -
void main() => runApp(MyApp(fetchProduct()));
Создайте новый виджет ProductBoxList для создания списка продуктов на домашней странице.
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]),
),
);
},
);
},
);
}
}
Обратите внимание, что мы использовали ту же концепцию, что и в приложении Navigation для вывода списка продуктов, за исключением того, что он разработан как отдельный виджет путем передачи продуктов (объекта) типа List <Product>.
Наконец, измените метод сборки виджета MyHomePage , чтобы получать информацию о продукте с использованием параметра Future вместо обычного вызова метода.
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());
},
),
)
);
}
Обратите внимание, что мы использовали виджет FutureBuilder для визуализации виджета. FutureBuilder попытается получить данные из своего будущего свойства (типа Future <List <Product>>). Если свойство future возвращает данные, оно будет отображать виджет с помощью ProductBoxList, в противном случае выдает ошибку.
Полный код main.dart выглядит следующим образом:
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(),
],
)
)
)
]
),
)
);
}
}
Наконец, запустите приложение, чтобы увидеть результат. Он будет таким же, как наш пример с навигацией, за исключением того, что данные поступают из Интернета, а не локальные статические данные, введенные при кодировании приложения.