آموزش کامل کار با پکیج DIO برای ارتباط با سرور در فلاتر

ارتباط با سرور در برنامه نویسی فلاتر و هر فریمورک دیگری تقریبا مهم ترین بخش طراحی اپلیکیشن میباشد.
امروزه کمتر برنامه ای را پیدا میکنید نیاز به ارتباط اینترنت نداشته باشد و به شکل آفلاین کار کند.
به همین دلیل در طول مسیر آموزش فلاتر باید به بخش کار با API ها دقت بسیاری داشته باشید.
اهمیت ارتباط با API در برنامه نویسی
همانطور که گفته شد بخش ارتباط با وب سرویس در روند طراحی اپلیکیشن موبایل اهمیت بسیار زیادی دارد. در ادامه بخشی از این موارد را بررسی میکنیم.
- دسترسی به دادههای اینترنتی: با استفاده از ارتباط با وب سرویس، برنامه موبایل شما قادر است به دادههای موجود در سرویسها و پلتفرمهای آنلاین دسترسی پیدا کند.
این امکان به شما این اجازه را میدهد که از منابع بیرونی، مانند پایگاه دادهها، سامانههای تحلیلی، سرویسهای ابری و غیره، دادههای لازم را در برنامه خود استفاده کنید.
به این ترتیب، شما قادر خواهید بود تا اطلاعات جدید را به برنامه خود بیافزایید و از امکانات وب بهره ببرید. - بهروزرسانی آنلاین و همگامسازی: با استفاده از وب سرویس، میتوانید اطلاعات برنامه را با سرورها و سرویسهای آنلاین همگام کنید.
این امکان به شما این امکان را میدهد تا دادههای بروزرسانی شده را به برنامههای کاربردی خود ارسال کنید و دادههای جدید را دریافت کنید.
به عنوان مثال، اگر یک برنامه اخبار دارید، میتوانید از وب سرویس برای دریافت آخرین اخبار و نمایش آنها به کاربران استفاده کنید. - اشتراک گذاری دادهها: با استفاده از وب سرویس، شما میتوانید دادههای خود را با دیگر برنامهها و سرویسها به اشتراک بگذارید.
معرفی پکیج DIO در فلاتر
پکیج Dio یک پکیج محبوب در فریمورک فلاتر است که به برنامهنویسان امکان ایجاد و مدیریت درخواستهای شبکه (Network Requests) را فراهم میکند. Dio بر پایه Dart ایجاد شده و قابلیتهای متنوعی را برای ارسال و دریافت درخواستها و پاسخهای HTTP فراهم میکند.
به طور خلاصه، Dio امکانات زیر را در برنامهنویسی Flutter فراهم میکند:
- ارسال درخواستهای HTTP: Dio امکان ارسال درخواستهای GET، POST، PUT، DELETE و … را با استفاده از روشهای مختلفی مانند
get
وpost
فراهم میکند. - مدیریت اینترسپتورها (Interceptors): Dio این امکان را به شما میدهد تا با استفاده از اینترسپتورها، درخواستها را در حین ارسال مدیریت و تغییر دهید. این امکان به شما اجازه میدهد تا توکنهای دسترسی، تغییرات در هدرها، اضافه کردن امضاها و بسیاری از عملیات دیگر را انجام دهید.
- پشتیبانی از فایلها و آپلود: Dio به شما امکان ارسال فایلها به سرور و همچنین دانلود فایلها از سرور را میدهد. این پکیج به صورت طبقهبندی شده و راحت قابل استفاده است.
- پشتیبانی از نشانگرها (Interceptors) خودکار: Dio به شما امکان مدیریت نشانگرها برای نشان دادن وضعیت درخواستها (مانند نمایش نوار بارگذاری) را میدهد. این امکان به شما اجازه میدهد تا برنامههای خود را بهبود داده و تجربه کاربر را بهتر کنید.
شروع کار با DIO
در ابتدا برای شروع کار نیاز داریم تا پکیج Dio را در پروژه نصب کنیم.
برای این کار دستور زیر را در ترمینال بنویسید.
dart pub add dio
کار با API تست
برای اینکه بتونیم از DIO استفاده کنیم نیاز به یک وب سرویس یا API تست داریم. به همین منظور از سرویس REQ | RES برای کار استفاده میکنیم که شامل چندین اندپوینت با خروجی های متفاوت میباشد.
برای شروع از API زیر استفاده میکنیم تا اطلاعات کاربر و با ارسال درخواست GET دریافت کنیم.
GET https://reqres.in/api/users/<id>
خروجی وب سرویس بالا به شکل یک JSON به حالت زیر خواهد بود.
{
"data": {
"id": 2,
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://reqres.in/img/faces/2-image.jpg"
}
}
ساخت کلاس مدل
برای مدیریت راحت خروجی APIها همیشه بهترین گزینه ساخت کلاس مدل متناظر با خروجی می باشد.
کلاس ما که User نام دارد به صورت زیر است.
class User {
User({
required this.data,
});
Data data;
factory User.fromJson(Map<String, dynamic> json) => User(
data: Data.fromJson(json["data"]),
);
Map<String, dynamic> toJson() => {
"data": data.toJson(),
};
}
برای اینکه از به وجود خطاهایی احتمالی جلوگیری کنید میتوانید از قابلیت JSON serialization نیز در کلاس مدل کمک بگیرید.
برای اینکار ابتدا پکیج های زیر را وارد پروژه کنید.
dependencies:
json_annotation: ^4.0.1
dev_dependencies:
json_serializable: ^4.1.3
build_runner: ^2.0.4
برای مدیریت بهتر دو کلاس جدا به نام های User و Data ایجاد میکنیم.
import 'package:json_annotation/json_annotation.dart';
import 'data.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
User({
required this.data,
});
Data data;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
import 'package:json_annotation/json_annotation.dart';
part 'data.g.dart';
@JsonSerializable()
class Data {
Data({
required this.id,
required this.email,
required this.firstName,
required this.lastName,
required this.avatar,
});
int id;
String email;
@JsonKey(name: 'first_name')
String firstName;
@JsonKey(name: 'last_name')
String lastName;
String avatar;
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
Map<String, dynamic> toJson() => _$DataToJson(this);
}
متدهای fromJson و toJson قرار هست که به شکل خودکار جلوتر توسط برنامه ساخته شوند.
از @JsonKey هم زمانی استفاده میکنیم نام یکی از فیلدهای کلاس با نام مقدار خروجی از سرور برابر نباشد.
برای مثال از سمت ما مقدار first_name داریم ولی در کلاس نام فیلد firstName میباشد. اگر این دو نام باهم برابر باشند نیازی به استفاده از JsonKey نداریم.
برای اینکه متدهای مورد نیاز ساخته شوند دستور زیر را در بخش ترمینال اجرا کنید.
flutter pub run build_runner build
مقداردهی کردن Dio
در ادامه بستگی به معماری و ساختار پروژه فلاتر که دارید میتوانید کلاس و متدهای مختلفی برای ارسال درخواست به سرور ایجاد کنید.
به شکل ساده میتوانید بصورت زیر درخواست های خود را ارسال کنید.
final dio = Dio();
void getHttp() async {
final response = await dio.get('https://dart.dev');
print(response);
}
ابتدا یک نمونه از کلاس dio ایجاد میکنیم و سپس با استفاده از متد get یک درخواست به سرور ارسال میشود.
مقدار بازگشتی از سرور نیز در متغیر response ذخیره میشود.
در اینجا هیچ پردازشی روی اطلاعات انجام نداده ایم و صرفا خروجی سرور را پرینت کردیم.
اما برای عمل کردن به شکل کمی حرفه ای تر کلاس جدیدی بصورت زیر تعریف میکنیم.
class DioClient {
final Dio _dio = Dio();
final _baseUrl = 'https://reqres.in/api';
// TODO: Add methods
}
برای استفاده در آینده از کلاس DioClient کمک خواهیم گرفت که آدرس بخش ثابت وب سرویس را در آن ذخیره کرده ایم.
ارسال درخواست Get در فلاتر
برای ارسال درخواست به سرور متد جدیدی به نام getUser که از نوع Future است تعریف میکنیم.
پارامتر ورودی این متد آیدی کاربری است که میخواهیم اطلاعات آن را دریافت کنیم.
Future<User> getUser({required String id}) async {
// Perform GET request to the endpoint "/users/<id>"
Response userData = await _dio.get(_baseUrl + '/users/$id');
// Prints the raw data returned by the server
print('User Info: ${userData.data}');
// Parsing the raw JSON data to the User class
User user = User.fromJson(userData.data);
return user;
}
این کد بدون هیچ مشکلی اجرا میشود. اما مسئله مدیریت خطا در این متد رعایت نشده است و اگر برنامه دچار خطایی شود اپلیکیشن بسته میشود.
به همین دلیل کدهای بالا را به شکل زیر تغییر میدهیم.
Future<User?> getUser({required String id}) async {
User? user;
try {
Response userData = await _dio.get(_baseUrl + '/users/$id');
print('User Info: ${userData.data}');
user = User.fromJson(userData.data);
} on DioError catch (e) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx and is also not 304.
if (e.response != null) {
print('Dio error!');
print('STATUS: ${e.response?.statusCode}');
print('DATA: ${e.response?.data}');
print('HEADERS: ${e.response?.headers}');
} else {
// Error due to setting up or sending the request
print('Error sending request!');
print(e.message);
}
}
return user;
}
تمام کدها را در قالب try-catch قرار میدهیم.
مقدار خروجی متد را هم تغییر دادیم تا مقادیر null را بتواند پذیرش کند.
برای اینکه اطلاعات کاربر را نمایش دهیم یک صفحه به نام HomePage ایجاد میکنیم.
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final DioClient _client = DioClient();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Info'),
),
body: Center(
child: FutureBuilder<User?>(
future: _client.getUser(id: '1'),
builder: (context, snapshot) {
if (snapshot.hasData) {
User? userInfo = snapshot.data;
if (userInfo != null) {
Data userData = userInfo.data;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(userData.avatar),
SizedBox(height: 8.0),
Text(
'${userInfo.data.firstName} ${userInfo.data.lastName}',
style: TextStyle(fontSize: 16.0),
),
Text(
userData.email,
style: TextStyle(fontSize: 16.0),
),
],
);
}
}
return CircularProgressIndicator();
},
),
),
);
}
}
در این صفحه یک نمونه از کلاس DioClient تعریف میکنیم.
در داخل متد build هم از ویجت FutureBuilder استفاده میکنیم.
تا زمان دریافت اطلاعات از سرور نیز با کمک ویجت CircularProgressIndicator یک المان لودینگ را نمایش میدهیم.

