REST API در فلاتر – Flutter
فلاتر پکیج http را برای مصرف منابع HTTP فراهم می کند. http یک کتابخانه Future-based است و از ویژگی های await و async استفاده می کند. این بسیاری از متد سطح بالا را فراهم می کند و توسعه برنامه های کاربردی موبایل مبتنی بر REST را ساده می کند.
مقالات
مفاهیم پایه
پکیج http یک کلاس سطح بالا یا high level و http را برای انجام درخواستهای ( request) وب فراهم می کند.
- کلاس http فانکشنی را برای انجام انواع درخواست های HTTP فراهم می کند.
- متدهای http یک آدرس اینترنتی و اطلاعات اضافی را از طریق Dart Map (post data, additional headers, و غیره) می پذیرند. این سرور ا درخواست می کند و پاسخ را در الگوی async / await جمع می کند. به عنوان مثال ، کد زیر داده های مربوط به آدرس مشخص شده را خوانده و آن را در کنسول چاپ می کند.
1 |
print(await http.read('https://flutter.dev/')); |
برخی از متدهای اصلی به صورت زیر است –
- read – آدرس مشخص شده را از طریق روش GET درخواست می کند و پاسخ را به عنوان Future<String> برگردانید
- get – آدرس مشخص شده را با روش GET درخواست کنید و پاسخ را به عنوان آینده Future<Response> برگردانید. Response کلاس است که اطلاعات response را نگهداری می کند.
- post – url مشخص را از طریق متد POST با پست کردن داده های ارائه شده و پاسخ را به عنوان Future<Response> بر می گرداند
- put – آدرس مشخص شده را با متد PUT درخواست می کند و پاسخ را به عنوان آینده Future <Response> برگردانید
- head – از طریق متد HEAD آدرس مشخص شده را درخواست می کند و پاسخ را به عنوان آینده <Response> بر می گرداند.
- Delete – آدرس مشخص شده را با استفاده از متد DELETE درخواست می کند و پاسخ را به عنوان Future<Response> بر می گرداند.
http همچنین یک کلاس client. کلاینت HTTP استاندارد تر ارائه می دهد. client از اتصال مداوم (persistent connection) پشتیبانی می کند. در صورتی که درخواست زیادی برای سرور ارسال شود مفید خواهد بود . باید با متد close بسته شود. در غیر این صورت ، شبیه به کلاس http است. کد نمونه به صورت زیر است –
1 2 3 4 5 6 7 |
var client = new http.Client(); try { print(await client.get('https://flutter.dev/')); } finally { client.close(); } |
دسترسی به Product service API
بگذارید یک برنامه ساده ایجاد کنیم تا داده های محصول را از یک وب سرور دریافت کنیم و سپس محصولات را با استفاده از ListView نشان دهیم .
- یک برنامه جدید Flutter را در Android studioایجاد کنید ، product_rest_app .
- کد startup فرض (main.dart) را با کد product_nav_app ما جایگزین کنید .
- پوشه assets را از product_nav_app به product_rest_app کپی کنید و assets موجود در پرونده pubspec.yaml را اضافه کنید.
1 2 3 4 5 6 7 8 |
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 پیکربندی کنید همانطور که در زیر آمده
1 2 |
dependencies: http: ^0.12.0+2 |
در اینجا از آخرین نسخه پکیج http استفاده خواهیم کرد. Studio Android یک alert package را برای بروزرسانی pubspec.yaml برای شما ارسال می کند.

