Delete Post

ما هو DELETE فعلياً؟

في POST كنا نقول:

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

في PUT كنا نقول:

عدّل المنشور الموجود
Dart

أما DELETE فنقول:

احذف المنشور رقم 5
Dart

ماذا نرسل للسيرفر؟

لاحظ أننا في DELETE غالباً لا نرسل Body.

في POST:

{
  "title":"Flutter",
  "body":"Learning Dio"
}
Dart

في PUT:

{
  "title":"New Title"
}
Dart

أما DELETE:

DELETE /posts/5
Dart

فقط.


لماذا لا نرسل Body؟

لأن السيرفر لا يحتاج بيانات جديدة.

هو يحتاج فقط معرفة:

من هو العنصر المطلوب حذفه؟
Dart

وهذه المعلومة موجودة في:

/posts/5
Dart

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

كما في PUT:

Path Parameter
Dart

أو:

Resource ID
Dart

الشكل الحقيقي

Flutter
    │
    ▼
DELETE /posts/5
    │
    ▼
Server
    │
    ▼
Post Deleted
    │
    ▼
200 OK
Dart

إضافة دالة الحذف داخل ApiService

سنضيف:

Future<bool> deletePost(
  int postId,
) async {

}
Dart

لماذا int فقط؟

لأننا لا نحتاج:

PostModel
Dart

كامل.

نحتاج فقط:

id
Dart

تنفيذ DELETE

Response response =
    await dio.delete(
Dart

الرابط

'https://jsonplaceholder.typicode.com/posts/$postId'
Dart

إذا كان:

postId = 5
Dart

يصبح:

/posts/5
Dart

الكود

Future<bool> deletePost(
  int postId,
) async {

  Response response =
      await dio.delete(

    'https://jsonplaceholder.typicode.com/posts/$postId',
  );

  return response.statusCode == 200;
}
Dart

لماذا 200 ؟

لأن العملية نجحت.

بعض APIs تعيد:

204 No Content
Dart

أي:

تم الحذف
ولا يوجد محتوى راجع
Dart

لكن jsonplaceholder يعيد 200.


كيف ننفذ الحذف من الشاشة؟

الطالب يجب أن يرى شيئاً مهماً:

قبل الحذف:

Post 1
Post 2
Post 3
Dart

بعد حذف Post 2:

Post 1
Post 3
Dart

بدون إعادة تشغيل التطبيق.


الزر

داخل Card أضف:

IconButton(
  onPressed: () {
    deletePost(post);
  },
  icon: const Icon(
    Icons.delete,
  ),
)
Dart

لماذا لا نحذف مباشرة؟

خطأ شائع جداً.

المستخدم قد يضغط بالخطأ.

لذلك نضيف:

Confirmation Dialog
Dart

إظهار Dialog

showDialog(
  context: context,
  builder: (_) {
    return AlertDialog(
      title: const Text(
        'Delete Post',
      ),
      content: const Text(
        'Are you sure?',
      ),
    );
  },
);
Dart

لماذا؟

أفضل ممارسة UX.


زر نعم

TextButton(
  onPressed: () {
    Navigator.pop(context);

    deletePost(post);
  },
  child: const Text('Yes'),
)
Dart

زر إلغاء

TextButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: const Text('Cancel'),
)
Dart

تنفيذ الحذف

Future<void> deletePost(
  PostModel post,
) async {

}
Dart

استدعاء API

bool success =
    await apiService.deletePost(
      post.id,
    );
Dart

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

