التنقل والتوجيه

أساسيات Navigator 1.0

15 دقيقة الدرس 1 من 14

أساسيات Navigator 1.0

يعتمد Navigator 1.0 في Flutter على نظام تنقل أمري (imperative) قائم على المكدس (stack) ومدمج في الإطار. تخيّله كمكدّس فعلي من الشاشات: كل صفحة جديدة تفتحها تُضاف فوق المكدس، وعند الضغط على زر الرجوع تُزال الصفحة العليا لتكشف الشاشة السابقة. فهم هذا النموذج الذهني هو أساس كل التنقل في Flutter.

تُدير ودجت Navigator مكدساً من كائنات Route. يحتوي كل Route على واجهة مستخدم كاملة الشاشة (أو نافذة مشروطة)، وحركة الانتقال، ودورة حياته. أكثر أنواع المسارات شيوعاً هو MaterialPageRoute، الذي يوفر تلقائياً انتقال الانزلاق الصحيح للمنصة على Android، وانزلاقاً من اليمين بأسلوب Cupertino على iOS.

ملاحظة: يُسمى Navigator 1.0 "أمرياً" لأنك تُخبره بالضبط ما يجب فعله — ادفع هذا المسار، أو ارجع إلى هذه الشاشة. في المقابل، Navigator 2.0 (Router API) تصريحي ومدفوع بحالة التطبيق. لمعظم التطبيقات الصغيرة والمتوسطة، يُعدّ Navigator 1.0 نقطة البداية الصحيحة.

الوصول إلى Navigator

تصل إلى أقرب Navigator في شجرة الودجات عبر BuildContext. يوفر Flutter عدة توابع ثابتة مريحة:

  • Navigator.push(context, route) — يدفع مساراً جديداً إلى المكدس.
  • Navigator.pop(context) — يزيل المسار العلوي من المكدس.
  • Navigator.pushNamed(context, routeName) — يدفع مساراً مسمى (يتطلب جدول مسارات في MaterialApp).
  • Navigator.pushReplacement(context, route) — يستبدل المسار الحالي دون إبقائه في المكدس (مفيد لانتقالات تسجيل الدخول → الرئيسية).
  • Navigator.popUntil(context, predicate) — يزيل المسارات حتى يُرجع الشرط true.

دفع شاشة جديدة باستخدام MaterialPageRoute

MaterialPageRoute هو فئة فرعية من PageRoute تبني ودجتك داخل انتقال صفحة Material قياسي. معامله الإلزامي هو رد نداء builder يستقبل BuildContext ويُرجع ودجت الوجهة.

الانتقال إلى شاشة التفاصيل

// الشاشة الأولى
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('الرئيسية')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // دفع DetailScreen إلى مكدس التنقل
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailScreen(itemId: 42),
              ),
            );
          },
          child: const Text('اذهب إلى التفاصيل'),
        ),
      ),
    );
  }
}

// الشاشة الثانية
class DetailScreen extends StatelessWidget {
  final int itemId;
  const DetailScreen({super.key, required this.itemId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('عنصر $itemId')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('رجوع'),
        ),
      ),
    );
  }
}
نصيحة: تُضيف ودجت AppBar تلقائياً زر الرجوع عندما يوجد مسار أسفلها في المكدس — لا تحتاج إلى إضافة Navigator.pop لسهم الرجوع. استدعاء pop اليدوي ضروري فقط لأزرار الرجوع المخصصة أو التنقل البرمجي.

إرجاع البيانات من شاشة

من أقوى مزايا Navigator 1.0 إمكانية إرجاع البيانات من شاشة مدفوعة. تُرجع Navigator.push كائن Future يكتمل عند إزالة المسار المدفوع. تُمرر النتيجة إلى Navigator.pop(context, result).

تمرير البيانات إلى الشاشة المستدعية

// المستدعي: انتظر النتيجة من شاشة الاختيار
Future<void> _openColorPicker(BuildContext context) async {
  final String? selectedColor = await Navigator.push<String>(
    context,
    MaterialPageRoute(
      builder: (context) => const ColorPickerScreen(),
    ),
  );

  if (selectedColor != null) {
    // استخدم القيمة المُرجعة
    print('اختار المستخدم: $selectedColor');
  }
}

// تُزيل ColorPickerScreen نفسها مع القيمة المختارة
class ColorPickerScreen extends StatelessWidget {
  const ColorPickerScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('اختر لوناً')),
      body: ListView(
        children: ['أحمر', 'أخضر', 'أزرق'].map((color) {
          return ListTile(
            title: Text(color),
            onTap: () => Navigator.pop(context, color), // إرجاع القيمة
          );
        }).toList(),
      ),
    );
  }
}

دورة حياة مكدس التنقل

فهم كيفية تأثير المسارات على دورات حياة الودجات يمنع أخطاء الذاكرة والحالة الشائعة:

  • عند دفع مسار، تُبنى شجرة الودجات الخاصة به ويُستدعى initState على أي StatefulWidget فيها.
  • المسار السابق يبقى حياً في الذاكرة لكنه محجوب؛ لا تُعاد بناء ودجاته أثناء الإخفاء.
  • عند إزالة مسار، يُستدعى dispose على جميع StatefulWidgets في ذلك المسار — يجب تنظيف وحدات التحكم والتدفقات والمستمعين هناك.
  • إذا أردت تشغيل كود في كل مرة تصبح فيها الشاشة مرئية (مثل العودة من مسار فرعي)، استخدم RouteObserver أو استدع دالة تحديث في Future الذي تُرجعه Navigator.push.
تحذير: لا تستدعِ Navigator.pop أبداً إذا كان المسار الحالي هو آخر مسار في المكدس. سيُغلق التطبيق بالكامل على Android أو يُلقي خطأ. استخدم Navigator.canPop(context) للتحقق عند الشك.

المسارات المسمّاة (نظرة عامة سريعة)

في التطبيقات التي تحتوي على شاشات كثيرة، يُبقي تسجيل أسماء المسارات في MaterialApp.routes استدعاءات التنقل نظيفة. عرّف خريطة routes واستخدم Navigator.pushNamed:

تسجيل المسارات المسمّاة واستخدامها

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => const HomeScreen(),
    '/detail': (context) => const DetailScreen(itemId: 0),
    '/settings': (context) => const SettingsScreen(),
  },
)

// التنقل من أي مكان في التطبيق
Navigator.pushNamed(context, '/detail');
النقطة الرئيسية: Navigator 1.0 هو مكدس دفع/إزالة بسيط وموثوق. استخدم Navigator.push مع MaterialPageRoute للمسارات المجهولة، وNavigator.pushNamed لإعداد متعدد الشاشات أكثر نظافة، وأزِل دائماً مع بيانات اختيارية عندما تحتاج شاشة إلى إرجاع نتيجة إلى مستدعيها.