Create → POST
Update → PUTDartقبل أي كود يجب أن نفهم ماذا يعني Update فعلياً.
ماذا يعني تعديل Post؟
لدينا منشور موجود مسبقاً:
{
"id": 5,
"title": "Flutter",
"body": "Old Content"
}Dartالمستخدم ضغط:
EditDartثم غيّر العنوان إلى:
Advanced FlutterDartوالنص إلى:
Learning Dio PUT RequestDartنريد إرسال هذه البيانات الجديدة للسيرفر.
ماذا يحدث فعلياً؟
في POST كنا نقول:
أنشئ منشوراً جديداًDartأما في PUT فنقول:
يوجد منشور رقمه 5
استبدل بياناته بهذه البيانات الجديدةDartلماذا نحتاج ID؟
السيرفر لديه آلاف المنشورات.
كيف يعرف أي منشور يجب تعديله؟
لهذا نرسل:
/posts/5Dartالرقم 5 هو هوية المنشور.
أول مفهوم جديد
Path Parameter
في GET كنا نستخدم:
/postsDartأما الآن:
/posts/5Dartماذا يسمى الرقم 5؟
يسمى:
Path ParameterDartأو
Resource IdentifierDartالشكل العام لـ PUT
PUT /posts/5DartBody:
{
"title": "Advanced Flutter",
"body": "Learning Dio PUT",
"userId": 1
}Dartلماذا سنستخدم Model؟
حتى نستفيد من:
toJson()Dartالتي أضفناها سابقاً.
تعديل ApiService
سنضيف دالة جديدة.
Future<bool> updatePost(
PostModel post,
) async {
}Dartلماذا نستقبل PostModel؟
لأن المنشور كله موجود داخل الكائن.
يمكننا الوصول إلى:
post.idDartpost.titleDartpost.bodyDartإرسال 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 = 5Dartسيصبح:
https://jsonplaceholder.typicode.com/posts/5Dartإرسال البيانات
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 CreatedDartلأننا أنشأنا مورداً جديداً.
في PUT:
200 OKDartلأن المورد موجود مسبقاً وتم تحديثه.
إنشاء صفحة Edit
سننشئ:
edit_post_page.dartDartأهم نقطة
الصفحة يجب أن تستقبل Post موجود مسبقاً.
مثال:
EditPostPage(
post: post,
)DartConstructor
class EditPostPage
extends StatefulWidget {
final PostModel post;
const EditPostPage({
super.key,
required this.post,
});
}Dartلماذا؟
حتى نستطيع تعبئة الحقول الحالية.
مثال
المنشور الحالي:
title = FlutterDartعند فتح الصفحة نريد أن يرى:
FlutterDartوليس TextField فارغ.
تعبئة Controllers
داخل initState
@override
void initState() {
super.initState();
titleController.text =
widget.post.title;
bodyController.text =
widget.post.body;
}Dartماذا يحدث هنا؟
إذا كان:
widget.post.titleDartيساوي:
FlutterDartفسيظهر مباشرة داخل TextField.
إنشاء نسخة جديدة من المنشور
عند الضغط على Save
لا نرسل النصوص مباشرة.
ننشيء PostModel جديد.
PostModel updatedPost =
PostModel(
userId:
widget.post.userId,
id:
widget.post.id,
title:
titleController.text,
body:
bodyController.text,
);Dartماذا حدث؟
احتفظنا بنفس:
idDartلأن المنشور نفسه.
وغيّرنا:
title
bodyDartفقط.
استدعاء 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
لا يوجد منشور
↓
إنشاء منشور جديد
↓
POSTDartPUT
يوجد منشور مسبقاً
↓
تعديل بياناته
↓
PUTDartوالأهم أن هذه أول عملية في المشروع تستخدم فعلياً:
post.toJson()Dartبشكل منطقي واحترافي، لذلك سيبدأ الطالب الآن بفهم القيمة الحقيقية للـ Models بدلاً من التعامل مع Map مباشرة.
سنحتاج لتعديل 3 ملفات فقط:
lib/
│
├── services/
│ └── api_service.dart
│
├── screens/
│ ├── posts_page.dart
│ └── edit_post_page.dart
│
└── models/
└── post_model.dartDartملف 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;
```
}
}
Dart2- 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',
),
),
),
],
),
),
);
```
}
}
Dart3- تعديل 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.textDartDio
✅ PUT Request
dio.put(...)Dart✅ إرسال Model
post.toJson()Dart✅ Path Parameters
/posts/${post.id}DartAPI
الطالب يرى الآن دورة كاملة:
GET
↓
عرض البيانات
↓
Edit
↓
PUT
↓
تحديث البياناتDartوبذلك يصبح جاهزاً تماماً للمرحلة التالية وهي DELETE Request حيث سيفهم دورة CRUD كاملة (Create – Read – Update – Delete).