1) ما هو showDialog وماذا يرجع؟
showDialog<T> هو دالة تعرض حوار (modal) فوق واجهة التطبيق، مع حاجز (modal barrier) يمنع التفاعل مع المحتوى الذي تحته.
عندما يُغلَق الحوار، تُكمِل الـ Future التي ترجعها الدالة بقيمة (أو null إذا أغلق المستخدم الحوار بدون إرجاع قيمة).
يمكنك استخدامه لعرض AlertDialog, SimpleDialog, Dialog أو أي ويدجت مخصّص.
ما هو showDialog؟
- هو دالة في Flutter تُستخدم لعرض مربع حوار (Dialog) فوق واجهة التطبيق.
- الهدف منه: إيقاف تفاعل المستخدم مؤقتًا مع بقية الواجهة وإجباره على التعامل مع الرسالة أو الاختيار المعروض.
- مثل: تنبيه ⚠️، رسالة تأكيد ✅، إدخال بيانات ✍️، أو خيارات متعددة.
📌 أين يُستخدم؟
- رسائل التنبيه (Alert)
مثل: “هل أنت متأكد أنك تريد الحذف؟”- هذا أكثر استخدام شائع.
- رسائل التأكيد/الرفض (Confirm)
عندما تريد من المستخدم أن يختار “نعم / لا” أو “موافق / إلغاء”. - إظهار خطأ أو معلومة مهمة
مثل: “لا يوجد اتصال بالإنترنت”. - إدخال بيانات بسيطة
مثل: إدخال كلمة مرور أو تعليق قصير داخل مربع حوار. - Custom Dialog
أي تصميم خاص بك (نموذج صغير أو Widget معين داخل نافذة).
2) الاستخدام الأساسي — مثال تأكيد (confirm)
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('حذف العنصر'),
content: Text('هل أنت متأكد أنك تريد حذف هذا العنصر؟'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('إلغاء'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('حذف'),
),
],
),
);
if (confirmed == true) {
// تنفّذ حذف
}PHPنقطة مهمة: القيمة المرجعة قد تكون null إذا أغلق المستخدم الحوار عن طريق الضغط خارج الحوار أو زر الرجوع (إذا سمحت الإعدادات بذلك). Flutter API Docs
3) تحكّم في إغلاق الحوار (الخارج / زر الرجوع)
- لإيقاف إغلاق الحوار عند النقر خارج المنطقة، استعمل
barrierDismissible: false. - لإيقاف إغلاق الحوار عند زر الرجوع (Back)، غلاف محتوى الحوار بـ
WillPopScopeأو (في نسخ Flutter الأحدث) استخدمPopScope/الآلية المناسبة، وارجعFuture.value(false)منonWillPop. هذا يمنعpop. مثال:
showDialog(
context: context,
barrierDismissible: false, // يمنع النقر بالخارج
builder: (context) {
return WillPopScope(
onWillPop: () async => false, // يمنع زر الرجوع
child: AlertDialog(
title: Text('اشعار مهم'),
content: Text('لا يمكنك إغلاق هذا الآن'),
),
);
},
);PHPمعلومة من المصدر: barrierDismissible يتحكّم بسلوك النقر على الحاجز، وWillPopScope يسجل callback لرفض محاولات الـ pop. Flutter API Docs+1
4) إدارة الحالة داخل الحوار (stateful dialog)
إذا احتجت واجهة حوار يتغيّر محتواها داخليًا (عداد، اختيارات، فورم، …)، عندك خياران شائعان:
- استخدام
StatefulBuilderلعملsetStateمحلي خاص بالحوار: بسيط وسريع. Flutter API Docs
مثال مختصر:
showDialog(
context: context,
builder: (context) {
int counter = 0;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('العداد: $counter'),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text('زيادة'),
),
],
),
);
},
);
},
);PHP- بناء
StatefulWidgetمخصّص كـ dialog content — أنظف للكود المعقّد أو الفورمات الكبيرة (معGlobalKey<FormState>إلخ).
5) حركات/انتقالات مخصّصة (Animations)
إذا أردت رسوم دخول/خروج خاصّة أو تحكم كامل في الـ transition، استخدم showGeneralDialog (يعطيك pageBuilder وtransitionBuilder). يمكنك تطبيق ScaleTransition, FadeTransition, SlideTransition، إلخ. مثال مبسّط:
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'إغلاق',
barrierColor: Colors.black54,
transitionDuration: Duration(milliseconds: 300),
pageBuilder: (ctx, anim1, anim2) => Align(
alignment: Alignment.center,
child: Material(
child: Container(padding: EdgeInsets.all(20), child: Text('محتوى')),
),
),
transitionBuilder: (ctx, a1, a2, child) {
return FadeTransition(
opacity: a1,
child: ScaleTransition(scale: a1, child: child),
);
},
);PHPملاحظات: showGeneralDialog لديه بعض اختلافات افتراضية (مثلاً barrierDismissible قد يختلف) ويفترض عليك نقطة تسميات الحاجز لأغراض الوصول عندما يكون barrierDismissible true. Flutter API Docs
6) خواص مهمة في showDialog (نقاط عملية)
useSafeArea: افتراضيًاtrue— يمنع الحوار من التداخل مع مناطق النظام (notches, status bar). Flutter API DocsuseRootNavigator: افتراضيًاtrue— يدفع الحوار إلى الـ rootNavigator، مهم عند وجودNavigatorداخل جزء من التطبيق (مثل nested navigators). Flutter API DocsbarrierColor: إن لم تحدده، يستخدم إعدادات الـDialogThemeأو القيمة الافتراضية (Colors.black54). Flutter API Docs- عند إغلاق الحوار من مكان آخر أو عند وجود navigators متعددة: استخدم
Navigator.of(context, rootNavigator: true).pop(result)وفق الحاجة. Flutter API Docs
7) متى لا تستعمل showDialog — أخطاء وملاحظات عملية
- لا تضع
showDialogمباشرة فيinitState()لأن الـBuildContextقد لا يكون جاهزًا تمامًا؛ بدلاً من ذلك استعملWidgetsBinding.instance.addPostFrameCallback(...)لعرضه بعد أول إطار. هذا حل شائع لعرض حوار عند بداية الشاشة. flutteris.com+1 - تجنّب استخدام
showDialogكـ “loading indicator” طويل الأمد؛ إن أردت مؤشر تحميل استخدم overlay أو إدارة حالة تظهر/تخفي ويدجت داخل الشجرة بدلًا من فتح dialog كامل (لكون الـ dialog قد يؤثر على سلوك الـ Navigator). - انتبه لرسائل الخطأ عند محاولة دفع route أثناء عملية build — هذا ما يسبب مشاكل عند النداء داخل
build/didChangeDependencies.
8) أمثلة سريعة مفيدة (قصيرة وسهلة النسخ)
حوار تأكيد عادي (راجعنا) — موجود أعلاه.
حوار تحميل غير قابل للإغلاق:
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => WillPopScope(
onWillPop: () async => false,
child: Center(child: CircularProgressIndicator()),
),
);PHPحوار مخصّص بانتقال منزلق من الأسفل (باستخدام showGeneralDialog):
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'close',
barrierColor: Colors.black54,
transitionDuration: Duration(milliseconds: 250),
pageBuilder: (context, a1, a2) => SizedBox.shrink(),
transitionBuilder: (context, a1, a2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset.zero).animate(a1),
child: Align(
alignment: Alignment.bottomCenter,
child: Material(
child: Container(height: 300, color: Colors.white, child: Text('من الأسفل')),
),
),
);
},
);PHP9) أفضل الممارسات (مختصر)
- استخدم النوع العام
showDialog<T>لتمرر وتستقبل قيمة منطقية/كائنية من الحوار. Flutter API Docs - إذا كان محتوى الحوار معقّدًا، سمّه (انقل من builder إلى ويدجت منفصل) لتحسين الاختبار وإعادة الاستخدام.
- احذر من استخدام
contextعبر فواصلasyncبدون التحقق منmounted. - راعِ الوصول (accessibility): ضع
barrierLabelإذا كان الحاجز قابلًا للإغلاق (important for screen readers). Flutter API Docs
خاتمة
هذا يغطي معظم النقاط العملية والنظرية حول showDialog وطرق تخصيصه وإدارته في Flutter — من الاستخدام البسيط (AlertDialog) إلى الحوارات المخصّصة المتحركة (showGeneralDialog)، وكيفية إدارة الحالة داخل الحوار ومنع الإغلاق غير المرغوب فيه. إذا تحب، أرسل لي سيناريو محدد (مثلاً: “أريد حواراً يحتوي على فورم تحقق من رقم الهاتف مع تحميل داخلي”) وسأعطيك كوداً كاملاً ومختبراً يناسبه فوراً. 🚀
showDialog: دالة (function) في Flutter تُستخدم لعرض حوار (modal) فوق واجهة التطبيق. لا يعرضshowDialogمظهر الحوار بنفسه — هو فقط يفتح ما يبنيهbuilder(مثلAlertDialog,SimpleDialogأوDialog). عند إغلاق الحوار، تُرجعshowDialogقيمة عبرFuture(يمكنك الحصول عليها عبرawait). Flutter API DocsAlertDialog: ويدجت (widget) جاهز للعرض داخلshowDialog، مخصّص لرسائل بسيطة أو تأكيدات (عنوان، محتوى، قائمة أزرار/إجراءات). عادةً ما تضعAlertDialogداخلbuilderالخاص بـshowDialog. Flutter API Docs
أمثلة صغيرة لفهم الفكرة سريعاً
كود بسيط يعرض AlertDialog بواسطة showDialog ثم يستقبل نتيجة (true/false):
final bool? result = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text('تأكيد'),
content: Text('هل تريد الاستمرار؟'),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: Text('لا')),
ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: Text('نعم')),
],
),
);
if (result == true) {
// المستخدم ضغط نعم
}PHPخصائص مهمة خاصة بـ showDialog (القيم/الخيارات التي تمرّرها إلى showDialog)
هذه الخصائص تتحكم في سلوك عرض الحوار (لا في مظهر محتواه).
context— سياق الـ Build المطلوب لعرض الحوار (مطلوب). Flutter API Docsbuilder— دالة تبني وترجع ويدجت الحوار (مثلAlertDialog). Flutter API DocsbarrierDismissible—boolيحدد هل يمكن إغلاق الحوار بالنقر على الخلفية (الحاجز). الافتراضي: true. إذا جعلتهfalseلن يُغلق بالنقر على الخارج. Flutter API DocsbarrierColor— لون الحاجز (الظِلّ الذي يغطي الخلفية). إذا لم تحدده يستخدم قيمة منDialogThemeأوColors.black54. Flutter API DocsbarrierLabel— تسمية لذوي الاحتياجات (accessibility) عند وجود حاجز قابل للإغلاق. Flutter API Docs+1useSafeArea— إذا كان الحوار يجب أن يبقى داخل مناطق الأمان (notch/status bar). الافتراضي: true. Flutter API DocsuseRootNavigator— يحدد أيNavigatorسيُستخدم (الجذر أم الأقرب). مفيد إذا عندك nested navigators. الافتراضي: true. Flutter API DocsrouteSettings— لتمرير إعدادات الـ Route (اسم، بيانات …). Flutter API Docs- (ملاحظة للمتحكمين بالانتقالات) إذا أردت تحكّم كامل بالانتقال (animation/transition) استخدم
showGeneralDialogالذي يتيحtransitionBuilderوtransitionDuration. Flutter API Docs
تلميح عملي: إذا أردت حوار تحميل لا يُغلق بالخطأ — ضع
barrierDismissible: falseوغالبًا غلف المحتوى بـWillPopScope(onWillPop: () async => false)لمنع زر الرجوع كذلك. Flutter API Docs
خصائص مهمة خاصة بـ AlertDialog (الخيارات التي تتحكم بمظهر ومحتوى الـ AlertDialog)
هذه الخصائص تتحكم في محتوى ومظهر مربع الحوار نفسه.
title— ويدجت (عادةText) يظهر فوق المحتوى كعنوان. Flutter API Docscontent— ويدجت في منتصف الحوار (نص، فورم، أو أي ويدجت). Flutter API Docsactions— قائمة ويدجت للأزرار (عادةTextButton/ElevatedButton) تظهر أسفل المحتوى. عند الضغط على زر عادةً تستدعيNavigator.pop(result). Flutter API Docs+1backgroundColor— لون خلفية البطاقة (Material) للحوار. إذا لم تحدده يستخدمDialogThemeDataأو نظام الألوان الافتراضي. Flutter API Docselevation— ارتفاع الظل (shadow) للحوار. waldo.comshape— شكل الحافة (مثلاًRoundedRectangleBorder) لتدوير الزوايا. waldo.cominsetPadding— المسافة بين إطار الحوار وحافة الشاشة (يجعل الحوار أصغر/أكبر بالنسبة للشاشة). waldo.comscrollable—boolإن كان محتوى الحوار يمكن أن يكون قابل للتمرير عندما يكون كبيرًا. Flutter API DocstitlePadding— المسافات حولtitle(Padding) — تُستخدم فقط إذا كانtitleغيرnull. الافتراضيات موجودة لكن بإمكانك تخصيصها. o7planning.orgcontentPadding— padding حولcontent. o7planning.orgactionsPaddingوbuttonPadding— لتحكم بالـ padding حول شريط الأزرار أو كل زر على حدة. o7planning.orgclipBehavior— سلوك القص عند وجودshape. waldo.comsemanticLabel— تسمية للـ accessibility عندما يُستخدم الحوار بطريقة معينة. Flutter API Docs
نصائح للمبتدئين (مختصرة ومفيدة)
showDialog= طريقة لـ عرض (open)؛AlertDialog= ما تعرضه (مظهر المحتوى). Flutter API Docs+1- دائمًا استعمل
Navigator.of(context).pop(value)داخل أزرار الـactionsلإغلاق الحوار وإرجاع قيمة إن احتجت. Flutter API Docs - إذا أردت أن يُعرض الحوار بعد تحميل الواجهة مباشرةً، لا تنادي
showDialogداخلbuild()مباشرة — استعملWidgetsBinding.instance.addPostFrameCallback(...)بعد الظهور الأولي. (هذه قاعدة عملية معروفة في Flutter). - لجلسات معقدة داخل الحوار (فورم، عداد، خيارات) استخدم
StatefulBuilderأوStatefulWidgetداخلbuilderللحفاظ على الحالة محليًا.
مثال 1 — حوار تأكيد (الأبسط)
(استخدام showDialog مع AlertDialog وإرجاع قيمة true/false)
// داخل أي وظيفة async (مثلاً onPressed)
final bool? result = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text('تأكيد'),
content: Text('هل تريد المتابعة؟'),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: Text('لا')),
ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: Text('نعم')),
],
),
);
if (result == true) {
// المستخدم اختار نعم
}PHPشرح: showDialog يفتح الحوار. الأزرار تنهي الحوار عبر pop(value) والقيمة ترجع للمتغير result.
مثال 2 — منع الإغلاق بالنقر على الخارج أو زر الرجوع
(حوار تحميل لا يمكن إغلاقه بالخطأ)
showDialog(
context: context,
barrierDismissible: false, // يمنع النقر على الخلفية لإغلاقه
builder: (ctx) {
return WillPopScope(
onWillPop: () async => false, // يمنع زر الرجوع
child: Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 12),
Text('جاري التحميل...'),
],
),
),
),
),
);
},
);PHPشرح: استخدم barrierDismissible: false + WillPopScope لمنع الإغلاق نهائيًا — مناسب لعمليات تحتاج انتظار.
مثال 3 — حوار يحتاج حالة داخلية (StatefulBuilder)
(زيادة عداد داخل الحوار)
showDialog(
context: context,
builder: (ctx) {
int counter = 0;
return StatefulBuilder(
builder: (ctx, setState) {
return AlertDialog(
title: Text('عداد داخل الحوار'),
content: Text('القيمة: $counter'),
actions: [
TextButton(
onPressed: () => setState(() => counter--),
child: Text('-'),
),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text('+'),
),
TextButton(onPressed: () => Navigator.of(ctx).pop(counter), child: Text('حفظ')),
],
);
},
);
},
);
PHPشرح: StatefulBuilder يسمح باستدعاء setState محلي داخل الـ dialog لتحديث واجهته فقط.
مثال 4 — حوار يحتوي فورم مع التحقق (Form + validation)
final _formKey = GlobalKey<FormState>();
String? name;
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: Text('نموذج بسيط'),
content: Form(
key: _formKey,
child: TextFormField(
decoration: InputDecoration(labelText: 'الاسم'),
validator: (v) => (v == null || v.trim().isEmpty) ? 'اكتب اسمًا' : null,
onSaved: (v) => name = v,
),
),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('إلغاء')),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
Navigator.of(ctx).pop(name);
}
},
child: Text('حفظ'),
),
],
);
},
);
PHPشرح: مثال عملي لفورم داخل AlertDialog مع validator وonSaved.
مثال 5 — حوار قابل للتمرير (كبر المحتوى)
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('محتوى طويل'),
scrollable: true, // مهم
content: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(20, (i) => Text('سطر طويل رقم ${i+1}')),
),
actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('اغلاق'))],
),
);
PHPشرح: استخدم scrollable: true ليصبح محتوى الـ AlertDialog قابلًا للتمرير بدلًا من الخروج عن الشاشة.
مثال 6 — انتقال مخصّص (حوار ينزلق من الأسفل)
(باستخدام showGeneralDialog وSlideTransition)
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'إغلاق',
barrierColor: Colors.black54,
transitionDuration: Duration(milliseconds: 300),
pageBuilder: (ctx, a1, a2) => SizedBox.shrink(), // المحتوى يبنى في transitionBuilder
transitionBuilder: (ctx, anim, secAnim, child) {
final tween = Tween(begin: Offset(0, 1), end: Offset.zero).chain(CurveTween(curve: Curves.easeOut));
return SlideTransition(
position: anim.drive(tween),
child: Align(
alignment: Alignment.bottomCenter,
child: Material(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
child: Container(
height: 300,
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('حوار من الأسفل', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Spacer(),
ElevatedButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('اغلاق')),
],
),
),
),
),
);
},
);PHPشرح: showGeneralDialog يعطيك transitionBuilder كامل لتصميم أي حركة تريد.
مثال 7 — استخدام useRootNavigator (عند وجود nested navigators)
// اذا عندك nested Navigator (مثل داخل BottomNavigationBar each tab has its own Navigator)
// وتريد عرض الحوار فوق كلّ التطبيق (root), استخدم:
showDialog(
context: context,
useRootNavigator: true, // يضمن أن الحوار يُعرض عبر الـ root Navigator
builder: (ctx) => AlertDialog(
title: Text('Root Dialog'),
content: Text('هذا الحوار فوق الـ root navigator'),
actions: [TextButton(onPressed: () => Navigator.of(ctx, rootNavigator: true).pop(), child: Text('اغلاق'))],
),
);PHPشرح: useRootNavigator: true مهم عندما تعمل بتراكيب مع أكثر من Navigator.
مثال 8 — تغيير الشكل / اللون (backgroundColor, shape, elevation, insetPadding)
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('حوار مخصص'),
content: Text('خلفية بلون مخصص وزوايا مدوّرة'),
backgroundColor: Colors.teal[50],
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
insetPadding: EdgeInsets.symmetric(horizontal: 40, vertical: 24), // مسافة من حواف الشاشة
actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('اغلاق'))],
),
);
PHPشرح: تحكم في مظهر الحوار بسهولة باستخدام خصائص AlertDialog.