Bloc Counter Project

أنت عندك ملفين أساسيين في BLoC (Cubit):

  1. counter_state.dart
  2. counter_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. يأخذ القيمة الحالية
  2. يزيدها 1
  3. يرسل حالة جديدة باستخدام 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
  • المستخدم ضغط زر ➕

الذي يحدث:

  1. Cubit يعرف أن هناك حدث (زيادة)
  2. يحسب القيمة الجديدة (1)
  3. يستخدم emit لإرسال الحالة الجديدة
  4. كل الواجهات (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

  1. BlocBuilder يسمع التغيير
  2. يعيد تنفيذ builder
  3. يأخذ state.counter = 1
  4. يعرض القيمة الجديدة

🔁 دورة كاملة (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
👉 المسؤول عن:

  • تحديث الحالة
  • إرسالها
  • إعادة بناء الواجهة