if(success){
Dart

حذف العنصر من القائمة المحلية

هذه أهم نقطة تعليمية.

كثير من المبتدئين يفعل:

await getPosts();
Dart

من جديد.


لكن نحن سنفعل:

posts.removeWhere(
  (element) =>
      element.id == post.id,
);
Dart

ماذا تفعل removeWhere ؟

لنفترض:

[
  id=1,
  id=2,
  id=3,
]
Dart

نحذف:

id=2
Dart

تصبح:

[
  id=1,
  id=3,
]
Dart

تحديث الشاشة

setState(() {});
Dart

رسالة نجاح

ScaffoldMessenger.of(
  context,
).showSnackBar(

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

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

HTTP

✅ DELETE Method

✅ Path Parameter

✅ Resource ID


Dio

✅ dio.delete()

✅ Response بعد الحذف


Flutter

✅ AlertDialog

✅ Confirmation

✅ removeWhere

✅ تحديث القائمة محلياً


نقطة احترافية مهمة جداً

بعد POST تعلمنا:

Model → toJson → Server
Dart

بعد PUT تعلمنا:

Model → toJson → Update
Dart

أما DELETE فهو أول Request لا يحتاج:

toJson()
Dart

ولا يحتاج:

fromJson()
Dart

لأننا لا نرسل بيانات ولا نستقبل بيانات.

بل نرسل:

ID فقط
Dart

وهذه نقطة ممتازة لشرح أن كل HTTP Method لها احتياجات مختلفة:

GET
↓
استقبال بيانات
↓
fromJson()

POST
↓
إرسال بيانات
↓
toJson()

PUT
↓
تعديل بيانات
↓
toJson()

DELETE
↓
إرسال ID فقط
↓
لا يحتاج Model
Dart

الكود كامل:

سنعدل ملفين فقط:

api_service.dart

posts_page.dart
Dart

أولاً: api_service.dart

سنضيف دالة الحذف فقط.

/*
==================================================
DELETE POST
==================================================

تستقبل:

postId

لماذا؟

لأن السيرفر يحتاج معرفة
أي منشور سيتم حذفه.

لا نحتاج إرسال PostModel كامل.

DELETE Request غالباً
لا يحتوي Body.

بل يعتمد على ID الموجود
داخل الرابط.
*/
Future<bool> deletePost(int postId) async {

  /*
  إرسال DELETE Request

  إذا كان postId = 5

  سيصبح الرابط:

  /posts/5
  */

  Response response =
      await dio.delete(

    'https://jsonplaceholder.typicode.com/posts/$postId',
  );

  /*
  إذا نجحت العملية
  يعيد السيرفر 200.

  بعض APIs تعيد 204.

  لكن jsonplaceholder
  تعيد 200.
  */

  return response.statusCode == 200;
}
Dart

ثانياً: posts_page.dart

إضافة Dialog تأكيد الحذف

داخل _PostsPageState

أضف:

/*
==================================================
SHOW DELETE DIALOG
==================================================

قبل الحذف نعرض نافذة تأكيد.

لماذا؟

لأن المستخدم قد يضغط
زر الحذف بالخطأ.

وهذه تعتبر Best Practice.
*/
Future<void> showDeleteDialog(PostModel post) async {

  showDialog(

    context: context,

    builder: (_) {

      return AlertDialog(

        title: const Text(
          'Delete Post',
        ),

        content: const Text(
          'Are you sure you want to delete this post?',
        ),

        actions: [

          /*
          إلغاء العملية
          */

          TextButton(

            onPressed: () {

              Navigator.pop(
                context,
              );
            },

            child: const Text(
              'Cancel',
            ),
          ),

          /*
          تأكيد الحذف
          */

          TextButton(

            onPressed: () {

              Navigator.pop(
                context,
              );

              deletePost(
                post,
              );
            },

            child: const Text(
              'Delete',
            ),
          ),
        ],
      );
    },
  );
}
Dart

إضافة دالة الحذف

داخل _PostsPageState

أضف:

/*
==================================================
DELETE POST
==================================================

تنفذ عملية الحذف
من السيرفر.

ثم تحذف العنصر
من القائمة المحلية.
*/
Future<void> deletePost(PostModel post) async {

  bool success =
      await apiService.deletePost(
        post.id,
      );

  if (success) {

    /*
    حذف العنصر محلياً.

    لماذا؟

    حتى يتم تحديث الواجهة
    مباشرة دون إعادة تحميل
    جميع المنشورات.
    */

    posts.removeWhere(

      (element) {

        return element.id ==
            post.id;
      },
    );

    /*
    إعادة بناء الواجهة.
    */

    setState(() {});

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

    ScaffoldMessenger.of(
      context,
    ).showSnackBar(

      const SnackBar(

        content: Text(
          'Post Deleted Successfully',
        ),
      ),
    );

  } else {

    ScaffoldMessenger.of(
      context,
    ).showSnackBar(

      const SnackBar(

        content: Text(
          'Failed To Delete Post',
        ),
      ),
    );
  }
}
Dart

إضافة زر الحذف داخل Card

ابحث عن:

return Card(
Dart

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

const SizedBox(
  height: 10,
),
Dart

ثم:

Row(

  children: [

    /*
    زر التعديل
    */

    ElevatedButton(

      onPressed: () {

        Navigator.push(

          context,

          MaterialPageRoute(

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

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

    const SizedBox(
      width: 10,
    ),

    /*
    زر الحذف
    */

    ElevatedButton(

      onPressed: () {

        showDeleteDialog(
          post,
        );
      },

      child: const Text(
        'Delete',
      ),
    ),
  ],
),
Dart

ماذا تعلم الطالب من ميزة الحذف؟

HTTP

DELETE Method
Dart

Dio

dio.delete(...)
Dart

Flutter

AlertDialog
Dart

showDialog(...)
Dart

removeWhere(...)
Dart

Best Practice

❌ لا تحذف مباشرة عند الضغط.

onPressed: deletePost
Dart

هذا غير احترافي.


✅ اعرض Dialog أولاً.

showDeleteDialog()
Dart

ثم:

deletePost()
Dart

أفضل نقطة تعليمية هنا

لاحظ الفرق بين العمليات الأربع:

GET
↓
fromJson()

POST
↓
toJson()

PUT
↓
toJson()

DELETE
↓
ID فقط
Dart

ولهذا تعتبر DELETE أبسط HTTP Method ضمن CRUD لأنها لا تحتاج Body ولا Model جديد، بل تحتاج فقط معرف العنصر المطلوب حذفه.