Navigator

1) الفكرة العامة (مكدس الشاشات — Route Stack)
  • Flutter يحافظ على مكدس (Stack) من الـ Routes (الصفحات).
  • push → تدفع صفحة جديدة فوق المكدس.
  • pop → تزيل الصفحة العلوية وتعود للصفحة التي تحتها.
  • pushReplacement → تدفع صفحة جديدة وتزيل الصفحة الحالية مكانها (لا تترك الصفحة الحالية في الأعلى).
2) Navigator.push(...)

متى تستخدمه؟
للتنقل العادي إلى صفحة جديدة مع إمكانية الرجوع (مثلاً من Home → Details).

سلوك مهم: الدالة ترجع Future<T?> الذي ينتهي عندما تُغلق الصفحة الجديدة (pop) — هذا يمكن استخدامه لاستلام نتيجة من الصفحة التي فتحتها.

مثال:

// من الصفحة A:
final result = await Navigator.of(context).push<String>(
  MaterialPageRoute(builder: (_) => const BPage()),
);
// بعد عودة BPage عبر Navigator.pop(context, someValue),
// المتغير result يحتوي someValue
 Navigator.of(context).push(MaterialPageRoute(builder: (context)=> Homapage()));
Dart

في الصفحة B:

ElevatedButton(
  onPressed: () {
    Navigator.of(context).pop('تم الحفظ'); // ترجع 'تم الحفظ' للنداء السابق
  },
  child: Text('رجوع بالنتيجة'),
);
Dart

متى لا تستخدمه؟
لو تريد منع المستخدم من العودة لصفحة معينة بعد الانتقال (مثلاً بعد تسجيل الدخول)، فهنا push لوحده غير مناسب.

3) Navigator.pop(context, [result])

متى تستخدمه؟
للخروج من الصفحة الحالية والرجوع للي قبلها. يمكنك إرسال قيمة (result) تُعاد إلى الشخص الذي نادى push.

مثال:

// داخل الصفحة الحالية:
Navigator.of(context).pop(); // مجرد إغلاق
// أو
Navigator.of(context).pop('نجاح'); // إرجاع قيمة
Dart

ملاحظة: يمكنك التحقق إنّ هناك صفحة يمكن أن تُغلق عبر Navigator.canPop(context).

4) Navigator.pushReplacement(...)

ماذا يفعل؟
يدفع Route جديدة ويزيل (يستبدل) الـ Route الحالية. النتيجة: لا يمكن للمستخدم أن يعود للصفحة المستبدلة بالضغط على back لأنّها أُزيلت من المكدس.

متى تستخدم؟

  • بعد شاشة تسجيل الدخول: بعد نجاح الدخول تستبدل شاشة Login بـ Home حتى لا يعود المستخدم إلى Login.
  • عند الانتقال من شاشة Splash/Onboarding إلى Home بحيث لا تبقى الشاشة الأولى في الـ stack.

مثال:

Navigator.of(context).pushReplacement(
  MaterialPageRoute(builder: (_) => const HomePage()),
);
Dart

معلومة عن الـ result هنا:
pushReplacement يقبل باراميتر اختياري result — وهو قيمة تُرسل كأن الصفحة المستبدلة تمّت pop بها، فتُعاد لصفحة تحتها إذا كانت تنتظر نتيجة. أما pushReplacement نفسه يرجع Future ينتهي عندما تُغلق الصفحة الجديدة.

5) Navigator.pushAndRemoveUntil(...)

ماذا يفعل؟
يدفع Route جديدة ثم يزيل كل Routes السابقة حتى يحقق شرطًا (Predicate). لو استخدمت (route) => false سيُمسح كل المكدس ويجعل الصفحة الجديدة هي الجذر — مفيد تمامًا بعد عملية تسجيل دخول أو استكمال onboarding.

مثال (احذف الجميع):

Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => const HomePage()),
  (Route<dynamic> route) => false,
);
Dart

مثال (أبقي على صفحة معيّنة):

Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => const HomePage()),
  ModalRoute.withName('/someRouteName'), // تبقى حتى هذه Route فقط
);
Dart
6) Navigator.popUntil(...)

