// هذا الكلاس يمثل "الحالة الأساسية" (Base State)
// هذا الكلاس يمثل "الحالة الأساسية" (Base State)
// في Cubit نحن دائمًا نحتاج حالة (State) نحتفظ فيها بالبيانات
// هنا البيانات الوحيدة هي قيمة العداد (counter)
class CounterState {
// متغير نهائي (final) يعني لا يمكن تغييره بعد الإنشاء
// هذا مهم لأن الحالات في Cubit يجب أن تكون immutable (غير قابلة للتغيير)
final int counter;
// Constructor (الباني)
// يستقبل قيمة العداد ويخزنها داخل المتغير
// نستخدم هذا الكلاس لتمثيل أي حالة فيها رقم فقط
CounterState(this.counter);
}
// هذا كلاس يمثل حالة "الزيادة"
// يرث (extends) من CounterState
// يعني هو نفس الحالة الأساسية لكن مع دلالة إضافية (أنها زيادة)
class IncrementCounter extends CounterState {
// نستخدم super لتمرير القيمة إلى الكلاس الأب (CounterState)
// يعني هذا الكلاس لا يملك متغيرات جديدة
// فقط يحدد نوع الحالة (increment)
IncrementCounter(super.counter);
}
// هذا كلاس يمثل حالة "النقصان"
// أيضًا يرث من CounterState
class DecrementCounter extends CounterState {
// نفس الفكرة هنا
// نمرر القيمة إلى الكلاس الأب
// الفرق فقط في نوع الحالة (decrement)
DecrementCounter(super.counter);
}
JavaScript🔍 شرح الفكرة بالكامل (ببساطة جدًا)
1. ما هو الـ State هنا؟
الـ State هو الشكل الحالي للبيانات.
في مثالنا:
- الحالة = رقم العداد (counter)
2. لماذا أنشأنا أكثر من كلاس؟
لدينا:
CounterState→ الحالة العامةIncrementCounter→ حالة خاصة (زيادة)DecrementCounter→ حالة خاصة (نقصان)
💡 الهدف:
تمييز نوع التغيير وليس فقط القيمة
3. لماذا نستخدم extends؟
لأن:
- كل الحالات تشترك في نفس البيانات (
counter) - لكن تختلف في المعنى (نوع الحدث)
👉 لذلك نعيد استخدام الكود بدل التكرار
4. لماذا نستخدم final؟
final int counter;
لأن:
- في BLoC/Cubit يجب أن تكون الحالة غير قابلة للتعديل
- بدل تغيير القيمة، ننشئ حالة جديدة
❌ خطأ:
state.counter++;
✅ صحيح:
emit(CounterState(state.counter + 1));
5. ما فائدة وجود IncrementCounter و DecrementCounter؟
تُستخدم في الـ UI مثل:
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
if (state is IncrementCounter) {
return Text("تمت الزيادة: ${state.counter}");
} else if (state is DecrementCounter) {
return Text("تم النقصان: ${state.counter}");
}
return Text("${state.counter}");
},
);
💡 يعني نقدر نعرف:
- هل المستخدم ضغط زر زيادة؟
- أو زر نقصان؟
6. هل هذا التصميم ضروري؟
👉 ليس دائمًا
يمكنك الاكتفاء بـ:
class CounterState {
final int counter;
CounterState(this.counter);
}
لكن استخدام حالات متعددة مفيد عندما:
- تريد إظهار رسائل مختلفة
- أو تنفيذ منطق مختلف حسب الحالة
🧠 الخلاصة
CounterState= الحالة الأساسية (البيانات)IncrementCounter= حالة خاصة (زيادة)DecrementCounter= حالة خاصة (نقصان)extends= لإعادة استخدام الكودfinal= لضمان عدم تعديل الحالة- Cubit يعمل عن طريق إنشاء حالة جديدة كل مرة
🔹 ما هو الـ Constructor (الكونستركتور)؟
الـ Constructor هو:
دالة (function) خاصة تُستخدم لإنشاء كائن (object) من الكلاس.
📌 بمعنى:
عندما تكتب:
CounterState(5);
أنت هنا تستدعي الكونستركتور لإنشاء كائن جديد من الكلاس CounterState وقيمته = 5
🔹 شكله في الكود
class CounterState {
final int counter; // هذا هو الكونستركتور
CounterState(this.counter);
}
🔍 شرح السطر المهم
CounterState(this.counter);
هذا يعني:
- عند إنشاء كائن، أعطني قيمة
- وخزّنها داخل
counter
🔹 كيف يُستخدم؟
var state = CounterState(10);
📌 ماذا يحدث هنا؟
- يتم استدعاء الكونستركتور
- يتم إنشاء كائن جديد
- يتم وضع القيمة
10داخلcounter
🔹 لماذا نحتاج Constructor؟
1. لتهيئة البيانات عند إنشاء الكائن
بدونه لن تستطيع إدخال قيمة بسهولة
❌ بدون Constructor:
class CounterState {
int counter = 0;
}
كل القيم ستكون 0 دائمًا 😅
✅ مع Constructor:
CounterState(5);
CounterState(10);
CounterState(100);
صار عندك مرونة 💪
2. لتنظيم الكود
بدل:
var state = CounterState();
state.counter = 5;
نكتب:
var state = CounterState(5);
أنظف وأسهل 👌
3. مهم جدًا في Cubit / BLoC
لأننا كل مرة ننشئ State جديدة
emit(CounterState(state.counter + 1));
بدون constructor لن تستطيع تمرير القيمة الجديدة
🔹 أنواع الكونستركتور (بشكل سريع)
1. Default Constructor
CounterState(this.counter);
2. Constructor عادي
CounterState(int value) {
counter = value;
}
3. Constructor بدون باراميتر
CounterState() {
counter = 0;
}
🔥 مثال حياتي بسيط
تخيل الكلاس = “قالب سيارة” 🚗
والـ constructor = “طريقة إنشاء السيارة”
class Car {
String color; Car(this.color);
}
الاستخدام:
Car redCar = Car("أحمر");
Car blueCar = Car("أزرق");
👉 كل سيارة لها لون مختلف عند الإنشاء
🧠 الخلاصة
- الكونستركتور هو دالة تُستخدم لإنشاء كائن
- يُستدعى تلقائيًا عند إنشاء الكائن
- يُستخدم لتمرير القيم الأولية
- مهم جدًا في Flutter و Cubit لإنشاء حالات جديدة
تمام 🔥 هذا من أهم الفروقات التي لازم تفهمها كمبرمج Flutter / Dart 👌
خلّيني أشرحها لك بشكل عميق + بسيط + مع أمثلة واضحة
🔹 أولًا: الفرق الأساسي باختصار
| الطريقة | ماذا تعني |
|---|---|
this.counter | اختصار مباشر لتعريف المتغير وتمريره |
counter = value | تعيين قيمة داخل جسم الكونستركتور |
🔹 الحالة الأولى: this.counter
class CounterState {
final int counter; CounterState(this.counter);
}
🔍 ماذا يحدث هنا؟
- Dart يفهمها كالتالي:
CounterState(int counter) {
this.counter = counter;
}
💡 يعني:
- أنت تستقبل قيمة
- وتخزنها مباشرة في المتغير
🔥 لماذا نستخدمها؟
✔ اختصار
✔ كود نظيف
✔ أقل أخطاء
🔹 الحالة الثانية: counter = value
class CounterState {
int counter; CounterState(int value) {
counter = value;
}
}
🔍 ماذا يحدث هنا؟
- تستقبل
value - ثم تقوم بتعيينه يدويًا داخل الكونستركتور
⚠️ مشكلة مهمة جدًا (لازم تنتبه لها)
class CounterState {
int counter; CounterState(int counter) {
counter = counter; // ❌ خطأ منطقي
}
}
💥 هنا المشكلة:
counter(الباراميتر) غطّى علىcounter(المتغير داخل الكلاس)- وبالتالي لم يتم حفظ القيمة!
✅ الحل باستخدام this
CounterState(int counter) {
this.counter = counter;
}
💡 هنا:
this.counter= متغير الكلاسcounter= الباراميتر
🔥 لماذا this.counter أفضل؟
1. يتجنب الأخطاء
بدل ما تنسى this
2. كود مختصر جدًا
بدل:
CounterState(int counter) {
this.counter = counter;
}
نكتب:
CounterState(this.counter);
3. مستخدم بكثرة في Flutter
مثال حقيقي في Cubit:
emit(CounterState(state.counter + 1));
🔹 متى نستخدم الطريقة الثانية؟
نستخدمها إذا عندنا منطق إضافي 👇
class CounterState {
int counter; CounterState(int value) {
if (value < 0) {
counter = 0;
} else {
counter = value;
}
}
}
💡 هنا لا يمكن استخدام this.counter مباشرة لأننا نريد شرط
🔥 مقارنة نهائية
✅ هذا الأفضل (في أغلب الحالات)
CounterState(this.counter);
✅ هذا عند الحاجة لمنطق
CounterState(int value) {
counter = value;
}
🧠 الخلاصة المهمة جدًا
this.counter= اختصار ذكي وآمنcounter = value= تعيين يدوي (مفيد عند وجود شروط)- المشكلة الشائعة: نسيان
thisيؤدي لخطأ خطير
🚀 نصيحة احترافية
في Flutter + Cubit:
استخدم دائمًا
this.variableإلا إذا كنت تحتاج منطق داخل الكونستركتور
إذا حاب 👇
أشرح لك كيف Dart يميز بين المتغيرات داخل الكلاس والباراميتر (Scope) بشكل بصري وسهل جدًا 👍
import ‘package:counter_cubit/features/counter/cubit/counter_state.dart’; import ‘package:flutter_bloc/flutter_bloc.dart’; class CounterCubit extends Cubit<CounterState> { CounterCubit() : super(CounterState(0)); increment() { emit(IncrementCounter(state.counter + 1)); } decrement() { emit(DecrementCounter(state.counter – 1)); } } ايضا هنا اشرح بالتفصيل للكود بالتعليقات ومن ثم اشرح كامل الخطوات وايضا علاقتها مع الملف السابق
تمام 👌 الآن دخلنا على قلب Cubit الحقيقي 🔥
سأشرح لك الكود سطر سطر بالتعليقات ثم أربطه مع ملف state السابق بشكل كامل.
🧾 أولًا: الكود مع شرح تفصيلي داخل التعليقات
Writing
// استيراد ملف الحالات (State)
// هنا نستخدم الكلاسات التي أنشأناها:
// CounterState, IncrementCounter, DecrementCounter
import 'package:counter_cubit/features/counter/cubit/counter_state.dart';
// استيراد مكتبة flutter_bloc
// التي تحتوي على Cubit و BlocBuilder وغيرها
import 'package:flutter_bloc/flutter_bloc.dart';
// هذا الكلاس يمثل "Cubit"
// وهو المسؤول عن إدارة الحالة (State)
class CounterCubit extends Cubit<CounterState> {
// Constructor الخاص بالـ Cubit
// super() هنا يعني استدعاء الكونستركتور للكلاس الأب (Cubit)
// ونمرر له الحالة الابتدائية (initial state)
// هنا نبدأ بقيمة 0
CounterCubit() : super(CounterState(0));
// دالة لزيادة العداد
increment() {
// emit = إرسال حالة جديدة
// state = الحالة الحالية
// state.counter = القيمة الحالية
// ننشئ حالة جديدة من نوع IncrementCounter
// ونزيد القيمة +1
emit(IncrementCounter(state.counter + 1));
}
// دالة لإنقاص العداد
decrement() {
// نفس الفكرة لكن ننقص 1
emit(DecrementCounter(state.counter - 1));
}
}
🔍 الآن نشرح الفكرة بالكامل (ببساطة + عمق)
🔹 ما هو Cubit؟
الـ Cubit هو:
كلاس مسؤول عن إدارة الحالة وتغييرها
💡 يعني:
- يستقبل أحداث (مثل ضغط زر)
- ويُصدر (emit) حالة جديدة
🔹 هذا السطر مهم جدًا
class CounterCubit extends Cubit<CounterState>
📌 ماذا يعني؟
- نحن ننشئ Cubit
- نوع الحالة التي يديرها =
CounterState
👉 يعني:
هذا Cubit سيتعامل فقط مع الحالات التي أنشأناها سابقًا
🔹 الحالة الابتدائية
CounterCubit() : super(CounterState(0));
📌 ماذا يحدث هنا؟
- يتم إنشاء Cubit
- يتم إعطاؤه أول حالة
- هذه الحالة =
counter = 0
💡 بدون هذه الخطوة سيحدث خطأ ❌
🔹 ما هو state؟
state.counter
📌 هذا يمثل:
الحالة الحالية داخل Cubit
🔹 ما هو emit؟
emit(...)
📌 هذا أهم شيء في Cubit:
emit = إرسال حالة جديدة
💡 عند استدعائه:
- يتم تحديث الحالة
- يتم إعادة بناء UI تلقائيًا (BlocBuilder)
🔁 العلاقة مع ملف الـ State (المهم جدًا)
🧩 تذكير بالكلاسات:
CounterState
IncrementCounter
DecrementCounter
🔗 كيف يرتبطون مع Cubit؟
عند الضغط على زر ➕
increment()
يحدث:
emit(IncrementCounter(state.counter + 1));
👉 يعني:
- نأخذ القيمة الحالية
- نزيدها
- ننشئ حالة جديدة
- نرسلها
عند الضغط على زر ➖
decrement()
👉 نفس الشيء لكن نقص
🎯 لماذا استخدمنا IncrementCounter بدل CounterState؟
💡 حتى نميز نوع العملية
في UI:
if (state is IncrementCounter) {
// حدثت زيادة
}
🔄 التسلسل الكامل (من الضغط إلى الشاشة)
🧠 تخيل هذا السيناريو:
1. المستخدم يضغط زر ➕
↓
2. يتم استدعاء:
context.read<CounterCubit>().increment();
↓
3. Cubit ينفذ:
emit(IncrementCounter(state.counter + 1));
↓
4. يتم إرسال حالة جديدة
↓
5. BlocBuilder يستقبل التغيير
↓
6. يتم تحديث UI
🔥 مثال UI بسيط يربط كل شيء
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text("${state.counter}");
},
);
🧠 الخلاصة النهائية
- Cubit = مدير الحالة
- State = البيانات
- emit = إرسال تحديث
- كل مرة نغير القيمة → ننشئ State جديدة
- الربط بين Cubit و State هو أساس BLoC
🚀 نصيحة احترافية
التصميم الذي استخدمته ممتاز 👌
لكن في المشاريع الكبيرة:
- نضيف حالات مثل:
- Loading
- Error
- Success