Flutter-REST API 액세스
Flutter는 HTTP 리소스를 사용하기 위해 http 패키지를 제공합니다. http는 Future 기반 라이브러리이며 대기 및 비동기 기능을 사용합니다. 많은 고급 방법을 제공하고 REST 기반 모바일 애플리케이션의 개발을 단순화합니다.
기본 컨셉
http 패키지는 웹 요청을 수행하기 위해 높은 수준의 클래스와 http를 제공합니다.
http 클래스는 모든 유형의 HTTP 요청을 수행하는 기능을 제공합니다.
http 메소드는 Dart Map을 통해 URL 및 추가 정보 (게시물 데이터, 추가 헤더 등)를 허용합니다. 서버에 요청하고 비동기 / 대기 패턴으로 응답을 다시 수집합니다. 예를 들어, 아래 코드는 지정된 URL에서 데이터를 읽고 콘솔에 인쇄합니다.
print(await http.read('https://flutter.dev/'));
핵심 방법 중 일부는 다음과 같습니다.
read − GET 메서드를 통해 지정된 URL을 요청하고 응답을 Future <String>으로 반환합니다.
get− GET 메서드를 통해 지정된 URL을 요청하고 Future <Response>로 응답을 반환합니다. Response는 응답 정보를 담고있는 클래스입니다.
post − 제공된 데이터를 게시하여 POST 방식으로 지정된 url을 요청하고 응답을 Future <Response>로 반환
put − PUT 방식을 통해 지정된 URL을 요청하고 Future <Response>로 응답을 반환
head − HEAD 메서드를 통해 지정된 URL을 요청하고 응답을 Future <Response>로 반환
delete − DELETE 메서드를 통해 지정된 URL을 요청하고 Future <Response>로 응답을 반환합니다.
http는 또한 더 표준적인 HTTP 클라이언트 클래스 인 client를 제공합니다. 클라이언트는 지속적인 연결을 지원합니다. 특정 서버에 대한 요청이 많을 때 유용합니다. 닫기 방법을 사용하여 올바르게 닫아야합니다. 그렇지 않으면 http 클래스와 유사합니다. 샘플 코드는 다음과 같습니다.
var client = new http.Client();
try {
print(await client.get('https://flutter.dev/'));
}
finally {
client.close();
}
제품 서비스 API에 액세스
웹 서버에서 제품 데이터를 가져온 다음 ListView를 사용하여 제품을 표시하는 간단한 응용 프로그램을 만들어 보겠습니다 .
Android 스튜디오 product_rest_app 에서 새 Flutter 애플리케이션을 만듭니다 .
기본 시작 코드 (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
아래와 같이 pubspec.yaml 파일에서 http 패키지를 구성하십시오.
dependencies:
http: ^0.12.0+2
여기서는 최신 버전의 http 패키지를 사용합니다. Android 스튜디오는 pubspec.yaml이 업데이트되었다는 패키지 알림을 보냅니다.
종속성 가져 오기 옵션을 클릭하십시오. Android 스튜디오는 인터넷에서 패키지를 가져와 애플리케이션에 맞게 적절하게 구성합니다.
main.dart 파일에서 http 패키지 가져 오기-
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-server 애플리케이션을 설치하는 것입니다. http 서버 응용 프로그램을 설치하고 실행하려면 아래 단계를 따르십시오.
Nodejs 애플리케이션 ( nodejs.org ) 설치
JSONWebServer 폴더로 이동하십시오.
cd /path/to/JSONWebServer
npm을 사용하여 http-server 패키지를 설치합니다.
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
lib 폴더에 Product.dart라는 새 파일을 만들고 Product 클래스를이 폴더로 이동합니다.
Product 클래스 Product.fromMap에 팩토리 생성자를 작성하여 매핑 된 데이터 맵을 Product 객체로 변환합니다. 일반적으로 JSON 파일은 Dart Map 객체로 변환 된 후 해당 객체 (Product)로 변환됩니다.
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 데이터가 디코딩되면 Product 클래스의 fromMap을 사용하여 List <Product>로 변환됩니다.
MyApp 클래스에서 Future <Product> 유형의 제품인 새 멤버 변수를 추가하고 생성자에 포함합니다.
class MyApp extends StatelessWidget {
final Future<List<Product>> products;
MyApp({Key key, this.products}) : super(key: key);
...
MyHomePage 클래스에서 Future <Product> 유형의 새 멤버 변수 제품을 추가하고 생성자에 포함합니다. 또한 항목 변수 및 관련 메소드 인 getProducts 메소드 호출을 제거하십시오. 생성자에 제품 변수 배치. 응용 프로그램이 처음 시작될 때 한 번만 인터넷에서 제품을 가져올 수 있습니다.
class MyHomePage extends StatelessWidget {
final String title;
final Future<ListList<Product>> products;
MyHomePage({Key key, this.title, this.products}) : super(key: key);
...
위의 변경 사항을 수용하기 위해 MyApp 위젯의 빌드 방법에서 홈 옵션 (MyHomePage)을 변경하십시오.
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]),
),
);
},
);
},
);
}
}
List <Product> 유형의 제품 (오브젝트)을 전달하여 별도의 위젯으로 설계된다는 점을 제외하고는 Navigation 애플리케이션에서 사용한 것과 동일한 개념을 사용하여 제품을 나열했습니다.
마지막으로 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 속성 (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(),
],
)
)
)
]
),
)
);
}
}
마지막으로 애플리케이션을 실행하여 결과를 확인합니다. 애플리케이션을 코딩하는 동안 입력 된 로컬 정적 데이터 대신 인터넷에서 가져온 데이터라는 점을 제외하면 탐색 예제 와 동일 합니다.