ارسال درخواست POST به سرور
با کمک پکیج Dio به راحتی متوانید درخواست های از نوع POST را نیز در فلاتر به سرور ارسال کنید.
به همین منظور کلاس جدیدی به نام UserInfo به شکل زیر تعریف میکنیم.
part 'user_info.g.dart';
@JsonSerializable()
class UserInfo {
String name;
String job;
String? id;
String? createdAt;
String? updatedAt;
UserInfo({
required this.name,
required this.job,
this.id,
this.createdAt,
this.updatedAt,
});
factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
Map<String, dynamic> toJson() => _$UserInfoToJson(this);
}
در کلاس DioClient هم متدی جدیدی برای ساخت کاربر ایجاد میکنیم که وظیفه ارسال درخواست POST را برعهده دارد.
Future<UserInfo?> createUser({required UserInfo userInfo}) async {
UserInfo? retrievedUser;
try {
Response response = await _dio.post(
_baseUrl + '/users',
data: userInfo.toJson(),
);
print('User created: ${response.data}');
retrievedUser = UserInfo.fromJson(response.data);
} catch (e) {
print('Error creating user: $e');
}
return retrievedUser;
}
این متد یک UserInfo به عنوان پارامتر ورودی دریافت میکند.
این درخواست به آدرس /users ارسال میشود.
اطلاعات بازگشتی از این API شامل اطلاعات کاربر و زمان ساخته شدن آن میباشد.

