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 جديد، بل تحتاج فقط معرف العنصر المطلوب حذفه.