تخطَّ إلى المحتوى

StatelessWidget و StatefulWidget في Flutter

الدرس 4 من 33 · Flutter

حتى الآن كل شاشاتك ثابتة: تعرض نفس الشيء دائماً. لكن التطبيقات الحقيقية تتغير: عدد الإعجابات يزيد، والزر يتحول لونه عند الضغط، والسلة تمتلئ بالمنتجات. في هذا الدرس ستتعلم أهم مفهوم في Flutter على الإطلاق: الحالة (State)، والفرق بين نوعي الـ Widgets.

الحالة هي أي بيانات يمكن أن تتغير أثناء تشغيل التطبيق، ويجب أن تنعكس على الشاشة عندما تتغير. عدد الضغطات، وهل الزر مضغوط، والنص الذي كتبه المستخدم.

تخيّل لوحة النتائج في مباراة كرة قدم: الإطار واسم الفريقين ثابتان، لكن الأرقام حالة تتغير مع كل هدف، وكلما تغيرت يجب أن تتحدث اللوحة فوراً.

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 مثل لافتة مطبوعة: تعرض ما كُتب عليها عند طباعتها، ولا تتغير.

عندما يحتاج الـ 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(() {
_count++;
});

setState تقول لـ Flutter شيئين: «غيّر هذه البيانات، ثم أعد بناء الشاشة». فيستدعي Flutter الدالة build من جديد، وتظهر القيمة الجديدة.

ماذا لو كتبت _count++ بدون setState؟ القيمة ستتغير فعلاً في الذاكرة، لكن الشاشة لن تعرف، فيبقى الرقم القديم ظاهراً. كأن الحكم سجل الهدف في دفتره، لكن لم يخبر عامل لوحة النتائج!

لأن 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.

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;
});
},
);
}
}

قلب رمادي فارغ، يتحول إلى قلب أحمر ممتلئ عند الضغط، ويعود عند الضغط مرة أخرى. لاحظ استخدام المعامل الثلاثي لاختيار الأيقونة واللون حسب الحالة.

اسأل نفسك سؤالاً واحداً: هل يحتاج هذا الجزء أن يتغير بنفسه بعد ظهوره؟

  • لا ← StatelessWidget: عناوين، وبطاقات معلومات، وأيقونات ثابتة.
  • نعم ← StatefulWidget: عدّاد، وزر إعجاب، ونموذج إدخال، ومؤقت.

ابدأ دائماً بـ Stateless، ولا تحوّل إلى Stateful إلا عند الحاجة. والمحرر يحوّل بينهما بضغطة واحدة من قائمة الإصلاحات السريعة.

  1. شغّل العدّاد وتأكد أنه يعمل.
  2. أضف زراً ثانياً لـ الإنقاص، بشرط ألا ينزل العدد تحت صفر.
  3. اجعل لون الرقم أحمر إذا تجاوز 5 (تلميح: color: _count > 5 ? Colors.red : Colors.black).
  4. احذف setState من دالة الزيادة مؤقتاً، واضغط الزر، ولاحظ أن الرقم لا يتغير على الشاشة. ثم أعدها.
  • تغيير البيانات بدون setState: البيانات تتغير لكن الشاشة لا تتحدث.
  • وضع المتغيرات داخل build: int count = 0; داخل build يعيد العدّاد صفراً مع كل إعادة بناء! ضع الحالة كخاصية في كلاس الـ State.
  • استخدام Stateful لكل شيء: يجعل الكود أطول وأصعب دون فائدة. الحالة فقط حيث تحتاجها.

سؤال

ماذا يحدث إذا غيّرت متغيراً في الـ State بدون استدعاء setState؟

سؤال

أي Widget يناسب بطاقة تعرض اسم كورس ووصفه الثابت؟

  • الحالة بيانات تتغير أثناء التشغيل ويجب أن تظهر على الشاشة.
  • StatelessWidget ثابت يعرض ما يصله من المُنشئ.
  • StatefulWidget كلاسان: الـ Widget، وكلاس State الذي يحمل البيانات و build.
  • setState تغيّر البيانات وتعيد بناء الشاشة، وبدونها لا يظهر التغيير.
  • ابدأ بـ Stateless، واستخدم Stateful فقط عندما يتغير الجزء بنفسه.