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

مقدمة إلى GoRouter

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

مقدمة إلى GoRouter

مع نمو تطبيقات Flutter وازدياد تعقيدها، قد تصبح واجهة برمجة تطبيقات Navigator المدمجة صعبة الإدارة. GoRouter هو حزمة توجيه تصريحية رسمية من فريق Flutter تُدخل التنقل المستند إلى عناوين URL، والربط العميق، وهيكل شجرة مسارات نظيف. بدلاً من دفع المسارات وإزالتها بشكل أمري، تعلن تسلسل المسار بالكامل مسبقاً وتتنقل باستخدام مسارات تشبه عناوين URL.

ملاحظة: GoRouter هو حل التوجيه الموصى به لتطبيقات Flutter التي تتطلب الربط العميق أو دعم الويب أو التنقل المتداخل المعقد. يتم صيانته من قِبل فريق Flutter ويأتي كجزء من حزمة go_router على pub.dev.

إضافة go_router إلى مشروعك

ابدأ بإضافة التبعية إلى ملف pubspec.yaml الخاص بك:

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  go_router: ^14.0.0

بعد حفظ الملف، شغّل flutter pub get في طرفيتك لجلب الحزمة. ثم استوردها حيثما تقوم بتهيئة التوجيه:

استيراد GoRouter

import 'package:go_router/go_router.dart';

الإعلان عن شجرة مسارات باستخدام GoRoute

يتطلب GoRouter منك الإعلان عن شجرة مسارات — قائمة من إدخالات GoRoute تربط مسارات URL بالودجات. كل GoRoute يحدد path وbuilder (أو pageBuilder) يُعيد الودجت للعرض. ثم تمرر نسخة GoRouter إلى منشئ MaterialApp.router.

تعريف GoRouter بمسارات متعددة

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final GoRouter _router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomeScreen();
      },
    ),
    GoRoute(
      path: '/profile',
      builder: (BuildContext context, GoRouterState state) {
        return const ProfileScreen();
      },
    ),
    GoRoute(
      path: '/product/:id',
      builder: (BuildContext context, GoRouterState state) {
        final String productId = state.pathParameters['id']!;
        return ProductScreen(productId: productId);
      },
    ),
  ],
);

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'GoRouter Demo',
      routerConfig: _router,
    );
  }
}
نصيحة: احتفظ بنسخة GoRouter الخاصة بك كمتغير على المستوى الأعلى أو محدد النطاق بالموفر، وليس داخل طريقة build الخاصة بالودجت. إعادة إنشاء الموجه عند كل إعادة بناء تتسبب في إعادة تعيين حالة التنقل بشكل غير متوقع.

التنقل باستخدام context.go و context.push

يضيف GoRouter أساليب امتداد مباشرة على BuildContext حتى تتمكن من التنقل في أي مكان في شجرة الودجات دون الحاجة إلى مرجع لكائن الموجه. الأسلوبان الأهم هما:

  • context.go(path) — يستبدل مكدس التنقل بالكامل بالمسار الجديد. لا يستطيع المستخدم الضغط على زر الرجوع للعودة. استخدمه للتنقل على المستوى الأعلى كتبديل التبويبات أو الذهاب إلى الشاشة الرئيسية بعد تسجيل الدخول.
  • context.push(path) — يدفع المسار الجديد فوق المكدس الحالي مع الحفاظ على سلوك زر الرجوع. استخدمه للتعمق في التفاصيل: الانتقال من قائمة إلى شاشة تفاصيل.

استخدام context.go و context.push

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('الرئيسية')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                // يستبدل المكدس — لا زر رجوع للرئيسية
                context.go('/profile');
              },
              child: const Text('الذهاب إلى الملف الشخصي'),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                // يدفع فوق المكدس — زر الرجوع يعيد هنا
                context.push('/product/42');
              },
              child: const Text('عرض المنتج 42'),
            ),
          ],
        ),
      ),
    );
  }
}

معاملات المسار ومعاملات الاستعلام

يدعم GoRouter كلاً من معاملات المسار (شرائح مسبوقة بنقطتين مثل :id) ومعاملات الاستعلام (أزواج مفتاح-قيمة بعد ?). يمكن الوصول إليها من خلال كائن GoRouterState الممرر لكل منشئ:

  • state.pathParameters['id'] — يستخرج قيمة شريحة المسار
  • state.uri.queryParameters['filter'] — يستخرج قيمة سلسلة الاستعلام

لتمرير معامل استعلام عند التنقل، ما عليك سوى إلحاقه بسلسلة المسار:

مثال على معاملات المسار والاستعلام

// التنقل مع معامل استعلام
context.go('/profile?tab=settings');

// في منشئ المسار:
GoRoute(
  path: '/profile',
  builder: (BuildContext context, GoRouterState state) {
    final String tab = state.uri.queryParameters['tab'] ?? 'overview';
    return ProfileScreen(initialTab: tab);
  },
),

استبدال Navigator.push بـ GoRouter

إذا كنت تُهاجر تطبيقاً موجوداً، استبدل كل استدعاء Navigator.push(context, MaterialPageRoute(builder: ...)) باستدعاء context.push('/path')، وكل استدعاء Navigator.pushReplacement(...) بـ context.go('/path'). هذا يجعل نية التنقل واضحة ويتيح الربط العميق تلقائياً.

تحذير: لا تخلط بين Navigator.push وcontext.go/context.push في التطبيق نفسه إلا إذا فهمت تماماً كيف يتفاعل GoRouter مع Navigator الأساسي. يمكن أن يُنتج الخلط بينهما سلوكاً غير متوقع لمكدس الرجوع، خاصة على الويب.

الملخص

يُحضر GoRouter التوجيه التصريحي المستند إلى عناوين URL إلى Flutter. تُضيف حزمة go_router، وتُعرّف شجرة مسارات بإدخالات GoRoute في نسخة GoRouter، وتربطها بـ MaterialApp.router، وتتنقل باستخدام context.go() (استبدال المكدس) أو context.push() (إضافة إلى المكدس). يتم الإعلان عن معاملات المسار والاستعلام في سلسلة مسار المسار والوصول إليها عبر GoRouterState. هذا الأساس يجعل الربط العميق والتنقل المتداخل ومزامنة URL أمراً مباشراً في تطبيقات Flutter سواء للجوال أو الويب.

النقطة الرئيسية: استخدم context.go() عندما تريد استبدال تاريخ التنقل (مثلاً بعد تسجيل الدخول) وcontext.push() عندما تريد أن يتمكن المستخدم من الضغط على رجوع (مثلاً عند عرض شاشة التفاصيل). شجرة المسارات المعلنة في GoRouter هي المصدر الوحيد للحقيقة لجميع عمليات التنقل في تطبيقك.