تحديث البوست

Create  → POST
Update  → PUT
Dart

قبل أي كود يجب أن نفهم ماذا يعني Update فعلياً.

ماذا يعني تعديل Post؟

لدينا منشور موجود مسبقاً:

{
  "id": 5,
  "title": "Flutter",
  "body": "Old Content"
}
Dart

المستخدم ضغط:

Edit
Dart

ثم غيّر العنوان إلى:

Advanced Flutter
Dart

والنص إلى:

Learning Dio PUT Request
Dart

نريد إرسال هذه البيانات الجديدة للسيرفر.

ماذا يحدث فعلياً؟

في POST كنا نقول:

أنشئ منشوراً جديداً
Dart

أما في PUT فنقول:

يوجد منشور رقمه 5

استبدل بياناته بهذه البيانات الجديدة
Dart
لماذا نحتاج ID؟

السيرفر لديه آلاف المنشورات.

كيف يعرف أي منشور يجب تعديله؟

لهذا نرسل:

/posts/5
Dart

الرقم 5 هو هوية المنشور.

أول مفهوم جديد
Path Parameter

في GET كنا نستخدم:

/posts
Dart

أما الآن:

/posts/5
Dart

ماذا يسمى الرقم 5؟

يسمى:

Path Parameter
Dart

أو

Resource Identifier
Dart

الشكل العام لـ PUT

PUT /posts/5
Dart

Body:

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

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

حتى نستفيد من:

toJson()
Dart

التي أضفناها سابقاً.


تعديل ApiService

سنضيف دالة جديدة.

Future<bool> updatePost(
  PostModel post,
) async {

}
Dart

لماذا نستقبل PostModel؟

لأن المنشور كله موجود داخل الكائن.


يمكننا الوصول إلى:

post.id
Dart
post.title
Dart
post.body
Dart

إرسال PUT

