BuildContext — ما هو، المفاهيم الرئيسية ومبدأ العمل

المؤلف: IT Sectr نُشر: 2026-07-01 وقت القراءة: 9 دق

BuildContext هو كائن أساسي في Flutter يمثل موقع ويجت محدد في شجرة العناصر ويوفر الوصول إلى بيئته. وفقًا للتوثيق الرسمي لـ Flutter (Flutter.dev، 2026)، يعمل BuildContext كجسر بين الويجت والإطار: من خلاله، يتلقى الويجت الموضوع (Theme)، واستعلامات الوسائط (MediaQuery)، والتوطين (Localizations) والبيانات من InheritedWidget. كل ويجت له BuildContext الخاص به، الذي يُمرّر إلى طريقة build كوسيطة أولى.

النقاط الرئيسية

  • BuildContext — كائن يمثل موقع الويجت في شجرة العناصر ويوفر الوصول إلى بيئته الهرمية
  • InheritedWidget — الآلية الرئيسية لنقل البيانات لأسفل شجرة الويجتات، يتم الوصول إليه عبر BuildContext
  • طريقة of() — طريقة ثابتة تستخدم BuildContext للعثور على أقرب InheritedWidget لأعلى شجرة الويجتات (Theme.of، MediaQuery.of)
  • السياق ودورة الحياة — يتغير BuildContext عند نقل الويجت، ولا يمكن الاحتفاظ بمرجع السياق بعد dispose
  • الأخطاء — يؤدي استخدام BuildContext خارج شجرته أو بعد dispose إلى استثناءات (إعادات التحميل الساخنة، استدعاءات غير متزامنة)

ما هو BuildContext؟

BuildContext هو واجهة يُنفّذها الفصل Element، وتزود الويجت بمعلومات عن موقعه في التسلسل الهرمي لواجهة المستخدم. كل مثال BuildContext فريد لموقع محدد في الشجرة ولا يمكن نقله إلى مكان آخر. إذا غيّر الويجت والده (على سبيل المثال، انتقل إلى حاوية أخرى)، يتلقى BuildContext جديدًا.

الغرض الرئيسي لـ BuildContext هو توفير الوصول إلى InheritedWidget. عبر السياق، يجد الويجت أقرب مثال Theme، MediaQuery، Navigator أو Directionality، صاعدًا لأعلى الشجرة. هذه الآلية تشكّل أساس نظام السمات، والتنقل، والتصميم المتكيّف في Flutter. دون BuildContext، لا يمكلن لأي ويجت الوصول إلى هذه البيانات.

وفقًا لوثائق معمارية Flutter (جوجل، 2026)، يستخدم BuildContext أيضًا للعثور على RenderObject المرتبط بالويجت لقياس الأحجام والتوضع. الطرق مثل findRenderObject() و size متاحة عبر السياق. يوفر السياق أيضًا الوصول إلى التوطين عبر Localizations.of(context).

BuildContext هو Element، ليس Widget

فهم معماري مهم: BuildContext هو واجهة يُنفّذها Element، ليس Widget. Element هو «الصمغ» بين Widget (التهيئة) و RenderObject (العرض الفعلي). عندما تقول الوثائق «سياق الويجت»، فهي تعني العنصر الذي يدير ذلك الويجت. تتلقى طريقة build هذا النوع من السياق بالضبط — سياق الويجت الذي يتم إنشاؤه، ليس الويجتات الفرعية التي يعيدها.

كيف يعمل BuildContext؟

آلية عمل BuildContext تقوم على اجتياز شجرة العناصر من الأسفل إلى الأعلى. عندما يستدعي الويجت Theme.of(context)، يبدأ السياق البحث من العنصر الحالي ويتحرك لأعلى نحو الجذر، متحققًا من كل عنصر وجود InheritedWidget من نوع Theme. يتم إرجاع أول InheritedWidget يتم العثور عليه — وهذا يضمن حصول الويجت على الموضوع من أقرب تعريف.

يخزن كل BuildContext مرجعًا للسياق الأصلي (parent) وللسياقات الفرعية. هذا ارتباط ثنائي الاتجاه يسمح بالتنقل في الشجرة كلا لأعلى (إلى الآباء) ولأسفل (إلى الأبناء). في Flutter، يتم استخدام التنقل لأعلى فقط للبحث عن InheritedWidget — يمكن للويجت الحصول على البيانات فقط من الأسلاف، ليس من الأحفاد. هذا قيد معماري أساسي.

