الفصل 06Chapter 06

حقن التبعيات (Dependency Injection) Dependency Injection (GetIt)

إدارة العلاقات بين الكائنات وتسهيل الوصول للخدمات باستخدام نظام Service Locator. Managing object relationships and facilitating service access using a Service Locator system.

💉 يعني إيه حقن تبعيات؟ What is Dependency Injection?

تخيل إنك بتبني بيت. هل ينفع كل ما تحتاج شاكوش تروح تصنعه من الصفر؟ طبعاً لأ. الأفضل يكون فيه "شنطة عدة" فيها الشاكوش جاهز، وإنت تطلبه وقت ما تحتاجه. ده بالضبط الـ Dependency Injection.

Imagine building a house. Should you manufacture a hammer from scratch every time you need one? Of course not. Better to have a "toolbox" where the hammer is ready, and you request it when needed. This is Dependency Injection.

في البرمجة، الـ Dependency هو أي كلاس كلاس تاني محتاجه عشان يشتغل (زي الـ Repository اللي الـ BLoC محتاجه).

📦 ليه GetIt؟ Why GetIt?

إحنا اختارنا مكتبة GetIt لأنها بسيطة، سريعة، ومش محتاجة BuildContext. يعني تقدر تجيب أي خدمة حتى لو إنت بره الـ Widgets خالص.

// إزاي بنطلب خدمة؟
final repository = sl<IBookRepository>();
🏗️ Singletons vs Factories Registration Types

في GetIt، عندنا طريقتين أساسيتين لتسجيل الكلاسات:

1
LazySingleton

بيكّريه نسخة واحدة بس من الكلاس، ويفضل محتفظ بيها طول ما الأبلكيشن شغال. بنستخدمه مع الداتابيز والـ Repositories.

2
Factory

بيكريه نسخة "جديدة" في كل مرة تطلبه فيها. بنستخدمه مع الـ Blocs والـ Cubits عشان كل شاشة تاخد نسخة فريش.

🔍 جولة في الكود Code Walkthrough

تعالوا نشوف إزاي بنرتب "شنطة العدة" بتاعتنا في التطبيق:

📁 lib/core/di/injection_container.dart
final sl = GetIt.instance; // sl = Service Locator

Future initDI() async {
  // تسجيل الداتابيز كـ Singleton (نسخة واحدة للكل)
  sl.registerLazySingleton(() => AppDatabase());

  // تسجيل الـ Repository مع حقن الـ DAO جواه
  sl.registerLazySingleton(
    () => BookRepositoryImpl(sl()),
  );

  // تسجيل البلوك كـ Factory (نسخة جديدة لكل شاشة)
  sl.registerFactory(() => BooksBloc(repository: sl()));
}
sl<BookDao>()

لاحظ هنا إننا بنعمل "حقن تلقائي". الـ Repository محتاج DAO، فإحنا بنقول لـ GetIt: "روحي هاتي الـ DAO اللي سجلناه فوق وحطيه هنا".

🔢 قوة المعاملات: registerFactoryParam The Power of Parameters: registerFactoryParam

أحياناً، الـ Cubit مش بس محتاج Repository، ده محتاج معلومة تانية بتيجي وقت التشغيل (زي الـ bookId). إزاي بنبعت المعلومة دي لـ GetIt؟

// التسجيل في injection_container.dart
sl.registerFactoryParam(
  (bookId, _) => AddEntryCubit(entryDao: sl(), bookId: bookId),
);

// الاستدعاء في الـ UI
final cubit = sl(param1: currentBookId);
param1: currentBookId

هنا GetIt بيشتغل كـ "خلاط". بياخد الـ EntryDao اللي عنده أصلاً، وبياخد الـ bookId اللي إنت لسه باعته، ويطلعلك Cubit جاهز للشغل فوراً.

الوقت هو كل شيء: التهيُئة غير المتزامنة Timing is Everything: Async Initialization

بعض الخدمات (زي SharedPreferences) بتحتاج وقت عشان تحمل من ذاكرة الموبايل. عشان كدة الـ initDI لازم تكون Future<void>.

// في main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initDI(); // لازم نستنى لغاية ما كل العدة تجهز!
  runApp(const MyApp());
}

لو نسينا الـ await، التطبيق ممكن يضرب (Crash) لأنه هيحاول يستخدم خدمة لسه مخلصتش تحميل.

🧪 التبديل من أجل النجاح: الاختبار بالـ DI Swapping for Success: Testing with GetIt