Response response =
    await dio.put(
Dart

لماذا put ؟

لأن Dio يدعم:

dio.get()
dio.post()
dio.put()
dio.delete()
Dart

وكل واحدة تمثل HTTP Method.


الرابط

'https://jsonplaceholder.typicode.com/posts/${post.id}'
Dart

إذا كان:

post.id = 5
Dart

سيصبح:

https://jsonplaceholder.typicode.com/posts/5
Dart

إرسال البيانات

data: post.toJson(),
Dart

هنا تظهر أهمية:

toJson()
Dart

الدالة كاملة

Future<bool> updatePost(
  PostModel post,
) async {

  Response response =
      await dio.put(

    'https://jsonplaceholder.typicode.com/posts/${post.id}',

    data: post.toJson(),
  );

  return response.statusCode == 200;
}
Dart

لماذا 200 وليس 201 ؟

في POST:

201 Created
Dart

لأننا أنشأنا مورداً جديداً.


في PUT:

200 OK
Dart

لأن المورد موجود مسبقاً وتم تحديثه.


إنشاء صفحة Edit

سننشئ:

edit_post_page.dart
Dart

أهم نقطة

الصفحة يجب أن تستقبل Post موجود مسبقاً.


مثال:

EditPostPage(
  post: post,
)
Dart

Constructor

class EditPostPage
    extends StatefulWidget {

  final PostModel post;

  const EditPostPage({
    super.key,
    required this.post,
  });

}
Dart

لماذا؟

حتى نستطيع تعبئة الحقول الحالية.


مثال

المنشور الحالي:

title = Flutter
Dart

عند فتح الصفحة نريد أن يرى:

Flutter
Dart

وليس TextField فارغ.


تعبئة Controllers

داخل initState

@override
void initState() {

  super.initState();

  titleController.text =
      widget.post.title;

  bodyController.text =
      widget.post.body;
}
Dart

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

إذا كان:

widget.post.title
Dart

يساوي:

Flutter
Dart

فسيظهر مباشرة داخل TextField.


إنشاء نسخة جديدة من المنشور

عند الضغط على Save

لا نرسل النصوص مباشرة.

ننشيء PostModel جديد.


PostModel updatedPost =
    PostModel(

  userId:
      widget.post.userId,

  id:
      widget.post.id,

  title:
      titleController.text,

  body:
      bodyController.text,
);
Dart

ماذا حدث؟

احتفظنا بنفس:

id
Dart

لأن المنشور نفسه.


وغيّرنا:

title
body
Dart

فقط.


استدعاء API

bool success =
    await apiService
        .updatePost(
          updatedPost,
        );
Dart

إذا نجحت العملية

if(success){
Dart

رسالة نجاح

ScaffoldMessenger.of(context)
.showSnackBar(

  const SnackBar(

    content: Text(
      'Post Updated Successfully',
    ),

  ),
);
Dart

العودة للشاشة السابقة

Navigator.pop(context);
Dart

ماذا تعلم الطالب هنا؟

من HTTP

✅ PUT Request

✅ Path Parameter

✅ Resource ID

✅ الفرق بين POST و PUT


من Dio

✅ dio.put()

✅ إرسال Model باستخدام toJson()

✅ فحص statusCode


من Flutter

✅ تمرير Object بين الصفحات

✅ تعبئة TextField بقيم موجودة

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

✅ حفظ التغييرات


الفرق النهائي الذي يجب أن يرسخ في ذهن الطالب

POST

لا يوجد منشور

↓
إنشاء منشور جديد

↓
POST
Dart

PUT

يوجد منشور مسبقاً

↓
تعديل بياناته

↓
PUT
Dart

والأهم أن هذه أول عملية في المشروع تستخدم فعلياً:

post.toJson()
Dart

بشكل منطقي واحترافي، لذلك سيبدأ الطالب الآن بفهم القيمة الحقيقية للـ Models بدلاً من التعامل مع Map مباشرة.

سنحتاج لتعديل 3 ملفات فقط:

lib/
│
├── services/
│   └── api_service.dart
│
├── screens/
│   ├── posts_page.dart
│   └── edit_post_page.dart
│
└── models/
    └── post_model.dart
Dart

ملف post_model.dart لن يتغير إذا كان يحتوي على fromJson و toJson بالفعل.


1- api_service.dart

import 'package:dio/dio.dart';

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

class ApiService {

/*
Dio Object

مسؤول عن جميع الاتصالات
مع السيرفر.
*/
final Dio dio = Dio();

# /*

# GET POSTS

جلب جميع المنشورات من السيرفر.
*/
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;
```

}

# /*

# UPDATE POST

تستقبل PostModel كامل.

لماذا؟

لأننا نريد الاستفادة
من toJson().

بدلاً من تمرير title
و body بشكل منفصل.
*/
Future<bool> updatePost(
PostModel post,
) async {

```
/*
PUT Request

نرسل id داخل الرابط.

مثال:

/posts/5
*/

Response response =
    await dio.put(

  'https://jsonplaceholder.typicode.com/posts/${post.id}',

  /*
  تحويل Model إلى Map

  ثم Dio يحولها إلى JSON
  ويرسلها للسيرفر.
  */
  data: post.toJson(),
);

/*
إذا كانت العملية ناجحة
يعيد السيرفر 200.
*/
return response.statusCode == 200;
```

}
}
Dart

2- edit_post_page.dart

import 'package:flutter/material.dart';

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

class EditPostPage extends StatefulWidget {

/*
المنشور المراد تعديله.

سيتم تمريره من الشاشة السابقة.
*/
final PostModel post;

const EditPostPage({
super.key,
required this.post,
});

@override
State<EditPostPage> createState() =>
_EditPostPageState();
}

class _EditPostPageState
extends State<EditPostPage> {

/*
Controller للعنوان.
*/
final TextEditingController
titleController =
TextEditingController();

/*
Controller للمحتوى.
*/
final TextEditingController
bodyController =
TextEditingController();

/*
Api Service
*/
final ApiService apiService =
ApiService();

/*
لعرض اللودينغ.
*/
bool isLoading = false;

@override
void initState() {

```
super.initState();

/*
تعبئة الحقول بالقيم الحالية.

إذا كان المنشور:

Flutter
Learning Dio

ستظهر هذه القيم
مباشرة داخل TextFields.
*/

titleController.text =
    widget.post.title;

bodyController.text =
    widget.post.body;
```

}

# /*

# UPDATE POST

*/
Future<void> updatePost() async {

```
setState(() {
  isLoading = true;
});

/*
إنشاء نسخة جديدة
من المنشور.

نحافظ على:

id
userId

ونأخذ القيم الجديدة
من TextFields.
*/

PostModel updatedPost =
    PostModel(

  userId:
      widget.post.userId,

  id:
      widget.post.id,

  title:
      titleController.text,

  body:
      bodyController.text,
);

/*
إرسال البيانات للسيرفر.
*/

bool success = await apiService.updatePost(updatedPost);

setState(() {
  isLoading = false;
});

if (success) {

  /*
  رسالة نجاح.
  */

  ScaffoldMessenger.of(
    context,
  ).showSnackBar(

    const SnackBar(
      content: Text(
        'Post Updated Successfully',
      ),
    ),
  );

  /*
  العودة للشاشة السابقة.
  */

  Navigator.pop(
    context,
  );

} else {

  /*
  رسالة فشل.
  */

  ScaffoldMessenger.of(
    context,
  ).showSnackBar(

    const SnackBar(
      content: Text(
        'Failed To Update Post',
      ),
    ),
  );
}
```

}

@override
Widget build(
BuildContext context,
) {

```
return Scaffold(

  appBar: AppBar(
    title: const Text(
      'Edit Post',
    ),
  ),

  body: Padding(

    padding:
        const EdgeInsets.all(
      16,
    ),

    child: Column(

      children: [

        /*
        Title Field
        */

        TextField(

          controller:
              titleController,

          decoration:
              const InputDecoration(

            labelText:
                'Title',

            border:
                OutlineInputBorder(),
          ),
        ),

        const SizedBox(
          height: 16,
        ),

        /*
        Body Field
        */

        TextField(

          controller: bodyController,

          maxLines: 5,

          decoration: const InputDecoration(

            labelText:
                'Body',

            border:
                OutlineInputBorder(),
          ),
        ),

        const SizedBox(
          height: 20,
        ),

        SizedBox(

          width:
              double.infinity,

          child:
              ElevatedButton(

            onPressed:
                isLoading
                    ? null
                    : updatePost,

            child: isLoading

                ? const CircularProgressIndicator()

                : const Text(
                    'Update Post',
                  ),
          ),
        ),
      ],
    ),
  ),
);
```

}
}
Dart

3- تعديل posts_page.dart

داخل Card الخاصة بالمنشور أضف زر تعديل.

ابحث عن:

return Card(
Dart

وأضف داخل الـ Column:

ElevatedButton(

  onPressed: () {

    Navigator.push(

      context,

      MaterialPageRoute(

        builder: (_) =>
            EditPostPage(
              post: post,
            ),
      ),
    );
  },

  child: const Text(
    'Edit',
  ),
),
Dart

ولا تنس إضافة:

import 'edit_post_page.dart';
Dart

في أعلى الملف.


ماذا تعلم الطالب هنا؟

Flutter

✅ تمرير Object بين الصفحات

EditPostPage(
  post: post,
)
Dart

✅ تعبئة TextField بقيم جاهزة

titleController.text =
    widget.post.title;
Dart

✅ قراءة التعديلات من المستخدم

titleController.text
Dart

Dio

✅ PUT Request

dio.put(...)
Dart

✅ إرسال Model

post.toJson()
Dart

✅ Path Parameters

/posts/${post.id}
Dart

API

الطالب يرى الآن دورة كاملة:

GET
 ↓
عرض البيانات

 ↓

Edit

 ↓

PUT

 ↓

تحديث البيانات
Dart

وبذلك يصبح جاهزاً تماماً للمرحلة التالية وهي DELETE Request حيث سيفهم دورة CRUD كاملة (Create – Read – Update – Delete).