وفقًا لكود Flutter المصدري (Flutter SDK، 2026)، يحتوي BuildContext على الطرق: visitAncestorElements، visitChildElements، findAncestorWidgetOfExactType، dependOnInheritedWidgetOfExactType و getRenderObject. الطريقتان الأخيرتان هما الأكثر استخدامًا: dependOnInheritedWidgetOfExactType لا تجد فقط InheritedWidget، بل تشترك أيضًا في تغييراته (يتم إعادة بناء الويجت عندما يتغير InheritedWidget).

الاشتراك عبر السياق

dependOnInheritedWidgetOfExactType هي الطريقة الرئيسية لـ BuildContext التي توفر التفاعلية. عندما يستدعي الويجت Theme.of(context)، لا يحصل فقط على الموضوع — بل يشترك في تغييراته. إذا تغيّر Theme (على سبيل المثال، عند التبديل بين الوضع المظلم/الفاتح)، يتم إعادة بناء جميع الويجتات المشتركة تلقائيًا. هذه هي آلية التفاعلية في Flutter.

BuildContext vs Element

BuildContext هو واجهة، بينما Element هو تنفيذها. في كود Flutter، تعمل دائمًا عبر واجهة BuildContext دون معرفة نوع العنصر المحدد (StatelessElement، StatefulElement، ProxyElement، إلخ). هذا متعمد: لا يحتاج المطور لمعرفة تفاصيل تنفيذ العنصر — الواجهة للوصول إلى البيئة كافية.

تُنفّذ أنواع مختلفة من العناصر BuildContext بطرق مختلفة: StatelessElement يمرر استدعاءات build ببساطة، StatefulElement يدير State، و InheritedElement يتتبع الاشتراكات عبر dependOnInheritedWidgetOfExactType. ومع ذلك، من وجهة نظر المطور، كلها BuildContext بواجهة API موحدة.

الجانبBuildContextElement
النوعواجهة (فصل مجرد)فصل تنفيذي
الاستخدامبواسطة المطور في buildآلية Flutter الداخلية
طرق البحثof()، findAncestor...()mount، update، unmount
العلانيةAPI عامداخلي للحزمة
العلاقة بالويجتعبر حقل widgetيملك widget و state

أمثلة كود Dart

الاستخدام الأساسي لـ BuildContext للوصول إلى الموضوع واستعلامات الوسائط:

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

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final media = MediaQuery.of(context);

    return Container(
      padding: EdgeInsets.all(media.size.width * 0.02),
      child: Text(
        'نص منقح',
        style: theme.textTheme.headlineMedium,
      ),
    );
  }
}

مثال مع التنقل عبر BuildContext. يستخدم Navigator.of(context) السياق للعثور على أقرب Navigator لأعلى الشجرة:

dart
class _NavigateButtonState extends State<NavigateButton> {
  void _navigate() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) => const DetailsScreen(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _navigate,
      child: const Text('انتقل إلى التفاصيل'),
    );
  }
}

مثال للعثور على حجم الويجت عبر BuildContext. تعيد طريقة findRenderObject() RenderObject الذي يمكن الحصول على الحجم منه:

dart
void _printSize(BuildContext context) {
  final renderBox = context.findRenderObject() as RenderBox?;
  if (renderBox != null) {
    print('حجم الويجت: ${renderBox.size}');
  }
}

مهم: findRenderObject() تعيد null إذا لم يكن الويجت مركّبًا بعد أو تم فصله بالفعل. تحقّق دائمًا من النتيجة قبل الاستخدام. قد تعيد استدعاء هذه الطريقة داخل build قبل اكتمال البناء نتيجة null أيضًا.

InheritedWidget و BuildContext

InheritedWidget هو ويجت خاص ينشر البيانات بكفاءة لأسفل الشجرة عبر BuildContext. عندما يستدعي ويجت فرعي MyInheritedWidget.of(context)، يتنقل BuildContext لأعلى الشجرة، يجد أقرب InheritedWidget من النوع المطابق ويعيد بياناته. في نفس الوقت، يشترك السياق في التغييرات: إذا تغيّر InheritedWidget، يتم إعادة بناء جميع الويجتات المشتركة تلقائيًا.

