Counter Project By Cubit

// هذا الكلاس يمثل "الحالة الأساسية" (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);

📌 ماذا يحدث هنا؟

  1. يتم استدعاء الكونستركتور
  2. يتم إنشاء كائن جديد
  3. يتم وضع القيمة 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));

📌 ماذا يحدث هنا؟

  1. يتم إنشاء Cubit
  2. يتم إعطاؤه أول حالة
  3. هذه الحالة = counter = 0

💡 بدون هذه الخطوة سيحدث خطأ ❌


🔹 ما هو state؟

state.counter

📌 هذا يمثل:

الحالة الحالية داخل Cubit


🔹 ما هو emit؟

emit(...)

📌 هذا أهم شيء في Cubit:

emit = إرسال حالة جديدة

💡 عند استدعائه:

  • يتم تحديث الحالة
  • يتم إعادة بناء UI تلقائيًا (BlocBuilder)

🔁 العلاقة مع ملف الـ State (المهم جدًا)

🧩 تذكير بالكلاسات:

CounterState
IncrementCounter
DecrementCounter

🔗 كيف يرتبطون مع Cubit؟

عند الضغط على زر ➕

increment()

يحدث:

emit(IncrementCounter(state.counter + 1));

👉 يعني:

  1. نأخذ القيمة الحالية
  2. نزيدها
  3. ننشئ حالة جديدة
  4. نرسلها

عند الضغط على زر ➖

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