أكبر ميزة للـ DI هي في الـ Unit Testing. إحنا نقدر "نلعب" في شنطة العدة ونشيل الشاكوش الأصلي ونحط واحد بلاستيك (Mock) عشان التست.

void main() {
  test('يجب الحصول على البيانات من النسخة المزيفة', () {
    final mockRepo = MockBookRepository();
    
    // بنسجل النسخة المزيفة بدل الأصلية
    sl.registerSingleton(mockRepo);
    
    // دلوقتي أي حد يطلب IBookRepository هياخد الـ Mock!
    final repo = sl();
    expect(repo, isA());
  });
}
🗺️ خريطة العدة: جدول التبعيات Mapping the Toolbox: Dependency Map

عشان متتوهش، ده جدول بيوضح كل نوع تبعية بيتم التعامل معاه إزاي في المشروع:

LayerServicePatternLifetime
PersistenceSharedPreferencesLazySingletonطول عمر البرنامج.
DatabaseAppDatabaseLazySingletonطول عمر البرنامج.
DomainIBookRepositoryLazySingletonطول عمر البرنامج.
PresentationBooksBlocFactoryبيموت أول ما الشاشة تتقفل.
🧩 الـ DI المقسم: إدارة الميزات Modular DI: Managing Features

لما التطبيق يكبر، مينفعش نحط كل حاجة في ملف واحد. عشان كدة بنستخدم injectDashboardFeature مثلاً.

📁 lib/features/dashboard_injection.dart
void injectDashboardFeature(GetIt sl) {
  sl.registerFactory(() => DashboardCubit(
    bookDao: sl(),
    entryDao: sl(),
  ));
}

ده بيخلي كل ميزة مسؤولة عن "حقن" نفسها، وده بيريحنا جداً لما نحب نضيف ميزة جديدة أو نمسح ميزة قديمة.

🕵️‍♂️ تحدي: المحقق DI (The DI Detective) Interactive Exercise: The DI Detective

حاول تلاقي الغلطة في الحالات دي:

1
Problem: "Object not found"

السيناريو: بتطلب sl<MyService>() وبيطلع Error إن الخدمة مش موجودة.

الحل: اتأكد إنك سجلت الخدمة جوه initDI، وفوق السطر اللي بتطلبها فيه. الترتيب مهم!

2
Problem: Memory Leak in Singleton

السيناريو: سجلت MyCubit كـ Singleton.

الحل: غلط! الـ Cubit لازم يكون Factory. لو بقى Singleton، هيفضل محتفظ ببيانات قديمة من شاشات تانية حتى لو اتقفلت.

🧠 الفلسفة: DI ولا Service Locator؟ The Philosophy: DI vs Service Locator

من الناحية التقنية، GetIt هو Service Locator. الفرق بسيط بس مهم:

ConceptService Locator (GetIt)Pure DI (Constructor)
How it worksالكلاس بيطلب حاجته بنفسه من GetIt.الكلاس بيستنى حاجته تجيله في الـ Constructor.
Prosسهل جداً، مش محتاج BuildContext.واضح جداً، التبعيات باينة من بره الشنطة.
Consبيخفي التبعيات شوية جوه الكود.بيعمل زحمة (Boilerplate) كتير في الـ Constructors.

في MRE CashBook، إحنا دمجنا الاتنين. بنسجل في GetIt، بس الـ Blocs بتاخد حاجتها عن طريق الـ Constructor Injection. ده بيجمع بين سهولة GetIt ووضوح الـ DI.

🌎 محاكاة العالم: اختبار التكامل (Integration Testing) Mocking the World: Integration Testing

في اختبارات التكامل، إحنا مش بس بنجرب كلاس واحد، إحنا بنجرب "البيت كله". الـ DI بيقدر يبهدل الدنيا لو مش مظبوط، بس GetIt بيسهل لنا التبديل حتى لو الأبلكيشن شغال.

setUp(() async {
  // بنصفر GetIt قبل كل تست
  await sl.reset();
  
  // بنسجل كل الخدمات المزيفة
  sl.registerLazySingleton(() => MockDatabase());
  sl.registerLazySingleton(() => MockPrefs());
  
  // بنشغل باقي الـ DI الأصلي
  initDI();
});

بالطريقة دي، الأبلكيشن بيفضل فاكر إنه بيكلم داتابيز حقيقية، بس في الحقيقة هو بيكلم ملف في الـ Memory بتاع التست. ده بيخليه سريع وآمن.