تستبدل المجموعة BuildContext + InheritedWidget المتغيرات العالمية وإرسال الخصائص (prop drilling) — نقل البيانات عبر سلسلة من البناء. بدلًا من إرسال موضوع عبر 10 مستويات من الويجتات، يمكن لكل ويجت الوصول إليه مباشرة عبر Theme.of(context). هذا يجعل الكود أنظف ويقلّل عدد المعلمات الممررة.

وفقًا لفريق Flutter (جوجل، أبريل 2026)، InheritedWidget هو آلية فعّالة لدرجة أن جميع الحلول الرسمية لإدارة الحالة مبنية عليه: Provider يُغلّف InheritedWidget، Riverpod يستخدمه كإحدى طبقاته، و Flutter SDK نفسه (Theme، MediaQuery، Navigator، Localizations) مبني بالكامل على هذه المعمارية.

إنشاء InheritedWidget خاص بك

يسمح إنشاء InheritedWidget خاص بك بنشر البيانات دون اعتمادات خارجية. يمد الفصل InheritedWidget ويوفر طريقة ثابتة of(BuildContext context). هذا بديل بسيط لـ Provider في سيناريوهات بسيطة:

dart
class AppConfig extends InheritedWidget {
  final String apiUrl;
  final bool useDarkMode;

  const AppConfig({
    super.key,
    required this.apiUrl,
    required this.useDarkMode,
    required super.child,
  });

  static AppConfig of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppConfig>()!;
  }

  @override
  bool updateShouldNotify(AppConfig oldWidget) {
    return apiUrl != oldWidget.apiUrl || useDarkMode != oldWidget.useDarkMode;
  }
}

الآن يمكن لأي ويجت أسفل في الشجرة الوصول إلى التهيئة: final config = AppConfig.of(context);. إذا تغيّرت التهيئة، ستتم إعادة بناء جميع الويجتات المشتركة تلقائيًا.

الأخطاء الشائعة

أول خطأ شائع هو الاحتفاظ بـ BuildContext بعد dispose أو استخدامه في دالة غير متزامنة دون التحقّق من mounted. BuildContext مرتبط بعنصر، ويمكن تدمير العنصر (عند إزالة الويجت من الشجرة). يؤدي استخدام السياق بعد تدمير العنصر إلى استثناء. الحل هو استخدام context.mounted (متاح في إصدارات Flutter الأحدث) أو التحقّق من mounted في State.

الخطأ الثاني هو استدعاء Theme.of(context) في initState. في مرحلة initState، لم يتم تركيب السياق بعد كاملًا في الشجرة. قد تعيد البحث عن InheritedWidget في initState نتيجة null أو تطرح استثناءً. جميع استدعاءات of(context) يجب أن تتم في build أو didChangeDependencies، حيث يكون السياق مضمونًا في الشجرة.

الخطأ الثالث هو استخدام BuildContext من ويجت لتحكيم ويجت آخر. BuildContext ليس مصممًا للتفاعل بين الويجتات خارج التسلسل الهرمي أب-ابن. إذا كنت تحتاج إلى إدارة حالة ويجت آخر، استخدم الاستدعاءات الراجعة، أو المتحكمات، أو أدوات إدارة الحالة.

الخطأ الرابع هو إرسال BuildContext إلى دالة غير متزامنة تعيش أكثر من dispose الويجت. سيناريو نموذجي: Navigator.of(context) مخزن في متغيّر ومستخدم بعد مغادرة المستخدم للشاشة. الحل هو عدم احتفاظ السياق في كائنات ثابتة أو طويلة العمر.

السياق في العمليات غير المتزامنة

نمط السلامة للعمل مع BuildContext في العمليات غير المتزامنة: تحقّق دائمًا من mounted قبل استخدام السياق ولا تحتفظ بالسياق في إغلاقات قد تعيش أكثر من الويجت:

dart
Future<void> _safeNavigation(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 2));
  if (!context.mounted) return;
  Navigator.of(context).push(MaterialPageRoute(...));
}

أفضل الممارسات

يتطلب العمل مع BuildContext فهم دورة حياته وقيوده. القاعدة الأولى: استخدم السياق فقط داخل الطرق التي تتلقاه كمعلم (build، didChangeDependencies). لا تحتفظ بالسياق في حقول الفصل أو متغيرات ثابتة — هذا يؤدي تقريبًا دائمًا إلى أخطاء.