- بر روی گزینه Get dependencies option کلیک کنید. studio Android پکیج را از اینترنت دریافت کرده و آن را برای برنامه به درستی پیکربندی می کند.
- پکیج http را در پرونده main.dart ایمپورت کنید –
1 2 3 |
import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; |
فایل JSON جدید ، products.json با اطلاعات مربوط به محصول مطابق شکل زیر ایجاد کنید –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
[ { "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 به عنوان root اصلی خود اجرا کنید و مسیر وب خود را طی کنید. به عنوان مثال ، http://192.168.184.1:8000/products.json. ما می توانیم از هر وب سرور مانند apache ، nginx و غیره استفاده کنیم ،
- ساده ترین
راه نصب برنامه http-serverمبتنی
بر node است. مراحل نصب زیر را برای نصب و
اجرای برنامه http-serverدنبال کنید
- برنامه Nodejs را نصب کنید ( nodejs.org )
- به پوشه JSONWebServer بروید.
1 |
cd /path/to/JSONWebServer |
پکیج http-server را با استفاده از npm نصب کنید.
1 |
npm install -g http-server |
اکنون سرور را اجرا کنید.
1 2 3 4 5 6 7 |
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 را در آن قرار دهید.
- یک سازنده factory را در کلاس Product، Product.fromMap بنویسید تا داده های نقشه برداری شده را به شی محصول تبدیل کنید. به طور معمول ، فایل JSON به شیء Dart Map تبدیل می شود و سپس به شیء مربوطه (Product) تبدیل می شود.
1 2 3 4 5 6 7 8 |
factory Product.fromJson(Map<String, dynamic> data) { return Product( data['name'], data['description'], data['price'], data['image'], ); } |
سورس کامل Product.dart به صورت زیر است –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 – را در کلاس main بنویسید تا بارگیری اطلاعات محصول از وب سرور در شی List<Product> انجام شود.
1 2 3 4 5 6 7 8 9 10 11 12 |
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 برای بارگذاری کند اطلاعات کالا استفاده می شود. Lazy loading مفهومی برای تعویق اجرای کد تا زمان لزوم است.
- http.get برای واکشی داده ها یا fetch data از اینترنت استفاده می شود.
- json.decode برای decode کردن داده های JSON به شیء Dart Map استفاده می شود. پس از اینکه داده های JSON دیکد یا رمزگشایی شد ، با استفاده از fromMap از محصول ، به لیست List<Product> تبدیل می شود.
- در کلاس MyApp ، متغیر عضو جدید ، products از نوع Future <Product> را اضافه کنید و آن را در سازنده یا constructor قرار دهید.
1 2 3 4 |
class MyApp extends StatelessWidget { final Future<List<Product>> products; MyApp({Key key, this.products}) : super(key: key); ... |
در کلاس MyHomePage ، products متغیر عضو جدید را از نوع Future <Product> اضافه کنید و آن را در سازنده قرار دهید. همچنین ، متغیر items و متد getProducts مربوطه را حذف کنید
قرار دادن متغیر products در سازنده. این امکان را می دهد که products را از طریق اینترنت فقط یک بار با شروع برنامه بارگیری کنید.
1 2 3 4 5 |
class MyHomePage extends StatelessWidget { final String title; final Future<ListList<Product>> products; MyHomePage({Key key, this.title, this.products}) : super(key: key); ... |
گزینه داخلی (MyHomePage) را در روش build از ویجت MyApp تغییر دهید تا تغییرات فوق را انجام گیرد
1 |
home: MyHomePage(title: 'Product Navigation demo home page', products: products), |
فانکشن main را تا شامل ارگومان های Future<Product> شود –
1 |
void main() => runApp(MyApp(fetchProduct())); |
برای ساختن لیست محصولات در صفحه اصلی ، ویجت جدیدی با نام ProductBoxList ایجاد کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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 برای لیست کردن محصول استفاده کردیم ، مگر اینکه با پاس دادن producta (شی) از List<Product>. به عنوان یک ویجت جداگانه طراحی شود.
- در آخر ، متد build ساخت ویجت MyHomePage را اصلاح کنید تا به جای فراخوانی با متد normal ، اطلاعات محصول را با استفاده از گزینه Future بدست آورید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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()); }, ), ) ); } |
- در اینجا توجه داشته باشید که برای ارائه widget از ویجت FutureBuilder استفاده کردیم. FutureBuilder سعی خواهد کرد داده های مربوط به ویژگی آینده خود را واکشی کند (از نوع Future <List <Product>>). future property داده ها را برگرداند ، ویجت را با استفاده از ProductBoxList رندر می کند ، در غیر این صورت خطایی به همراه دارد.
سورس کامل main.dart به شرح زیر است –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
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(), ], ) ) ) ] ), ) ); } } |
در آخر برنامه را اجرای تا نتیجه را ببینید. همان نمونه Navigation ما خواهد بود به جزاینکه داده ها هنگام کدنویسی برنامه. از اینترنت به جای داده های local, static وارد شده است.
دیدگاهتان را بنویسید