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

마지막으로 애플리케이션을 실행하여 결과를 확인합니다. 애플리케이션을 코딩하는 동안 입력 된 로컬 정적 데이터 대신 인터넷에서 가져온 데이터라는 점을 제외하면 탐색 예제 와 동일 합니다.