StatefulWidget

ما هي StatefulWidget؟
  • هي ويدجت قابلة لتغيير مظهرها أثناء التشغيل لأن لها كائن حالة State منفصل يخزّن القيم المتغيرة (عداد، نص، أنيميشن… إلخ).
  • الودجت نفسها غير قابلة للتغيير (immutable)، لكن الحالة هي التي تتغير وتعيد بناء الواجهة عبر setState.
كيف تتم الوراثة (التكوين البنيوي)

أنت تبني فصلين مرتبطين ببعض:

  1. الودجت:
class MyWidget extends StatefulWidget {
  const MyWidget({super.key, required this.title});
  final String title;

  @override
  State<MyWidget> createState() => _MyWidgetState();
}
Dart
  • يرث من StatefulWidget.
  • يحتفظ بالخصائص الثابتة (final) التي لا تتغير أثناء حياة الودجت.
  • ينشئ كائن الحالة عبر createState().
  1. الحالة:
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('زيادة'),
        ),
      ],
    );
  }
}
Dart
2) استخدام 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);
  }
}
Dart
3) أنيميشن سريع (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),
    );
  }
}
Dart
4) طلب بيانات غير متزامن + فحص 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