القاعدة الثانية: للوصول إلى بيانات InheritedWidget، فضّل didChangeDependencies على build. إذا كانت البيانات مطلوبة فقط للتهيئة وليس للعرض، didChangeDependencies هو المكان الصحيح. هذا يسمح بفصل منطق التهيئة عن بناء واجهة المستخدم ويتجنب الاستدعاءات المتكررة في كل تحديث.

القاعدة الثالثة: عند العمل مع العمليات غير المتزامنة، استخدم استدعاءات راجعة لا تعتمد على السياق، أو تحقّق من mounted. إذا كانت عملية غير متزامنة تتطلب تنقلًا أو وصولًا إلى الموضوع، احصل على هذه البيانات مقدمًا (في سياق build أو initState المتزامن) وخزنها في متغيرات محلية، ليس في السياق.

متى يكون السياق مطلوبًا ومتى لا

  • مطلوب: الوصول إلى Theme، MediaQuery، Navigator، Localizations، ScaffoldMessenger
  • مطلوب: البحث عن RenderObject لقياس الأحجام
  • مطلوب: إنشاء SnackBar، BottomSheet، Dialog
  • غير مطلوب: استدعاء طرق منطق الأعمال، طلبات HTTP، عمليات قاعدة البيانات
  • غير مطلوب: بناء الويجتات خارج build (في المصانع، البناء)

الأسئلة الشائعة

ما هو BuildContext في Flutter؟

BuildContext هو واجهة تمثل موقع الويجت في شجرة العناصر. من خلاله، يحصل الويجت على الوصول إلى بيئته: الموضوع، واستعلامات الوسائط، والمتنقل، والبيانات من InheritedWidget. كل ويجت له سياقه الفريد الخاص به.

كيف يعمل BuildContext؟

BuildContext يجتاز الشجرة من العنصر الحالي لأعلى إلى الجذر، ويجد أقرب InheritedWidget من النوع المطلوب. طريقة dependOnInheritedWidgetOfExactType لا تجد البيانات فقط، بل تشترك الويجت في التغييرات — عند تحديث InheritedWidget، يتم إعادة بناء الويجت تلقائيًا.

لماذا لا يمكن حفظ BuildContext في حقول الفصل؟

BuildContext مرتبط بعنصر في الشجرة، ويمكن تدمير العنصر (يتم إزالة الويجت). يؤدي استخدام سياق مخزن بعد إزالة الويجت إلى استثناء. إذا كان السياق مطلوبًا في استدعاء غير متزامن، تحقّق من mounted قبل الاستخدام.

ما الفرق بين BuildContext و Element؟

BuildContext هو واجهة، Element هو تنفيذ. يعمل المطور عبر BuildContext دون معرفة نوع العنصر المحدد. Element هو آلية Flutter الداخلية التي تربط Widget بـ RenderObject وتدير دورة الحياة.

هل يمكنني الحصول على BuildContext ويجت آخر؟

لا يوجد وصول مباشر إلى سياق ويجت آخر. للسياق الأصلي، استخدم context.findAncestorStateOfType لـ State أو المفاتيح (GlobalKey). للسياق الفرعي — مرر استدعاءً راجعًا. BuildContext غير مصمم للوصول بين الويجتات خارج التسلسل الهرمي.

الملخص

  • BuildContext — كائن أساسي في Flutter يمثل موقع الويجت في الشجرة ويوفر الوصول إلى البيئة الهرمية عبر InheritedWidget
  • آلية البحث — BuildContext يجتاز الشجرة من الأسفل لأعلى، ويجد أقرب InheritedWidget من النوع المطلوب ويشترك في تغييراته
  • الاستخدام الرئيسي — Theme.of(context)، MediaQuery.of(context)، Navigator.of(context) للوصول إلى السمات، الاستجابة والتنقل
  • BuildContext vs Element — BuildContext واجهة عامة، Element تنفيذ خاص. يعمل المطور دائمًا عبر BuildContext
  • دورة الحياة — BuildContext يعيش طالما يعيش العنصر المقابل، بعد dispose لا يجب استخدام السياق
  • الأخطاء — احتفاظ السياق في كائنات طويلة العمر، استخدامه في initState، استخدامه بعد dispose هي مصادر شائعة للأخطاء
  • القاعدة — استخدم BuildContext فقط داخل build/didChangeDependencies، لا تحتفظ به، تحقّق من mounted في السيناريوهات غير المتزامنة

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا