StatelessWidget و StatefulWidget في Flutter
حتى الآن كل شاشاتك ثابتة: تعرض نفس الشيء دائماً. لكن التطبيقات الحقيقية تتغير: عدد الإعجابات يزيد، والزر يتحول لونه عند الضغط، والسلة تمتلئ بالمنتجات. في هذا الدرس ستتعلم أهم مفهوم في Flutter على الإطلاق: الحالة (State)، والفرق بين نوعي الـ Widgets.
ما هي الحالة؟
Section titled “ما هي الحالة؟”الحالة هي أي بيانات يمكن أن تتغير أثناء تشغيل التطبيق، ويجب أن تنعكس على الشاشة عندما تتغير. عدد الضغطات، وهل الزر مضغوط، والنص الذي كتبه المستخدم.
تخيّل لوحة النتائج في مباراة كرة قدم: الإطار واسم الفريقين ثابتان، لكن الأرقام حالة تتغير مع كل هدف، وكلما تغيرت يجب أن تتحدث اللوحة فوراً.
StatelessWidget: بلا حالة
Section titled “StatelessWidget: بلا حالة”Widget لا يتغير بعد بنائه. يعرض ما يُعطى له فقط:
class Greeting extends StatelessWidget { final String name;
const Greeting({super.key, required this.name});
@override Widget build(BuildContext context) { return Text('أهلاً يا $name'); }}واستخدامه: const Greeting(name: 'سارة'). لاحظ أن البيانات تصله من الخارج عبر المُنشئ، كما تعلمت في درس الكلاسات بكورس Dart، وأن خصائصه final. هذا الـ Widget مثل لافتة مطبوعة: تعرض ما كُتب عليها عند طباعتها، ولا تتغير.
StatefulWidget: له حالة
Section titled “StatefulWidget: له حالة”عندما يحتاج الـ Widget أن يتذكر شيئاً ويتغير، نستخدم StatefulWidget. وهو يتكون من كلاسين:
class CounterScreen extends StatefulWidget { const CounterScreen({super.key});
@override State<CounterScreen> createState() => _CounterScreenState();}
class _CounterScreenState extends State<CounterScreen> { int _count = 0;
void _increment() { setState(() { _count++; }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('عدّاد الضغطات')), body: Center( child: Text( 'عدد الضغطات: $_count', style: const TextStyle(fontSize: 28), ), ), floatingActionButton: FloatingActionButton( onPressed: _increment, child: const Icon(Icons.add), ), ); }}- الكلاس الأول
CounterScreen: الـ Widget نفسه، ودوره فقط أن ينشئ الحالة عبرcreateState. - الكلاس الثاني
_CounterScreenState: يحمل البيانات المتغيرة_count، ودالةbuild. والشرطة السفلية تجعله خاصاً بهذا الملف.
عند فتح الشاشة يظهر «عدد الضغطات: 0»، ومع كل ضغطة على الزر يزيد الرقم واحداً.
السحر كله في setState
Section titled “السحر كله في setState”setState(() { _count++;});setState تقول لـ Flutter شيئين: «غيّر هذه البيانات، ثم أعد بناء الشاشة». فيستدعي Flutter الدالة build من جديد، وتظهر القيمة الجديدة.
ماذا لو كتبت _count++ بدون setState؟ القيمة ستتغير فعلاً في الذاكرة، لكن الشاشة لن تعرف، فيبقى الرقم القديم ظاهراً. كأن الحكم سجل الهدف في دفتره، لكن لم يخبر عامل لوحة النتائج!
إضافات للعدّاد
Section titled “إضافات للعدّاد”لأن build تُعاد مع كل تغيير، تستطيع إظهار أشياء حسب الحالة:
children: [ const Greeting(name: 'سارة'), Text('عدد الضغطات: $_count', style: const TextStyle(fontSize: 28)), if (_count >= 10) const Text('رائع! وصلت إلى 10'), TextButton(onPressed: _reset, child: const Text('تصفير')),],ودالة التصفير:
void _reset() { setState(() { _count = 0; });}بعد 10 ضغطات تظهر رسالة «رائع! وصلت إلى 10»، وعند الضغط على «تصفير» يعود الرقم صفراً وتختفي الرسالة. لاحظ if داخل القائمة، وهي نفس «collection if» التي تعلمتها في درس القوائم بكورس Dart.
مثال آخر: زر الإعجاب
Section titled “مثال آخر: زر الإعجاب”class LikeButton extends StatefulWidget { const LikeButton({super.key});
@override State<LikeButton> createState() => _LikeButtonState();}
class _LikeButtonState extends State<LikeButton> { bool _liked = false;
@override Widget build(BuildContext context) { return IconButton( icon: Icon( _liked ? Icons.favorite : Icons.favorite_border, color: _liked ? Colors.red : Colors.grey, ), onPressed: () { setState(() { _liked = !_liked; }); }, ); }}قلب رمادي فارغ، يتحول إلى قلب أحمر ممتلئ عند الضغط، ويعود عند الضغط مرة أخرى. لاحظ استخدام المعامل الثلاثي لاختيار الأيقونة واللون حسب الحالة.
متى أستخدم أيهما؟
Section titled “متى أستخدم أيهما؟”اسأل نفسك سؤالاً واحداً: هل يحتاج هذا الجزء أن يتغير بنفسه بعد ظهوره؟
- لا ←
StatelessWidget: عناوين، وبطاقات معلومات، وأيقونات ثابتة. - نعم ←
StatefulWidget: عدّاد، وزر إعجاب، ونموذج إدخال، ومؤقت.
ابدأ دائماً بـ Stateless، ولا تحوّل إلى Stateful إلا عند الحاجة. والمحرر يحوّل بينهما بضغطة واحدة من قائمة الإصلاحات السريعة.
جرّب بنفسك
Section titled “جرّب بنفسك”- شغّل العدّاد وتأكد أنه يعمل.
- أضف زراً ثانياً لـ الإنقاص، بشرط ألا ينزل العدد تحت صفر.
- اجعل لون الرقم أحمر إذا تجاوز 5 (تلميح:
color: _count > 5 ? Colors.red : Colors.black). - احذف
setStateمن دالة الزيادة مؤقتاً، واضغط الزر، ولاحظ أن الرقم لا يتغير على الشاشة. ثم أعدها.
أخطاء شائعة
Section titled “أخطاء شائعة”- تغيير البيانات بدون setState: البيانات تتغير لكن الشاشة لا تتحدث.
- وضع المتغيرات داخل build:
int count = 0;داخلbuildيعيد العدّاد صفراً مع كل إعادة بناء! ضع الحالة كخاصية في كلاس الـ State. - استخدام Stateful لكل شيء: يجعل الكود أطول وأصعب دون فائدة. الحالة فقط حيث تحتاجها.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يحدث إذا غيّرت متغيراً في الـ State بدون استدعاء setState؟
setState هي التي تخبر Flutter بإعادة استدعاء build، وبدونها تبقى الشاشة القديمة.
سؤال
أي Widget يناسب بطاقة تعرض اسم كورس ووصفه الثابت؟
البطاقة لا تتغير بنفسها بعد ظهورها، فلا تحتاج حالة.
الخلاصة
Section titled “الخلاصة”- الحالة بيانات تتغير أثناء التشغيل ويجب أن تظهر على الشاشة.
StatelessWidgetثابت يعرض ما يصله من المُنشئ.StatefulWidgetكلاسان: الـ Widget، وكلاسStateالذي يحمل البيانات وbuild.setStateتغيّر البيانات وتعيد بناء الشاشة، وبدونها لا يظهر التغيير.- ابدأ بـ Stateless، واستخدم Stateful فقط عندما يتغير الجزء بنفسه.