ما هي StatefulWidget؟
- هي ويدجت قابلة لتغيير مظهرها أثناء التشغيل لأن لها كائن حالة State منفصل يخزّن القيم المتغيرة (عداد، نص، أنيميشن… إلخ).
- الودجت نفسها غير قابلة للتغيير (immutable)، لكن الحالة هي التي تتغير وتعيد بناء الواجهة عبر
setState.
كيف تتم الوراثة (التكوين البنيوي)
أنت تبني فصلين مرتبطين ببعض:
- الودجت:
class MyWidget extends StatefulWidget {
const MyWidget({super.key, required this.title});
final String title;
@override
State<MyWidget> createState() => _MyWidgetState();
}
Dart- يرث من
StatefulWidget. - يحتفظ بالخصائص الثابتة (final) التي لا تتغير أثناء حياة الودجت.
- ينشئ كائن الحالة عبر
createState().
- الحالة:
class _MyWidgetState extends State<MyWidget> {
int counter = 0; // بيانات متغيرة
@override
Widget build(BuildContext context) {
return Text('${widget.title}: $counter');
}
}
Dart- يرث من
State<MyWidget>(لاحظ الـ Generic لربط الحالة بالودجت). - للوصول لخصائص الودجت استخدم
widget.title. - عند تغيير أي متغير يؤثر على الواجهة، استدعِ
setState(() { ... }).
الخلاصة البنيوية:
MyWidget extends StatefulWidget+class _MyWidgetState extends State<MyWidget>،
والربط بينهما يحصل عبر الـ جنيرك وcreateState().
دورة الحياة (متى أكتب إيه؟)
داخل كلاس الـ State:
initState()→ أول مرة يُنشأ فيها الـ State.
ضع فيه: تهيئة متحكمات (Animation/TextEditingController)، بدء اشتراكات/تايمرات خفيفة.didChangeDependencies()→ يُستدعى بعدinitStateوعند تغيّر الـ InheritedWidgets (مثل Locale/Theme/MediaQuery).build()→ يرسم الواجهة بناءً على الحالة الحالية. اجعله سريعًا وخاليًا من العمل الثقيل.didUpdateWidget(oldWidget)→ إذا تبدّلت خصائص الودجت الأب (تغيّرwidget.titleمثلًا).setState(fn)→ أبلغ Flutter أن الحالة تغيّرت وأعد بناءbuild.deactivate()→ قبل إزالة الودجت مؤقتًا من الشجرة.dispose()→ عند إزالة الودجت نهائيًا.
مهم: أغلِ الاشتراكات وcontroller.dispose()هنا.
في وضع التطوير هناك أيضًا
reassemble()عند hot reload.
أمثلة واضحة
1) عداد بسيط (الأساس)
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('القيمة: $counter', style: const TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: const Text('زيادة'),
),
],
);
}
}
Dart2) استخدام Controller + التخلص الصحيح (dispose)
class NameField extends StatefulWidget {
const NameField({super.key});
@override
State<NameField> createState() => _NameFieldState();
}
class _NameFieldState extends State<NameField> {
final controller = TextEditingController();
@override
void initState() {
super.initState();
controller.text = 'الاسم الابتدائي';
}
@override
void dispose() {
controller.dispose(); // مهم جدًا
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(controller: controller);
}
}
Dart3) أنيميشن سريع (Mixin مناسب)
class PulseDot extends StatefulWidget {
const PulseDot({super.key});
@override
State<PulseDot> createState() => _PulseDotState();
}
class _PulseDotState extends State<PulseDot> with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
@override
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 1))
..repeat(reverse: true);
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: Tween(begin: 0.8, end: 1.2).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut)),
child: const CircleAvatar(radius: 10),
);
}
}
Dart4) طلب بيانات غير متزامن + فحص mounted
class AsyncLoader extends StatefulWidget {
const AsyncLoader({super.key});
@override
State<AsyncLoader> createState() => _AsyncLoaderState();
}
class _AsyncLoaderState extends State<AsyncLoader> {
String? data;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return; // تجنب setState بعد إزالة الودجت
setState(() => data = 'تم التحميل ✅');
}
@override
Widget build(BuildContext context) {
return Text(data ?? 'جار التحميل...');
}
}
Dartمتى أستخدم StatefulWidget؟
- عناصر تتغير حالتها محليًا: عدادات، مفاتيح تشغيل/إيقاف، تبويبات مع محتوى متغير.
- فورم مع Controllers وFocusNodes.
- أنيميشن، مؤقتات، استماع إلى Streams/ChangeNotifiers.
- واجهات تعتمد على استجابة شبكة مع تخزين حالة التحميل/الخطأ محليًا.
لو الحالة عابرة للشاشة أو مشتركة بين عدّة شاشات، فكّر في إدارة حالة أعلى (Provider / Riverpod / BLoC) بدل الاعتماد على
setStateوحدها.
نصائح ذهبية (Best Practices)
- اجعل
build()خفيفًا وسريعًا: لا شبكات/قراءات ملفات/منطق ثقيل داخلbuild. - استخدم
initStateلبدء أي عمل مرة واحدة (تهيئة Controllers، بدء تحميل). - دائمًا
dispose()لأي Controller/Stream/Timer لتفادي التسريبات. - لا تستدعِ
setStateبعدdispose: تحقق منmountedقبل التحديث بعد عمليات async. - قلّل نطاق إعادة البناء: قسّم الواجهة إلى ويدجتات أصغر، أو استخدم
ValueListenableBuilder/AnimatedBuilderلتحديث جزء محدد. - استخدم
constحيثما أمكن لتقليل إعادة البناء. - عند تحريك ويدجت في الشجرة وتريد الاحتفاظ بالحالة، استخدم
Keyمناسب (مثلValueKey)، أوAutomaticKeepAliveClientMixinفي القوائم/التبويبات.
أخطاء شائعة
- كتابة منطق ثقيل داخل
build→ بطء ووميض. - نسيان
dispose()→ تسريب ذاكرة وسلوك غريب. - استدعاء
setStateداخلbuildأو بشكل متكرر بلا داعٍ. - الاعتماد على StatefulWidget لحالة تطبيق كاملة وكبيرة → صعوبة صيانة؛ استخدم إدارة حالة.
مقارنة سريعة مع StatelessWidget
- StatelessWidget: لا حالة داخلية—يبني مرة حسب المدخلات فقط.
- StatefulWidget: يحتوي حالة داخلية تتغير بمرور الوقت وتعيد بناء الواجهة عند الحاجة.
قوالب جاهزة (اختصارات VS Code)
- اكتب
stfulثم Tab ⇒ هيولّد لك قالب StatefulWidget كامل. - للوصول لخصائص الودجت داخل الـ State: استخدم
widget.someProp.
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int i = 0;
bool status = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("data")),
body: Center(
child: Column(
children: [
IconButton(
onPressed: () {
setState(() {
i++;
});
print(i);
},
icon: Icon(Icons.add),
),
Text("العداد $i"),
Switch(value: status, onChanged: (val){
setState(() {
status = !val;
print(val);
});
})
],
),
),
),
);
}
}Dart