ماذا يفعل؟
يُغلق صفحات حتى يصل إلى صفحة تحقق شرطًا معينًا (predicate). مفيد للعودة لصفحة بعينها بدون الحاجة لمعرفة كم عدد الصفحات بينهما.

مثال:

Navigator.of(context).popUntil(ModalRoute.withName('/home'));
Dart
7) الفروقات والملخّص العملي
  • push → أضف صفحة جديدة. يمكن العودة. (استخدمه للتنقل العادي بين شاشات قابلة للعودة)
  • pop → اخرج من الصفحة الحالية وارجع. يمكن إرجاع قيمة. (استخدمه عند الضغط على زر رجوع أو عند إكمال مهمة وإرسال نتيجة)
  • pushReplacement → أدفع صفحة جديدة وأزل الصفحة الحالية. (استخدمه حين لا تريد السماح بالعودة للصفحة السابقة — مثال: بعد تسجيل الدخول)
  • pushAndRemoveUntil → أدفع صفحة جديدة وامسح جزءًا أو كلّ المكدس حتى شرط معيّن. (استخدمه لتنظيف التاريخ بعد خطوات متتالية مثل الانتهاء من onboarding)
  • popUntil → اخرج إلى صفحة معيّنة في المكدس.
8) أمثلة حالات استخدام واقعية
  • فتح صفحة تفاصيل ثم العودة مع نتيجة (مثلاً: اختيار عنصر):
    • FirstPage: await Navigator.push(...)
    • SecondPage: Navigator.pop(context, selectedItem)
  • بعد تسجيل الدخول (لا تريد العودة لشاشة الدخول):
    • استخدم pushReplacement أو pushAndRemoveUntil(..., (r)=>false).
  • بعد إنهاء خطوات onboarding — اجعل Home الجذر:
    • Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder:_=>Home()), (r)=>false);
  • التنقل داخل تبويبات (BottomNavigationBar):
    • عادة لا تستخدم push/pop للتنقل بين تبويبات؛ تستخدم IndexedStack أو Nested Navigators للحفاظ على الحالة لكل تبويب.
9) نصائح واعتبارات عملية
  • انتظر Future من push إذا تريد نتيجة (مثلاً لإظهار SnackBar بعد عودة المستخدم).
  • لا تضع عمليات ثقيلة داخل onPressed قبل التنقل (اجعل التنقل سريعًا، وانفّذ المعالجة في صفحة الهدف أو في background).
  • استخدم named routes (pushNamed, pushReplacementNamed, إلخ) لتنظيم المسارات في مشروع كبير.
  • استخدم pushAndRemoveUntil(..., (r)=>false) بحذر لأنّه يمسح التاريخ — قد يحير المستخدم إن احتاج الرجوع.
  • pop يمكن أن يُرسل بيانات صغيرة (مثل boolean أو id). لا تستخدمه لتمرير كميات بيانات كبيرة؛ استخدم state management أو storage.
10) مثال عملي صغير يوضّح push / pop / pushReplacement / pushAndRemoveUntil
// من الصفحة A
// 1) push وانتظار نتيجة
final picked = await Navigator.of(context).push<String>(
  MaterialPageRoute(builder: (_) => const PickerPage()),
);
// بعد العودة
print('user picked: $picked');

// 2) استبدال الصفحة الحالية
Navigator.of(context).pushReplacement(
  MaterialPageRoute(builder: (_) => const HomePage()),
);

// 3) فتح Home ومسح كل السجل (بعد login)
Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => const HomePage()),
  (route) => false,
);
Dart

وفي PickerPage:

// عندما يختار المستخدم شيئًا:
Navigator.of(context).pop('item-42'); // تعود 'item-42' إلى من نادى push
Dart

لو تحب أجهّز لك مقتطف كود كامل لتجربة تفاعلية (تطبيق صغير به 3 شاشات: A -> B (pop مع نتيجة) و زر للـ pushReplacement و زر للـ pushAndRemoveUntil) أرسله جاهز للتشغيل — أعدّيه لك فورًا.