Api Dio 1

الآن ندخل إلى واحدة من أهم المراحل في فهم الـ API و Dio.

معظم المبتدئيين يتعلمون:

final response = await dio.get(...);
print(response.data);
Dart

ثم ينتقلون مباشرة إلى Models دون أن يفهموا:

  • ما هو Response؟
  • ما هو data؟
  • لماذا أحياناً تكون List؟
  • لماذا أحياناً تكون Map؟
  • لماذا لا نستخدم jsonDecode مع Dio؟
  • ما هو dynamic؟
  • كيف يحول Dio الـ JSON تلقائياً؟

وهنا يبدأ التخبط لاحقاً.

المرحلة الثانية: تشريح Response بالكامل

لنبدأ من هذا الكود:

Response response = await dio.get(
  'https://jsonplaceholder.typicode.com/posts',
);
Dart
ما الذي يحدث خلف الكواليس؟

عندما يصل الطلب إلى السيرفر يحدث التالي:

السيرفر يعيد:

HTTP/1.1 200 OK

Content-Type: application/json
Dart

ثم يعيد Body:

[
  {
    "userId": 1,
    "id": 1,
    "title": "test",
    "body": "hello"
  }
]
Dart

Dio يستقبل هذا الرد ويضعه داخل كائن اسمه:

Response
Dart
شكل Response الحقيقي

بشكل مبسط:

class Response<T> {

  T? data;

  int? statusCode;

  String? statusMessage;

  Headers headers;

  RequestOptions requestOptions;
}
Dart

هذا ليس الكود الحقيقي بالكامل لكنه يوضح الفكرة.

ماذا يعني Response<T> ؟

لاحظ:

Response<T>
Dart

وليس:

Response
Dart

هنا يوجد Generic.

ما هو Generic ؟

مثال:

List<String>
Dart

معناها:

هذه القائمة تحتوي Strings فقط
Dart

مثال:

List<int>
Dart

معناها:

هذه القائمة تحتوي أرقام فقط
Dart

نفس الفكرة هنا:

Response<T>
Dart

أي:

نوع البيانات داخل response.data
Dart
لماذا غالباً نكتب
Response response
Dart

بدلاً من:

Response<List>
Dart

لأن Dio يحدد النوع تلقائياً غالباً.

لنطبع Response كاملاً
print(response);
Dart

ستشاهد شيئاً قريباً من:

Instance of Response
Dart

لذلك نحتاج الوصول للخصائص.

أول خاصية
response.data
Dart

وهذه أهم خاصية في Dio بالكامل.

ما هو data ؟

هو Body الخاص بالرد.

تذكر:

السيرفر يعيد:

Headers

Body
Dart

Dio يخزن Body هنا:

response.data
Dart
مثال عملي

السيرفر يعيد:

{
  "id":1,
  "name":"Ahmed"
}
Dart

عندها:

print(response.data);
Dart

النتيجة:

{
 id:1,
 name:Ahmed
}
Dart
مثال آخر

إذا أعاد السيرفر:

[
 {
   "id":1
 },
 {
   "id":2
 }
]
Dart

عندها:

response.data
Dart

تصبح:

[
 {id:1},
 {id:2}
]
Dart
سؤال مهم جداً

كيف عرف Dio ذلك؟

كيف عرف أن هذه List؟

أو Map؟

السر هنا

السيرفر يرسل:

Content-Type: application/json
Dart

فيرى Dio أن الرد JSON.

فيقوم تلقائياً بـ:

jsonDecode(...)
Dart

خلف الكواليس.

لذلك لا نفعل هذا
jsonDecode(response.data);
Dart

خطأ.

لماذا خطأ؟

لأن Dio قام بالتحويل مسبقاً.

مقارنة مع package:http

في package:http كنا نفعل:

var response = await http.get(...);

jsonDecode(response.body);
Dart

أما Dio:

response.data
Dart

جاهزة مباشرة.

ما نوع response.data ؟

جرّب:

print(response.data.runtimeType);
Dart
عند جلب posts
https://jsonplaceholder.typicode.com/posts
Dart

النتيجة:

List<dynamic>
Dart
لماذا List ؟

لأن JSON تبدأ بـ:

[
]
Dart

أي Array.

قاعدة ذهبية

إذا بدأ JSON بـ:

[
]
Dart

فالنوع:

List
Dart
وإذا بدأ بـ
{
}
Dart

فالنوع:

Map
Dart
مثال
[
 {
   "id":1
 }
]
Dart

