showDialog 

1) ما هو showDialog وماذا يرجع؟

showDialog<T> هو دالة تعرض حوار (modal) فوق واجهة التطبيق، مع حاجز (modal barrier) يمنع التفاعل مع المحتوى الذي تحته.

عندما يُغلَق الحوار، تُكمِل الـ Future التي ترجعها الدالة بقيمة (أو null إذا أغلق المستخدم الحوار بدون إرجاع قيمة).

يمكنك استخدامه لعرض AlertDialog, SimpleDialog, Dialog أو أي ويدجت مخصّص.

ما هو showDialog؟
  • هو دالة في Flutter تُستخدم لعرض مربع حوار (Dialog) فوق واجهة التطبيق.
  • الهدف منه: إيقاف تفاعل المستخدم مؤقتًا مع بقية الواجهة وإجباره على التعامل مع الرسالة أو الاختيار المعروض.
  • مثل: تنبيه ⚠️، رسالة تأكيد ✅، إدخال بيانات ✍️، أو خيارات متعددة.
📌 أين يُستخدم؟
  1. رسائل التنبيه (Alert)
    مثل: “هل أنت متأكد أنك تريد الحذف؟”
    • هذا أكثر استخدام شائع.
  2. رسائل التأكيد/الرفض (Confirm)
    عندما تريد من المستخدم أن يختار “نعم / لا” أو “موافق / إلغاء”.
  3. إظهار خطأ أو معلومة مهمة
    مثل: “لا يوجد اتصال بالإنترنت”.
  4. إدخال بيانات بسيطة
    مثل: إدخال كلمة مرور أو تعليق قصير داخل مربع حوار.
  5. 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)

إذا احتجت واجهة حوار يتغيّر محتواها داخليًا (عداد، اختيارات، فورم، …)، عندك خياران شائعان:

  1. استخدام 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
  1. بناء 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 Docs
  • useRootNavigator: افتراضيًا true — يدفع الحوار إلى الـ root Navigator، مهم عند وجود Navigator داخل جزء من التطبيق (مثل nested navigators). Flutter API Docs
  • barrierColor: إن لم تحدده، يستخدم إعدادات الـ 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('من الأسفل')),
        ),
      ),
    );
  },
);
PHP
9) أفضل الممارسات (مختصر)
  • استخدم النوع العام 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 Docs
  • AlertDialog: ويدجت (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 Docs
  • builder — دالة تبني وترجع ويدجت الحوار (مثل AlertDialog). Flutter API Docs
  • barrierDismissiblebool يحدد هل يمكن إغلاق الحوار بالنقر على الخلفية (الحاجز). الافتراضي: true. إذا جعلته false لن يُغلق بالنقر على الخارج. Flutter API Docs
  • barrierColor — لون الحاجز (الظِلّ الذي يغطي الخلفية). إذا لم تحدده يستخدم قيمة من DialogTheme أو Colors.black54. Flutter API Docs
  • barrierLabel — تسمية لذوي الاحتياجات (accessibility) عند وجود حاجز قابل للإغلاق. Flutter API Docs+1
  • useSafeArea — إذا كان الحوار يجب أن يبقى داخل مناطق الأمان (notch/status bar). الافتراضي: true. Flutter API Docs
  • useRootNavigator — يحدد أي Navigator سيُستخدم (الجذر أم الأقرب). مفيد إذا عندك nested navigators. الافتراضي: true. Flutter API Docs
  • routeSettings — لتمرير إعدادات الـ 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 Docs
  • content — ويدجت في منتصف الحوار (نص، فورم، أو أي ويدجت). Flutter API Docs
  • actions — قائمة ويدجت للأزرار (عادة TextButton/ElevatedButton) تظهر أسفل المحتوى. عند الضغط على زر عادةً تستدعي Navigator.pop(result). Flutter API Docs+1
  • backgroundColor — لون خلفية البطاقة (Material) للحوار. إذا لم تحدده يستخدم DialogThemeData أو نظام الألوان الافتراضي. Flutter API Docs
  • elevation — ارتفاع الظل (shadow) للحوار. waldo.com
  • shape — شكل الحافة (مثلاً RoundedRectangleBorder) لتدوير الزوايا. waldo.com
  • insetPadding — المسافة بين إطار الحوار وحافة الشاشة (يجعل الحوار أصغر/أكبر بالنسبة للشاشة). waldo.com
  • scrollablebool إن كان محتوى الحوار يمكن أن يكون قابل للتمرير عندما يكون كبيرًا. Flutter API Docs
  • titlePadding — المسافات حول title (Padding) — تُستخدم فقط إذا كان title غير null. الافتراضيات موجودة لكن بإمكانك تخصيصها. o7planning.org
  • contentPadding — padding حول content. o7planning.org
  • actionsPadding وbuttonPadding — لتحكم بالـ padding حول شريط الأزرار أو كل زر على حدة. o7planning.org
  • clipBehavior — سلوك القص عند وجود shape. waldo.com
  • semanticLabel — تسمية للـ 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.