طلبات POST وPUT وDELETE
طلبات POST وPUT وDELETE
بينما يسترجع GET البيانات، فإن الأفعال الثلاثة التي تُعدِّل البيانات — POST وPUT وDELETE — هي ما يجعل تطبيقك مشاركاً كاملاً في واجهة برمجة تطبيقات REST. يُنشئ POST مورداً جديداً، ويُحدِّث PUT (وPATCH) الموارد الموجودة، ويحذف DELETE الموارد. لكل فعل عقد مميز مع الخادم، والحصول على التفاصيل بشكل صحيح — الترويسات الصحيحة، وجسم مُشفَّر بشكل مناسب، والتعامل الدقيق مع رمز الحالة — هو ما يُميّز التطبيقات الموثوقة عن الهشّة.
ضبط ترويسة Content-Type
كلما أرسلت جسماً إلى الخادم، يجب عليك الإعلان عن كيفية ترميز ذلك الجسم عبر ترويسة Content-Type. تتوقع واجهات برمجة التطبيقات REST بشكل شبه عالمي application/json. بدونها، تُعيد كثير من الخوادم 400 Bad Request أو تُخطئ في تحليل حمولتك بصمت. توفر مكتبة dart:convert الدالة jsonEncode() لتسلسل Map في Dart إلى سلسلة JSON.
Content-Type: application/json تخبر الخادم بترميز الجسم. الترويسة المرافقة Accept: application/json تخبر الخادم بالتنسيق الذي تريده في المقابل. ضع كلتيهما دائماً لواجهات برمجة التطبيقات التي تستخدم JSON.POST — إنشاء مورد
استخدم http.post() لإنشاء مورد جديد. يرد الخادم عادةً بـ 201 Created ويُعيد الكائن المُنشأ حديثاً (غالباً مع id يُولِّده الخادم) في جسم الاستجابة.
طلب POST — إنشاء منشور جديد
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<Map<String, dynamic>> createPost({
required String title,
required String body,
required int userId,
}) async {
final uri = Uri.parse('https://jsonplaceholder.typicode.com/posts');
final response = await http.post(
uri,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
},
body: jsonEncode({
'title': title,
'body': body,
'userId': userId,
}),
);
if (response.statusCode == 201) {
// يُعيد الخادم الكائن المُنشأ، بما في ذلك المعرّف الجديد
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
throw Exception(
'فشل POST. الحالة: ${response.statusCode} — ${response.body}',
);
}
}
PUT — استبدال مورد
استخدم http.put() لـ استبدال مورد موجود بالكامل. يستهدف URI المورد المحدد (مثل /posts/1). PUT متسق (idempotent) — استدعاؤه عدة مرات بنفس الجسم يُنتج نفس النتيجة. يرد الخادم عادةً بـ 200 OK والتمثيل المحدَّث، أو 204 No Content إذا لم يُعِد شيئاً.
طلب PUT — استبدال منشور
Future<Map<String, dynamic>> updatePost({
required int postId,
required String title,
required String body,
required int userId,
}) async {
final uri = Uri.parse(
'https://jsonplaceholder.typicode.com/posts/$postId',
);
final response = await http.put(
uri,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json',
},
body: jsonEncode({
'id': postId,
'title': title,
'body': body,
'userId': userId,
}),
);
if (response.statusCode == 200) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else if (response.statusCode == 404) {
throw Exception('المنشور $postId غير موجود.');
} else {
throw Exception(
'فشل PUT. الحالة: ${response.statusCode}',
);
}
}
DELETE — حذف مورد
استخدم http.delete() لحذف مورد. طلبات DELETE عادةً لا تحتوي على جسم. يرد الخادم بـ 200 OK (مع حمولة تأكيد)، أو 204 No Content (نجاح بدون جسم)، أو 404 Not Found إذا لم يكن المورد موجوداً.
onPressed الخاص بزر بدون حارس.طلب DELETE — حذف منشور
Future<void> deletePost(int postId) async {
final uri = Uri.parse(
'https://jsonplaceholder.typicode.com/posts/$postId',
);
final response = await http.delete(
uri,
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
},
);
// 200 أو 204 كلاهما يشيران إلى النجاح
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception(
'فشل DELETE. الحالة: ${response.statusCode}',
);
}
// عند النجاح، لا يُعاد شيء — المورد لم يعد موجوداً
}
الجمع معاً — CRUD في ودجت
في ودجت Flutter حقيقي، تُغلِّف كل استدعاء API في try/catch، وتُدير علامة تحميل بـ setState، وتعرض الأخطاء للمستخدم. إليك نمطاً مختصراً لزر "حفظ" يستدعي POST أو PUT بناءً على ما إذا كنت تُنشئ أو تُعدِّل:
زر الحفظ — POST أو PUT بناءً على وضع التحرير
class PostFormState extends State<PostForm> {
bool _isSaving = false;
Future<void> _save() async {
setState(() => _isSaving = true);
try {
if (widget.postId == null) {
// إنشاء منشور جديد
await createPost(
title: _titleController.text,
body: _bodyController.text,
userId: 1,
);
} else {
// استبدال منشور موجود
await updatePost(
postId: widget.postId!,
title: _titleController.text,
body: _bodyController.text,
userId: 1,
);
}
if (mounted) Navigator.pop(context, true); // إعادة النجاح
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('خطأ: $e')),
);
}
} finally {
if (mounted) setState(() => _isSaving = false);
}
}
}
ملخص
في هذا الدرس تعلمت كيفية إرسال طلبات HTTP التعديلية من Flutter. يُنشئ POST الموارد ويتوقع استجابة 201؛ يستبدل PUT المورد بالكامل ويتوقع 200 أو 204؛ يحذف DELETE المورد ويتوقع أيضاً 200 أو 204. كل الطلبات التي تحمل جسماً تتطلب ترويسة Content-Type: application/json وجسماً مُسلسَلاً بـ jsonEncode(). غلِّف كل استدعاء في try/catch، وأدِر حالة التحميل بـ setState، وتحقق دائماً من رمز الحالة قبل التصرف بناءً على الاستجابة. في الدرس التالي ستتعلم كيفية التعامل مع رموز المصادقة وتدفقات التحديث.