به همین شکل با استفاده از متدهای put و delete درخواست های متفاوتی به وب سرویسی که در اختیار دارید ارسال کنید.
پکیچ Dio شامل امکانات مختلفی میباشد. برای مثال میتوانید مقدار آدرس پایه را مشخص کنید و هر بار آن را در درخواست های ارسالی قرار ندهید.
final Dio _dio = Dio(
BaseOptions(
baseUrl: 'https://reqres.in/api',
connectTimeout: 5000,
receiveTimeout: 3000,
),
);
در این حالت هنگام استفاده از این کلاس دیگر فقط آدرس اندپوینت مورد نظر را قرار میدهیم.
Response response = await _dio.post(
'/users',
data: userInfo.toJson(),
);
ارسال چندین درخواست
گاهی اوقات نیاز داریم در یک صفحه اطلاعات از چندین API دریافت شود و بعد از تکمیل خروجی صفحه به کاربر نمایش داده شود یا هر عملیات دیگری, برای این کار میتوانید به شکل زیر عمل کنید و چندین درخواست را به شکل همزمان ارسال کنید.
response = await Future.wait([dio.post('/info'), dio.get('/token')]);
البته به شکل دقیق تر درخواست ها همزمان ارسال نمیشود. هروقت یک عملیات تکمیل شد درخواست بعدی شروع میشود.
برای دریافت خروجی ها هم در اینجا مقدار خروجی ذخیره شده در response یک لیست میباشد.
برای دسترسی به خروجی عملیات اول میتوانید از response[0] استفاده کنید.
آپلود فایل در سرور
یکی از کارهای مهمی که با استفاده از پکیج Dio به راحتی متوانید انجام دهید آپلود فایل با استفاده از FormData است.
به مثال زیر توجه کنید.
String imagePath;
FormData formData = FormData.fromMap({
"image": await MultipartFile.fromFile(
imagePath,
filename: "upload.jpeg",
),
});
Response response = await _dio.post(
'/search',
data: formData,
onSendProgress: (int sent, int total) {
print('$sent $total');
},
);
از متد onSendProgress میتوانید برای نمایش نوار پیشرفت آپلود استفاده کنید.
برای تغییر هدر و قرار دادن اطلاعات مختلف مثل توکن نیز میتوانید به صورت زیر عمل کنید.
dio.options.headers['content-Type'] = 'application/json';
dio.options.headers["authorization"] = "token ${token}";
امیدوارم که این مطلب برای شما مفید باشه, در صورت وجود هرگونه سوالی میتوانید از بخش نظرات و همچنین بخش پرسش و پاسخ وبسایت استفاده کنید.
دیدگاهتان را بنویسید