State هو الكائن المركزي لإدارة البيانات في Flutter، المرتبط بـ StatefulWidget والمسؤول عن تخزين المعلومات القابلة للتغيير وبناء الواجهة. وفقًا للوثائق الرسمية لـ Flutter (Flutter.dev, 2026)، يوجد State طوال دورة حياة عنصر واجهة المستخدم بأكملها ويبقى بعد إعادة بنائه، مما يضمن اتساق البيانات بين تحديثات واجهة المستخدم. على عكس عنصر واجهة المستخدم نفسه، يمكن لـ State تعديل حقوله وبدء إعادة البناء من خلال استدعاء setState.
الملامح الرئيسية
State هو كائن في بنية Flutter يخزن البيانات القابلة للتغيير لـ StatefulWidget ويحدد كيفية عرض هذه البيانات في الواجهة. كل StatefulWidget، عند إدراجه في الشجرة، ينشئ كائن State واحدًا بالضبط عبر طريقة createState. يوجد State بشكل مستقل عن عنصر واجهة المستخدم: إذا أعاد الأصل بناء StatefulWidget بمعلمات جديدة، يظل State كما هو ويتلقى عنصر واجهة المستخدم المحدث عبر الخاصية widget.
وفقًا للنظرة العامة المعمارية لـ Flutter (Google, 2026)، فإن فصل Widget و State هو قرار معماري متعمد يسمح للإطار بإعادة استخدام عناصر الشجرة. يمكن إنشاء عنصر واجهة المستخدم (وصف خفيف) وتدميره عدة مرات، لكن State (كائن ثقيل بالبيانات) يبقى في الذاكرة طالما أن العنصر موجود في الشجرة. هذا يمنع فقدان البيانات أثناء عمليات إعادة البناء المتكررة لعناصر واجهة المستخدم الأصل.
ينفذ State واجهة StatefulWidget عبر الأدوية: class _MyState extends State<MyWidget>. يربط الدواء State بنوع معين من StatefulWidget، مما يوفر وصولاً آمنًا من حيث النوع إلى حقوله عبر الخاصية widget.
يتم تخزين كائن State في StatefulElement — طبقة وسيطة بين Widget و RenderObject. ينشئ StatefulElement State عبر createState، ويحتفظ بمرجع إليه، ويمرر State كمالك. يتم تدمير Element فقط عند إزالة عنصر واجهة المستخدم من الشجرة — حتى ذلك الحين، يعيش State في الذاكرة.
دورة حياة State محددة وتتكون من تسلسل صارم من الاستدعاءات. فهم هذا التسلسل هو أساس الإدارة الصحيحة للموارد ومنع تسرب الذاكرة.
initState يُستدعى أولاً عند إنشاء State. في هذه الطريقة، تتم تهيئة المتحكمات، والاشتراكات في تدفقات البيانات، والمؤقتات، والقيم الأولية للحقول. من الضروري استدعاء super.initState() في السطر الأول. في مرحلة initState، لم يتم تركيب شجرة عناصر واجهة المستخدم بالكامل بعد، لذا فإن طرقًا مثل MediaQuery.of(context) قد لا تعمل بشكل صحيح.
didChangeDependencies يُستدعى بعد initState وعند كل تغيير في تبعيات InheritedWidget. هنا، وليس في initState، يجب استدعاء MediaQuery.of(context) أو Theme.of(context)، لأنه بحلول هذا الوقت تكون الشجرة قد رُكبت بالفعل. تُستدعى هذه الطريقة أيضًا إذا انتقل عنصر واجهة المستخدم إلى سياق مختلف حيث يوفر InheritedWidget قيمًا أخرى.
build هو الطريقة الرئيسية لـ State التي تُرجع شجرة عناصر واجهة المستخدم. يُستدعى بعد initState، وبعد didChangeDependencies، وبعد كل setState. يجب ألا يكون لطريقة build آثار جانبية — فهي تصف الواجهة فقط بناءً على القيم الحالية لحقول State.
didUpdateWidget يُستدعى عندما يعيد الأصل بناء StatefulWidget بمعلمات جديدة. يحصل State على إمكانية الوصول إلى عنصر واجهة المستخدم القديم عبر oldWidget ويمكنه مقارنته بالجديد. إذا تغيرت المعلمات، يمكن تحديث الحالة أو تحميل بيانات جديدة أو إعادة تشغيل حركة.
dispose هو الطريقة النهائية حيث يتم تحرير جميع الموارد: المتحكمات، والاشتراكات، والمؤقتات. بعد dispose، يتم وضع علامة على State كميت: mounted يُرجع false، واستدعاء setState يُلقي استثناءً. من الضروري استدعاء super.dispose() في السطر الأخير من الطريقة.
| الطريقة | متى تُستدعى | super إلزامي |
|---|---|---|
| initState | عند إنشاء State | نعم، في السطر الأول |
| didChangeDependencies | بعد initState وعند تغيير InheritedWidget | نعم |
| build | بعد initState و didChangeDependencies و setState | لا |
| didUpdateWidget | عند وصول عنصر واجهة مستخدم جديد من الأصل | نعم |
| setState | عند استدعاء المطور | لا |
| dispose | عند الإزالة من الشجرة | نعم، في السطر الأخير |
آلية عمل State تستند إلى ثلاثة مبادئ رئيسية: الارتباط بـ Element، والتفاعلية عبر setState، والوصول إلى الأصل عبر خاصية widget. عندما يبني Flutter شجرة العناصر ويواجه StatefulElement، يستدعي createState لعنصر واجهة المستخدم المرتبط. يتم تخزين State الذي تم إنشاؤه في العنصر ويوجد حتى تتم إزالة العنصر.
عند استدعاء setState، يضع State علامة على نفسه كمتسخ ويجدول إعادة البناء للإطار التالي. الأهم: setState لا يستدعي build فورًا — بل يسجل فقط الحاجة إلى إعادة البناء. يجمع Flutter جميع العناصر المتسخة في الإطار الحالي ويعيد بنائها دفعة واحدة، مما يحسن الأداء. بعد استدعاء build، يعود State إلى الحالة النظيفة.
تسمح خاصية widget لـ State بقراءة المعلمات الممررة إلى مُنشئ StatefulWidget. نظرًا لأن StatefulWidget غير قابل للتغيير (مثل StatelessWidget)، فإن حقوله لا تتغير — عندما تتغير المعلمات، ينشئ الأصل عنصر واجهة مستخدم جديدًا، ويستقبله State عبر didUpdateWidget. هذا يضمن أن State يعمل دائمًا مع البيانات الحالية للأصل.
مثال أساسي لـ State مع حقل يتم تعديله بواسطة مؤقت. يوضح initState و setState و dispose:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
مثال يستخدم خاصية widget للوصول إلى معلمات الأصل والتفاعل مع تغييراتها عبر didUpdateWidget:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
في المثال الثاني، يتتبع State تغييرات معلمة الإدخال name ويعيد تنسيق العرض فقط عند حدوث تغيير فعلي. بدون التحقق widget.name != oldWidget.name، كان سيتم استدعاء الطريقة في كل إعادة بناء للأصل، حتى لو لم يتغير الاسم — عمل غير ضروري للإطار.
State و StatefulWidget هما فئتان مختلفتان في بنية Flutter تؤديان أدوارًا مختلفة. StatefulWidget هو غلاف غير قابل للتغيير وخفيف يصف تكوين عنصر واجهة المستخدم وينشئ State. State هو كائن ثقيل يخزن البيانات القابلة للتغيير ويدير الاشتراكات ويبني واجهة المستخدم. هذا الفصل يسمح لـ Flutter بتدمير وإنشاء عناصر واجهة المستخدم دون فقدان الحالة.
يجب أن تكون جميع حقول StatefulWidget نهائية ويتم تعيينها في المُنشئ — لا تتغير بعد الإنشاء. State، من ناحية أخرى، يمكنه تعديل حقوله في أي وقت، ولكن يجب أن تسبق جميع التغييرات استدعاء setState حتى يعلم Flutter بالحاجة إلى إعادة البناء. هذا هو الفرق الرئيسي: StatefulWidget هو “ماذا عرض”، State هو “كيف عرض وما البيانات المستخدمة”.
وفقًا لتحليل الكود المصدري لـ Flutter (Flutter SDK, 2026)، يحتوي StatefulWidget على حقل إلزامي واحد فقط — createState، بينما State لديه إمكانية الوصول إلى BuildContext، ويمكنه الاشتراك في التدفقات، وإدارة الحركات والمتحكمات. يُنصح بإبقاء StatefulWidget بسيطًا قدر الإمكان، ونقل كل المنطق إلى State.
فصل Widget و State هو قرار معماري يضمن عدم قابلية تغيير التكوين. إذا كان StatefulWidget نفسه يخزن الحالة، فستفقد الحالة في كل إعادة بناء للأصل. عن طريق نقل الحالة إلى كائن منفصل، يضمن Flutter أن البيانات تبقى بعد إعادة البناء، بينما تظل عناصر واجهة المستخدم خفيفة وقابلة للمقارنة.
كائن State معزول — لا يمكنه الوصول مباشرة إلى State لعناصر واجهة المستخدم الأخرى. لتبادل البيانات بين عناصر واجهة المستخدم، يتم استخدام InheritedWidget أو أدوات إدارة الحالة الخارجية: Provider و Riverpod و Bloc و Redux. كل نهج يحل المشكلة بطريقته: InheritedWidget يعمل عبر شجرة عناصر واجهة المستخدم، Provider عبر حاوية DI، Bloc عبر تدفقات الأحداث.
يعتمد اختيار الأداة على حجم المشروع. لتطبيق صغير، يكفي InheritedWidget و State المحلي. للمشاريع المتوسطة والكبيرة، يُوصى باستخدام Riverpod أو Bloc — فهما يضمنان قابلية الاختبار، والقدرة على التنبؤ، وفصل المنطق عن واجهة المستخدم. يُستخدم State بعد ذلك فقط للبيانات المحلية لعنصر واجهة المستخدم (التركيز، التمرير، الحركة).
وفقًا لاستطلاع مجتمع Flutter 2025 (Flutter Foundation, ديسمبر 2025)، Riverpod هو الحل الأكثر شعبية لإدارة الحالة في المشاريع الجديدة (38%)، يليه Bloc (31%) و Provider (22%). جميع الأدوات الثلاث متوافقة مع State ولا تتطلب التخلي عن دورة الحياة القياسية.
الخطأ الأول هو نسيان التحقق من mounted قبل setState في رد اتصال غير متزامن. عندما تتم إزالة عنصر واجهة المستخدم من الشجرة (على سبيل المثال، انتقل المستخدم بعيدًا)، ولكن العملية غير المتزامنة (طلب HTTP) لا تزال قيد التنفيذ، بعد اكتمالها يكون State ميتًا بالفعل. استدعاء setState في State ميت يُلقي استثناءً. التحقق if (mounted) setState(...) يحل المشكلة.
الخطأ الثاني هو تهيئة تبعيات InheritedWidget في initState بدلاً من didChangeDependencies. في initState، السياق لم يُركب بعد، لذا فإن MediaQuery.of(context) سيُلقي استثناءً. يجب إعداد جميع تبعيات InheritedWidget في didChangeDependencies أو في build.
الخطأ الثالث هو تغيير الحقول دون استدعاء setState. إذا قام المطور بتغيير حقل State بدون setState، فلن يعلم Flutter بالتغيير ولن يتم تحديث واجهة المستخدم. على سبيل المثال: _list.add(item) دون setState((){}) لاحق سيعدل القائمة، لكن الشاشة ستبقى كما هي.
نمط أمان للعمليات غير المتزامنة في State:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
التحقق من mounted يضمن أن setState يُستدعى فقط لـ State حي، مما يمنع استثناء “setState called after dispose”.
الأسئلة الشائعة
StatefulWidget هو تكوين عنصر واجهة مستخدم غير قابل للتغيير، بينما State هو كائن قابل للتغيير يخزن البيانات ويدير دورة الحياة. يمكن إعادة إنشاء عنصر واجهة المستخدم، لكن State لا يمكن. StatefulWidget ينشئ State عبر createState.
بالضبط واحد. يتم استدعاء طريقة createState مرة واحدة عند إدراج StatefulWidget في الشجرة لأول مرة. حتى إذا أعاد الأصل البناء عدة مرات، يظل كائن State كما هو حتى يتغير نوع عنصر واجهة المستخدم أو المفتاح.
mounted هو علامة منطقية توضح ما إذا كان State موجودًا في شجرة عناصر واجهة المستخدم. بعد استدعاء dispose، يصبح mounted false. يُستخدم للتحقق قبل setState في ردود الاتصال غير المتزامنة لتجنب الاستثناءات.
لا. State دائمًا مرتبط بـ StatefulWidget معين عبر الأدوية: State<T extends StatefulWidget>. إنشاء State مباشرة، دون ارتباط بعنصر واجهة مستخدم، مستحيل معماريًا.
يُلقى استثناء: “setState called after dispose”. بعد dispose، يعتبر State ميتًا، وأي محاولات لإعادة بناء واجهة المستخدم عبر setState محظورة. الحل هو التحقق من mounted قبل كل setState.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.