List<dynamic>
Dart
مثال
{
 "id":1
}
Dart

Map<String,dynamic>
Dart
ما معنى dynamic ؟

من أكثر الكلمات التي تخيف المبتدئين.

مثال
dynamic value;
Dart

معناها:

قد يكون أي شيء
Dart
يمكن أن يكون
String
Dart

أو:

int
Dart

أو:

List
Dart

أو:

Map
Dart
لذلك
response.data
Dart

نوعها غالباً:

dynamic
Dart

لأن Dio لا يعرف مسبقاً ماذا سيرجع السيرفر.

كيف نكتشف النوع؟

مثال:

print(response.data.runtimeType);
Dart
runtimeType

تعني:

ما نوع الكائن أثناء التشغيل؟
Dart
مثال
print(response.data.runtimeType);
Dart

قد تطبع:

List<dynamic>
Dart

أو:

_Map<String,dynamic>
Dart
الحالة الأولى: List

مثال:

[
 {
  "id":1
 },
 {
  "id":2
 }
]
Dart
الوصول لأول عنصر
print(response.data[0]);
Dart
الوصول للـ id
print(response.data[0]['id']);
Dart

الناتج:

1
Dart
الحالة الثانية: Map

مثال:

{
 "id":1,
 "name":"Ahmed"
}
Dart
الوصول للقيم
print(response.data['id']);
Dart
ماذا لو أخطأت؟

مثال:

الرد الحقيقي:

Map
Dart

وأنت كتبت:

response.data[0]
Dart

ستحصل على Exception.

لأن:

MapList
Dart
خاصية statusCode
print(response.statusCode);
Dart
ماذا تعني؟

هي حالة الطلب.

مثال
200
Dart

نجاح.


مثال

404
Dart

الرابط غير موجود.


مثال

500
Dart

خطأ داخل السيرفر.


خاصية statusMessage
print(response.statusMessage);
Dart

مثال:

OK
Dart

أو:

Not Found
Dart

خاصية headers
print(response.headers);
Dart

قد ترى:

content-type: application/json
Dart

لماذا مهمة؟

لأنها تخبرك:

  • نوع البيانات
  • التشفير
  • الكوكيز
  • معلومات إضافية

خاصية requestOptions

هذه من الخصائص التي لا يشرحها الكثيرون.

print(response.requestOptions.path);
Dart

ماذا تحتوي؟

الطلب الأصلي الذي أرسلته.

مثلاً:

response.requestOptions.method
Dart

الناتج:

GET
Dart

response.requestOptions.path
Dart

الناتج:

/posts
Dart

response.requestOptions.headers
Dart

الناتج:

كل Headers التي أرسلتها
Dart

تجربة عملية مهمة جداً

ضع هذا الكود:

Future<void> getPosts() async {

  Response response = await dio.get(
    'https://jsonplaceholder.typicode.com/posts',
  );

  print('DATA');
  print(response.data);

  print('TYPE');
  print(response.data.runtimeType);

  print('STATUS');
  print(response.statusCode);

  print('MESSAGE');
  print(response.statusMessage);

  print('HEADERS');
  print(response.headers);

}
Dart

وشاهد النتائج بنفسك.


ماذا يجب أن يفهم الطالب قبل الانتقال للـ Models؟

يجب أن يكون قادراً على الإجابة عن هذه الأسئلة:

✅ ما هو Response؟

✅ ما الفرق بين Body و Headers؟

✅ أين توجد البيانات الحقيقية؟

✅ ما هو response.data؟

✅ لماذا Dio لا يحتاج jsonDecode؟

✅ متى تكون data List؟

✅ متى تكون data Map؟

✅ ما هو dynamic؟

✅ ما هو runtimeType؟

✅ ما هو statusCode؟

✅ ما هو requestOptions؟

إذا فهم هذه النقاط فعلاً، فمرحلة Model + fromJson + toJson ستصبح سهلة جداً، لأن الطالب أصبح يعرف بالضبط ما الذي يستلمه من السيرفر وكيف يبدو داخل Dart.

المرحلة التالية

الآن نصل إلى المرحلة التي تعتبر نقطة التحول الحقيقية في تعلم الـ API.

قبل هذه المرحلة كان الطالب يتعامل مع البيانات هكذا:

response.data[0]['title']
Dart

أو:

response.data['name']
Dart

وهذا مناسب للتجارب السريعة فقط.

لكن في التطبيقات الحقيقية لا نعمل بهذه الطريقة.


المرحلة الثالثة: فهم JSON وتحويله إلى Model

لماذا نحتاج Model أصلاً؟

لنفترض أن السيرفر أعاد:

{
  "userId": 1,
  "id": 1,
  "title": "Flutter",
  "body": "Learning Dio"
}
Dart

الطريقة البدائية:

print(response.data['title']);
Dart

المشكلة:

إذا أخطأت بالحرف:

print(response.data['titl']);
Dart

لن يكتشف Dart الخطأ أثناء الكتابة.

سيظهر Runtime Error لاحقاً.


الحل

ننشئ كلاس يمثل هذا الـ JSON.

class PostModel {

}
Dart

هذا الكلاس يصبح نسخة Dart من البيانات القادمة من السيرفر.


ما هو Model؟

الـ Model هو تمثيل للبيانات داخل التطبيق.

مثال:

JSON:

{
  "id": 1,
  "name": "Ahmed"
}
Dart

Model:

class UserModel {

  final int id;

  final String name;

  UserModel({
    required this.id,
    required this.name,
  });
}
Dart

لماذا هذا أفضل؟

بدلاً من:

response.data['name']
Dart

نكتب:

user.name
Dart

وهذا:

✅ أوضح

✅ أسهل

✅ أقل أخطاء

✅ يدعم Auto Complete


إنشاء أول Model

أنشئ ملف:

models/post_model.dart
Dart

تحليل JSON أولاً

الـ API تعيد:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere",
  "body": "quia et suscipit..."
}
Dart

إذن لدينا 4 حقول.


إنشاء الخصائص

class PostModel {

  final int userId;

  final int id;

  final String title;

  final String body;

}
Dart

لماذا final؟

لأن البيانات القادمة من السيرفر غالباً لا نريد تغييرها مباشرة.

مثال:

post.title = 'new title';
Dart

غير مسموح.

وهذا يجعل البيانات أكثر أماناً.


إنشاء Constructor

class PostModel {

  final int userId;
  final int id;
  final String title;
  final String body;

  PostModel({
    required this.userId,
    required this.id,
    required this.title,
    required this.body,
  });
}
Dart

ما معنى required؟

تعني:

لا يمكن إنشاء الكائن بدون هذه القيمة
Dart

مثال:

PostModel(
  userId: 1,
  id: 1,
  title: 'Flutter',
  body: 'Dio',
);
Dart

صحيح.

أما:

PostModel(
  id: 1,
);
Dart

خطأ.


المشكلة الحالية

لدينا JSON:

{
  "userId": 1,
  "id": 1,
  "title": "Flutter",
  "body": "Dio"
}
Dart

لكن Constructor يحتاج:

PostModel(...)
Dart

كيف نحول JSON إلى Object؟


هنا يأتي fromJson


ما هو fromJson؟

هو Factory Constructor مسؤول عن تحويل:

JSON
Dart

إلى:

PostModel
Dart

الشكل الأساسي

factory PostModel.fromJson(
  Map<String, dynamic> json,
) {

}
Dart

شرح كلمة factory

قبل الفهم التقني:

الـ Constructor العادي:

PostModel(...)
Dart

ينشئ كائناً جديداً دائماً.

أما factory:

يمكنه إنشاء الكائن بطريقة خاصة.

في حالتنا:

سيقرأ JSON ثم ينشئ Model.


لماذا Map<String,dynamic> ؟

لأن JSON بعد أن يحوله Dio يصبح:

{
 'userId': 1,
 'id': 1,
 'title': 'Flutter',
 'body': 'Dio'
}
Dart

وهذا في Dart عبارة عن:

Map<String,dynamic>
Dart

بناء fromJson

factory PostModel.fromJson(
  Map<String, dynamic> json,
) {
  return PostModel(
    userId: json['userId'],
    id: json['id'],
    title: json['title'],
    body: json['body'],
  );
}
Dart

ماذا يحدث هنا؟

عندما نستدعي:

PostModel.fromJson(json)
Dart

يقوم Dart بتنفيذ:

return PostModel(...)
Dart

ثم يعيد الكائن.


تجربة عملية

لدينا:

Map<String,dynamic> json = {

  'userId':1,

  'id':1,

  'title':'Flutter',

  'body':'Learning Dio'

};
Dart

التحويل

PostModel post =
    PostModel.fromJson(json);
Dart

النتيجة

الآن:

print(post.title);
Dart

تعطي:

Flutter
Dart

بدلاً من:

response.data['title']
Dart

كيف نحول List كاملة؟

السيرفر يعيد:

[
  {...},
  {...},
  {...}
]
Dart

أي قائمة Posts.


نوع البيانات القادم

List<dynamic>
Dart

التحويل اليدوي

List<PostModel> posts = [];

for (var item in response.data) {

  posts.add(
    PostModel.fromJson(item),
  );

}
Dart

ماذا يحدث؟

العنصر الأول:

{
  'id':1,
  ...
}
Dart

PostModel
Dart

العنصر الثاني:

{
  'id':2,
  ...
}
Dart

PostModel
Dart

في النهاية:

List<PostModel>
Dart

الطريقة الاحترافية باستخدام map

بدلاً من loop:

List<PostModel> posts =
    response.data.map((item) {

      return PostModel.fromJson(item);

    }).toList();
Dart

شرح map

map لا تغير القائمة الأصلية.

بل تنشئ قائمة جديدة.

مثال:

[1,2,3]
Dart

['1','2','3']
Dart

في حالتنا:

JSON
Dart

PostModel
Dart

لماذا نستخدم toList؟

لأن map تعيد:

Iterable
Dart

وليس:

List
Dart

لذلك نكتب:

.toList()
Dart

للحصول على:

List<PostModel>
Dart

الكود النهائي

داخل ApiService:

Future<List<PostModel>> getPosts() async {

  Response response = await dio.get(
    'https://jsonplaceholder.typicode.com/posts',
  );

  List<PostModel> posts =
      response.data
          .map<PostModel>(
            (json) => PostModel.fromJson(json),
          )
          .toList();

  return posts;
}
Dart

لماذا غيرنا نوع Future؟

في البداية كان:

Future<void>
Dart

الآن:

Future<List<PostModel>>
Dart

لأن الدالة لم تعد تطبع فقط.

بل تعيد بيانات.


النتيجة النهائية

بدلاً من:

response.data[0]['title']

أصبح لدينا:

posts[0].title
Dart

وهذه خطوة احترافية جداً لأنها تجعل الكود:

✅ واضح

✅ آمن

✅ قابل للصيانة

✅ مناسب للمشاريع الكبيرة


قبل الانتقال إلى POST Request، أنصح بمرحلة صغيرة لكنها مهمة جداً وهي التعمق في fromJson و toJson و map و List<PostModel> و Iterable بالتفصيل لأن 80% من أخطاء المبتدئين في Flutter API تأتي من سوء فهم هذه المفاهيم، وليس من Dio نفسه.

في هذه المرحلة سنبني تطبيقاً كاملاً بسيطاً يجلب Posts ويعرضها داخل ListView باستخدام Dio و Model فقط، بدون Cubit وبدون Clean Architecture.

هيكل المشروع

lib

├── main.dart

├── models
│   └── post_model.dart

├── services
│   └── api_service.dart

└── screens
    └── posts_page.dart
Dart

الملف الأول

models/post_model.dart

هذا الملف يمثل البيانات القادمة من السيرفر.

class PostModel {

  // رقم المستخدم صاحب المنشور
  final int userId;

  // رقم المنشور
  final int id;

  // عنوان المنشور
  final String title;

  // محتوى المنشور
  final String body;

  // Constructor لإنشاء كائن جديد من PostModel
  PostModel({
    required this.userId,
    required this.id,
    required this.title,
    required this.body,
  });

  /*
  fromJson

  مسؤول عن تحويل JSON القادم من السيرفر
  إلى كائن PostModel

  json القادم يكون بالشكل:

  {
    "userId":1,
    "id":1,
    "title":"Flutter",
    "body":"Learning Dio"
  }
  */
  factory PostModel.fromJson(
    Map<String, dynamic> json,
  ) {
    return PostModel(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }

  /*
  toJson

  العكس تماماً.

  يحول الكائن إلى Map

  سنحتاجه لاحقاً في POST و PUT
  */
  Map<String, dynamic> toJson() {
    return {
      'userId': userId,
      'id': id,
      'title': title,
      'body': body,
    };
  }
}
Dart

الملف الثاني

services/api_service.dart

هذا الملف مسؤول عن الشبكة.

import 'package:dio/dio.dart';

import '../models/post_model.dart';

class ApiService {

  /*
  إنشاء Dio Object

  Dio هو المسؤول عن إرسال واستقبال الطلبات
  */
  final Dio dio = Dio();

  /*
  جلب جميع المنشورات

  Future<List<PostModel>>

  يعني:
  سأعيد مستقبلاً قائمة من المنشورات
  */
  Future<List<PostModel>> getPosts() async {

    /*
    إرسال GET Request
    */
    Response response = await dio.get(
      'https://jsonplaceholder.typicode.com/posts',
    );

    /*
    response.data

    تحتوي البيانات القادمة من السيرفر

    نوعها الحالي:

    List<dynamic>
    */

    /*
    تحويل List<dynamic>

    إلى

    List<PostModel>
    */
    List<PostModel> posts =
        response.data
            .map<PostModel>(
              (json) =>
                  PostModel.fromJson(json),
            )
            .toList();

    return posts;
  }
}
Dart

الملف الثالث

screens/posts_page.dart

هذا الملف سيعرض البيانات للمستخدم.

import 'package:flutter/material.dart';

import '../models/post_model.dart';
import '../services/api_service.dart';

class PostsPage extends StatefulWidget {
  const PostsPage({super.key});

  @override
  State<PostsPage> createState() =>
      _PostsPageState();
}

class _PostsPageState
    extends State<PostsPage> {

  /*
  إنشاء ApiService
  */
  final ApiService apiService =
      ApiService();

  /*
  قائمة المنشورات
  */
  List<PostModel> posts = [];

  /*
  متغير التحميل
  */
  bool isLoading = false;

  @override
  void initState() {
    super.initState();

    getPosts();
  }

  /*
  جلب البيانات من السيرفر
  */
  Future<void> getPosts() async {

    /*
    تشغيل اللودينغ
    */
    setState(() {
      isLoading = true;
    });

    try {

      /*
      انتظار البيانات
      */
      posts =
          await apiService.getPosts();

    } catch (e) {

      print(e);

    }

    /*
    إيقاف اللودينغ
    */
    setState(() {
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(
        title: const Text(
          'Posts',
        ),
      ),

      body: isLoading

          ? const Center(
              child:
                  CircularProgressIndicator(),
            )

          : ListView.builder(

              itemCount: posts.length,

              itemBuilder:
                  (context, index) {

                PostModel post =
                    posts[index];

                return Card(

                  margin:
                      const EdgeInsets.all(
                    10,
                  ),

                  child: Padding(

                    padding:
                        const EdgeInsets.all(
                      16,
                    ),

                    child: Column(

                      crossAxisAlignment:
                          CrossAxisAlignment
                              .start,

                      children: [

                        Text(
                          post.title,
                          style:
                              const TextStyle(
                            fontSize: 18,
                            fontWeight:
                                FontWeight.bold,
                          ),
                        ),

                        const SizedBox(
                          height: 10,
                        ),

                        Text(
                          post.body,
                        ),

                        const SizedBox(
                          height: 10,
                        ),

                        Text(
                          'ID : ${post.id}',
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
    );
  }
}
Dart

الملف الرابع

main.dart

import 'package:flutter/material.dart';

import 'screens/posts_page.dart';

void main() {

  runApp(
    const MyApp(),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {

    return MaterialApp(

      debugShowCheckedModeBanner:
          false,

      home: const PostsPage(),
    );
  }
}
Dart

ماذا سيتعلم الطالب من هذا التطبيق؟

من جهة Flutter

✅ StatefulWidget

✅ initState

✅ setState

✅ ListView.builder

✅ CircularProgressIndicator

✅ Card

✅ Future

✅ async / await


من جهة Dio

✅ Dio Object

✅ GET Request

✅ Response

✅ response.data

✅ تحويل JSON


من جهة Models

✅ Model

✅ fromJson

✅ toJson

✅ List<PostModel>


نقطة تعليمية مهمة جداً

التمرين 1

اعرض فقط:

post.title
Dart

بدون body.


التمرين 2

اعرض:

post.userId
Dart

داخل Card.


التمرين 3

اطبع:

response.runtimeType
Dart

و

response.data.runtimeType
Dart

لفهم الأنواع.


التمرين 4

اجعل عدد العناصر:

posts.take(10).toList()
Dart

ثم اعرض أول 10 فقط.


التمرين 5

أضف زر Refresh واستدعِ:

getPosts()
Dart

مرة أخرى.

بعد أن يفهم الطالب هذا المشروع بالكامل ننتقل للمرحلة التالية وهي POST Request + إرسال البيانات إلى السيرفر + فهم Request Body و Headers و Content-Type بالتفصيل الاحترافي.