📜 وصايا الحقن العشر (DI Commandments) The DI Commandments: Best Practices

عشان تحافظ على شنطة عدتك نظيفة، اتبع القواعد دي:

  • لا تسجل أي Widget جوه GetIt. الـ Widgets بتعتمد على الـ Context بس.
  • لا تستخدم sl.get() جوه الـ UI مباشرة؛ مرر الخدمة للـ Bloc الأول.
  • دائماً استخدم LazySingleton لو الخدمة تقيلة (زي الـ DB) عشان متبدأش إلا لو حد طلبها.
  • دائماً افصل ملفات الـ DI لكل Feature عشان المشروع ميكبرش منك ويهرب.
  • تجنب تسجيل كلاسات بتعتمد على بعض بشكل دائري (Circular Dependency).
أسئلة شائعة في حقن التبعيات Dependency Injection FAQ
هل ينفع استخدم GetIt بدل الـ Provider؟
Can I use GetIt instead of Provider?
أيوة، GetIt ممتاز لإدارة "الخدمات" (Services)، بينما Provider أو Bloc ممتاز لإدارة "الـ UI States". الأفضل تستخدم الاتنين مع بعض زي ما عملنا في المشروع.
Yes, GetIt is great for long-lived services, while Provider/Bloc is better for UI state. Using both together is the best practice.
إمتى أستخدم registerSingleton بدل registerLazySingleton؟
When to use registerSingleton instead of Lazy?
استخدم registerSingleton لو إنت عاوز الخدمة تشتغل فوراً أول ما الأبلكيشن يفتح (زي الـ Analytics)، واستخدم Lazy لو عاوزها تشتغل بس لما حد يحتاجها لأول مرة عشان توفر رامات.
Use registerSingleton for immediate initialization (e.g., Analytics). Use Lazy to save memory and only initialize when actually needed.
📖 قاموس الـ DI: مصطلحات تهمك The DI Dictionary: Glossary

عشان تتكلم بلغة المحترفين، لازم تكون عارف المصطلحات دي:

  • Dependency: أي كلاس معتمد على كلاس تاني.
  • Service Locator: المركز اللي بنطلب منه الخدمات (زي GetIt).
  • Decoupling: إننا نفصل الكلاسات عن بعض عشان التغيير في واحد ميبوظش التاني.
  • Mock: نسخة مزيفة من الكلاس بنستخدمها في الاختبارات.
  • Composition Root: المكان اللي بنربط فيه كل التبعيات ببعض (زي ملف initDI).
🔖 جدول مراجعة الفصل السادس Chapter 06 Recap Table
ConceptArabic RecapEnglish Recap
Initializationبتتم مرة واحدة في الـ main.dart.Initialized once at app startup.
Registrationعندنا Singletons (واحدة بس) و Factories (كتير).Singletons vs Factories.
Interfaceدايماً بنعتمد على الـ Interface مش الـ Implementation.Coding to interfaces, not concretes.
Testingسهل جداً نبدل الحقيقي بالمزيف.Easy swapping for mocks.
🏔️ مستويات الوصول: Scoping Advanced: Scoping Dependencies

أحياناً بنحتاج نسجل تبعيات "مؤقتة" لشاشة معينة بس، ونمسحها أول ما نمشي. GetIt بيدينا ميزة الـ pushNewScope.

// فتح مستوى جديد
sl.pushNewScope();
sl.registerFactory(() => TemporaryCubit());

// مسح كل اللي في المستوى ده
sl.popScope();

ده بيفيد جداً في العمليات المعقدة اللي مش عاوزين دوشتها تفضل موجودة في الذاكرة طول الوقت.

📊 ملخص مرئي للـ Dependency Injection DI Summary Infographic

[ User Request ] ──▶ [ GetIt sl ] ──▶ [ Search Toolbox ]
                            │
            ┌───────────────┴───────────────┐
            ▼                               ▼
    [ LazySingleton ]               [ Factory ]
    (One instance)                  (New instance)
    Used for: DB, Repo              Used for: BLoC, Cubit
            │                               │
            └───────────────┬───────────────┘
                            ▼
                    [ Injected Object ]
        

الرسمة دي بتلخص تدفق البيانات والطلبات جوه نظام الـ DI بتاعنا.

📝 الملخص Summary

الـ Dependency Injection هو اللي بيخلي الكود بتاعنا "مفكوك" (Decoupled)، وده سر سهولة الصيانة والتعديل في المستقبل.

الفصل السابقPrevious الفصل التاليNext