أنت عندك ملفين أساسيين في BLoC (Cubit):
counter_state.dartcounter_cubit.dart
🔹 أولًا: ملف counter_state.dart
class CounterState {
final int counter;
CounterState(this.counter);
}class IncrementCounter extends CounterState {
IncrementCounter(super.counter);
}class DecrementCounter extends CounterState {
DecrementCounter(super.counter);
}
✅ وظيفة هذا الملف
هذا الملف مسؤول عن تمثيل الحالة (State) للتطبيق.
يعني:
- ما هو الوضع الحالي للعداد؟
- كم قيمته الآن؟
🔍 شرح الكود بالتفصيل
🔸 class CounterState
class CounterState {
final int counter;
CounterState(this.counter);
}
counter: يمثل قيمة العداد الحاليةfinal: يعني القيمة لا تتغير بعد إنشائها (immutable)- الكونستركتور: CounterState(this.counter); يعني لما تنشئ الحالة، لازم تمرر قيمة العداد
📌 مثال:
CounterState(5);
🔸 class IncrementCounter
class IncrementCounter extends CounterState {
IncrementCounter(super.counter);
}
- يرث من
CounterState - يستخدم لتمييز أن الحالة جاءت من عملية زيادة
📌 لكن هنا ملاحظة مهمة:
أنت ما تستخدمه فعليًا داخل Cubit، لذلك وجوده حاليًا غير ضروري.
🔸 class DecrementCounter
class DecrementCounter extends CounterState {
DecrementCounter(super.counter);
}
- نفس الفكرة، لكن للنقصان
📌 أيضًا غير مستخدم حاليًا.
🧠 الخلاصة لملف state
- يحتوي على “شكل البيانات” فقط
- لا يحتوي منطق (logic)
- هو مجرد حامل للقيمة
🔹 ثانيًا: ملف counter_cubit.dart
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(CounterState(state.counter + 1));
} decrement() {
emit(CounterState(state.counter - 1));
}
}
✅ وظيفة هذا الملف
هذا الملف مسؤول عن:
👉 إدارة الحالة (State Management)
👉 تغيير قيمة العداد وإرسال الحالة الجديدة
🔍 شرح الكود بالتفصيل
🔸 class CounterCubit
class CounterCubit extends Cubit<CounterState>
- أنت تنشئ Cubit خاص بالعداد
<CounterState>يعني أن هذا Cubit يتحكم في نوع الحالة
🔸 constructor
CounterCubit() : super(CounterState(0));
- أول حالة للتطبيق = 0
super(...)يعني تمرير الحالة الابتدائية
📌 يعني أول ما التطبيق يشتغل:
counter = 0
🔸 increment()
increment() {
emit(CounterState(state.counter + 1));
}
state: الحالة الحاليةstate.counter: قيمة العداد الحالية
📌 الخطوات:
- يأخذ القيمة الحالية
- يزيدها 1
- يرسل حالة جديدة باستخدام
emit
🔸 decrement()
decrement() {
emit(CounterState(state.counter - 1));
}
نفس الفكرة لكن ينقص 1
🔗 كيف يتم الربط بين الملفين؟
🧩 العلاقة
1. CounterCubit يعتمد على CounterState
extends Cubit<CounterState>
يعني:
- كل مرة يتغير العداد → يتم إرسال
CounterState
2. عند التغيير
emit(CounterState(state.counter + 1));
- Cubit ينشئ State جديدة
- ويرسلها للواجهة (UI)
3. في الواجهة (UI)
عادة تستخدم:
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text("${state.counter}");
},
);
📌 هنا:
state.counterيأتي منCounterState- كل ما
emitيصير → UI يتحدث تلقائيًا
⚠️ ملاحظة مهمة جدًا
أنت عرّفت:
IncrementCounter
DecrementCounter
لكن لم تستخدمهم داخل Cubit.
🔧 الأفضل إما:
✔️ تستخدمهم:
emit(IncrementCounter(state.counter + 1));
❌ أو تحذفهم (الأبسط):
وتبقي فقط:
CounterState
🧠 الفكرة العامة (ببساطة جدًا)
State= البيانات (كم قيمة العداد؟)Cubit= العقل (يزيد؟ ينقص؟)emit= إرسال الحالة الجديدةUI= يعرض القيمة ويتحدث تلقائيًا
إذا تريد، أشرح لك:
✔ كيف تربطهم مع BlocProvider
✔ أو أبني لك مثال UI كامل خطوة خطوة
اشرح كاهو عمل emit تحديدا منطقيا ومن خلال الكود لنعرف كل شي هعنها
تمام 👌 خلينا نركز على emit تحديدًا، ونفهمه بعمق من الناحية المنطقية + من داخل الكود.
🔥 أولًا: ما هو emit؟
ببساطة:
👉 emit = إرسال حالة (State) جديدة
يعني:
“يا Cubit، غيّر الحالة الحالية وأخبر كل من يستمع (UI) بهذا التغيير”
🧠 الفكرة المنطقية (بدون كود)
تخيل عندك:
- عداد = 0
- المستخدم ضغط زر ➕
الذي يحدث:
- Cubit يعرف أن هناك حدث (زيادة)
- يحسب القيمة الجديدة (1)
- يستخدم
emitلإرسال الحالة الجديدة - كل الواجهات (UI) التي تستمع تتحدث تلقائيًا
🔍 كيف يعمل emit داخليًا؟
داخل flutter_bloc، الـ Cubit يعتمد على Stream
👉 يعني:
- الحالة يتم إرسالها عبر Stream
- الواجهة تستمع لهذا الـ Stream
📦 الفكرة بشكل مبسط جدًا
Cubit ---> emit(state) ---> Stream ---> UI (BlocBuilder)
🔧 نشرحها من كودك
🔹 قبل الضغط
state.counter = 0
🔹 لما تنادي increment()
increment() {
emit(CounterState(state.counter + 1));
}
خلينا نفككها:
1️⃣ قراءة الحالة الحالية
state.counter
👉 القيمة الحالية = 0
2️⃣ إنشاء حالة جديدة
CounterState(0 + 1)
👉 تصبح:
CounterState(1)
⚠️ مهم:
نحن لا نغير الحالة القديمة
نحن ننشىء حالة جديدة بالكامل
3️⃣ إرسال الحالة
emit(CounterState(1));
هنا يحدث السحر ✨
⚙️ ماذا يفعل emit فعليًا؟
داخليًا (تبسيط):
void emit(CounterState newState) {
currentState = newState;
stream.add(newState);
}
🔥 ماذا يعني ذلك؟
✔️ 1. تحديث الحالة
currentState = newState;
👉 state الآن = 1
✔️ 2. إرسالها عبر Stream
stream.add(newState);
👉 كل من يستمع (BlocBuilder) يستقبلها
🎯 ماذا يحدث في UI؟
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text("${state.counter}");
},
);
عند وصول emit
- BlocBuilder يسمع التغيير
- يعيد تنفيذ
builder - يأخذ
state.counter = 1 - يعرض القيمة الجديدة
🔁 دورة كاملة (Step by Step)
🟢 البداية
state = CounterState(0)
🔘 ضغط زر +
increment() ينادي emit
🧠 الحساب
0 + 1 = 1
🚀 emit
emit(CounterState(1))
📡 إرسال عبر Stream
state الجديدة تنتشر
🎨 UI يتحدث
Text = "1"
⚠️ نقطة مهمة جدًا
❌ هذا خطأ:
state.counter++;
👉 لأن state immutable
✅ الصحيح:
emit(CounterState(state.counter + 1));
👉 ننشئ State جديدة
🧠 لماذا emit مهم؟
لأنه:
✔ يضمن أن كل التغييرات:
- منظمة
- يمكن تتبعها
- تصل للواجهة تلقائيًا
✔ يفصل:
- المنطق (Cubit)
- عن العرض (UI)
🔥 مثال تشبيهي بسيط
تخيل:
- Cubit = محطة بث 📡
- emit = إرسال إشارة
- UI = أجهزة استقبال 📺
كل مرة تعمل emit:
👉 كأنك تبث قناة جديدة
👉 كل الشاشات تتحدث فورًا
💡 خلاصة قوية
emit هو:
👉 قلب الـ Cubit
👉 المسؤول عن:
- تحديث الحالة
- إرسالها
- إعادة بناء الواجهة