تحديث البوست

CreatePOST
UpdatePUT
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).