Koin — ما هو، مبادئ Dependency Injection وكيف يعمل

المؤلف: IT Sectr نُشر: 2026-05-04 وقت القراءة: 8 دق

Koin هو إطار عمل DI لـ Kotlin يعمل بدون توليد كود أو انعكاس أو تعليقات توضيحية. تستخدم المكتبة DSL لوصف الوحدات وتحقق التبعيات من خلال حاوية خفيفة مع دعم Android وKtor وMultiplatform. وفقًا للوثائق الرسمية لـ Koin، يوفر الإطار وحدات ونطاقات ودعمًا مدمجًا لـ Jetpack Compose مع حد أدنى من boilerplate.

الرئيسية

  • Koin هو إطار عمل DI لـ Kotlin يستخدم DSL بدون انعكاس أو توليد كود.
  • الوحدة هي تجميع منطقي لتسجيلات التبعيات من خلال دالتي single وfactory.
  • single تسجيل مفرد يتم إنشاء مثيله مرة واحدة.
  • factory تسجيل ينشئ مثيلاً جديدًا في كل طلب.
  • النطاق آلية لربط عمر التبعية بمكون، مثل Activity.

ما هو Koin وما الفرق

Koin هو إطار عمل DI لـ Kotlin مكتوب بلغة نقية بدون استخدام الانعكاس أو التعليقات التوضيحية أو توليد الكود. على عكس Dagger Hilt الذي يتطلب معالج تعليقات توضيحية وتوليد كود في وقت الترجمة، يعمل Koin حصريًا في وقت التشغيل باستخدام DSL خفيف لوصف الوحدات.

الفكرة الرئيسية لـ Koin هي توفير واجهة برمجة تطبيقات بسيطة لتسجيل وحل التبعيات دون الحاجة إلى تعلم مفاهيم معقدة لرسوم التبعيات وأشجار المكونات. يصف المطور الفئات المتاحة للحاوية، ويقوم Koin بحقنها تلقائيًا عبر الـ constructor أو المفوضين البطيئين by inject. الإطار متوافق تمامًا مع Kotlin Multiplatform، مما يسمح باستخدام نهج DI موحد على Android وiOS والجانب الخادم.

وفقًا لاستطلاع مجتمع مطوري Kotlin (2025)، يُستخدم Koin في 31% من مشاريع Android التجارية، في المرتبة الثانية بعد Hilt (47%). السبب الرئيسي لاختياره هو سهولة الإعداد وعدم الحاجة إلى توليد الكود، مما يسرع بناء المشروع.

اختر Koin للمشاريع المتوسطة والكبيرة حيث تكون البداية السريعة للتطوير مهمة، أو لحلول Kotlin Multiplatform حيث Hilt غير متاح لأسباب معمارية.

Koin لا يستخدم الانعكاس أو توليد الكود — جميع التسجيلات مبنية على دوال inline مع أنواع reified التي تستبدل النوع الملموس في جسم الدالة في وقت الترجمة. هذا يجعل Koin واحدًا من أخف أطر عمل DI من حيث حجم APK النهائي: إضافة Koin تزيد حجم التطبيق بمقدار 100-150 كيلوبايت فقط، بينما يضيف Dagger Hilt حوالي 500 كيلوبايت بسبب الكود المولد.

كيف يعمل حاوية وDSL الخاص بـ Koin

حاوية Koin تتم تهيئتها عبر دالة startKoin التي تقبل لامدا مع إعدادات. داخل هذه اللامدا يتم وصف الوحدات مع التسجيلات — اللبنة الأساسية لمنطق DI.

startKoin والوحدات

دالة startKoin تنشئ حاوية عامة يمكن الوصول إليها من أي مكان في التطبيق عبر GlobalContext، ولكن في المشاريع متعددة الوحدات يُوصى باستخدام KoinApplication لإنشاء حاويات معزولة. في Android، يتم استخدام AndroidContext للتهيئة، والذي يرتبط تلقائيًا بدورة حياة Application. يتم تسجيل الوحدات عبر معامل modules الذي يقبل قائمة من مثيلات Module.

kotlin
val networkModule = module {
    single {
        OkHttpClient()
    }
    single {
        Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .build()
    }
}

startKoin {
    modules(networkModule)
}

تحتوي كل وحدة على تعريفات عبر single (مفرد) أو factory (مثيل جديد). يمكن للتعريفات الرجوع إلى تبعيات أخرى مسجلة عبر get()، مما يشكل رسمًا بيانيًا للحقن دون تحديد نوع صريح ودون كود boilerplate.

DSL والدوال inline

يستخدم Koin بنشاط دوال inline مع معاملات reified لاستنتاج النوع من السياق. هذا يسمح بكتابة تسجيلات دون تحديد الفئة صراحة: single { MyService() } يحدد النوع تلقائيًا من قيمة إرجاع اللامدا.

على عكس Dagger، لا يتحقق Koin من رسم التبعيات البياني في وقت الترجمة — يتم اكتشاف جميع الأخطاء في وقت التشغيل عند أول وصول إلى تبعية غير محلولة. هذا تنازل يبسط الكود بشكل كبير ويسرع الترجمة، ولكنه يتطلب تغطية اختبارية لتكوين DI. تختار العديد من الفرق Koin تحديدًا لسرعة التطوير والبساطة، على الرغم من عدم وجود فحوصات في وقت الترجمة.

في إصدار Koin 3.5، ظهر فحص تجريبي للرسم البياني في وقت الترجمة عبر إضافة Koin Annotations. يضيف المطور التعليقات التوضيحية @Module و @KoinComponent، ويقوم الإضافة بإنشاء كود تحقق يتم تشغيله أثناء البناء. ومع ذلك، فإن الميزة الرئيسية لـ Koin — عدم وجود توليد كود — تُفقد في هذا الوضع، لذلك تستمر معظم الفرق في استخدام نهج DSL الكلاسيكي مع فحوصات وقت التشغيل عبر الاختبارات.

طرق حقن التبعيات في Koin

Koin يوفر عدة طرق لحقن التبعيات: by inject() و get() والتمرير المباشر عبر المنشئ. يعتمد الاختيار على سياق الاستخدام.

by inject() — الحقن البطيء

المفوض by inject هو الطريقة الأكثر شيوعًا للحقن في ViewModel وأجزاء Android. تتم تهيئة التبعية بشكل بطيء — فقط عند أول وصول للخاصية. هذا فعال للخدمات كثيفة الموارد التي قد لا تكون مطلوبة فورًا.

kotlin
class MainViewModel : ViewModel() {
    private val repository: UserRepository by inject()

    fun loadUsers() {
        repository.fetchAll()
    }
}

get() — الحصول الصريح

دالة get تعيد مثيل التبعية فورًا. تُستخدم داخل لامبدا المصنع أثناء التسجيل أو عندما تكون التبعية مطلوبة في سياق متزامن دون تهيئة بطيئة. على عكس by inject()، لا تدعم get() التحميل البطيء وتتطلب أن تكون الحاوية مهيأة بالفعل في وقت الاستدعاء.

الوحدات والنطاقات في Koin

النطاق في Koin هو آلية لربط عمر التبعيات بمكون محدد، مثل Activity أو Fragment أو جلسة مخصصة. هذه وظيفة رئيسية لإدارة الذاكرة في تطبيقات Android.

scope — الربط بمكون

دالة scope داخل وحدة تنشئ نطاقًا يعيش طالما يعيش المكون المرتبط. جميع التبعيات المسجلة في النطاق يتم تدميرها عند إغلاقه، مما يمنع تسرب الذاكرة.

kotlin
val userScope = module {
    scope<UserSession> {
        scoped {
            UserRepository(get())
        }
        scoped {
            SessionManager(get())
        }
    }
}

دالة scoped تسجل تبعية ستوجد فقط داخل النطاق. عند إغلاق النطاق، تصبح جميع الكائنات scoped متاحة لمجمع القمامة.

single مقابل factory

single تسجيل مثيل واحد للتطبيق بأكمله مع تهيئة بطيئة. يُستخدم للخدمات عديمة الحالة: عملاء الشبكة، مخازن التخزين المؤقت، السجلات.

factory ينشئ مثيلًا جديدًا في كل استدعاء get(). يُستخدم لـ ViewModel والمستودعات والكائنات ذات الحالة حيث يكون المثيل الجديد مهمًا في كل وصول.

Koin في مشاريع Android

دمج Koin في مشروع Android ضئيل: يكفي إضافة تبعية إلى build.gradle واستدعاء startKoin في Application.onCreate. يوفر Koin وحدات للتكامل مع Jetpack Compose وNavigation وWorkManager، مما يجعله بديلاً كاملاً لـ Hilt.

مكتبة koin-android-compose الخاصة تسمح بحقن التبعيات مباشرة في دوال Composable عبر koinViewModel() و koinInject(). هذا يلغي الحاجة إلى تمرير الحاوية عبر معلمات كل شاشة ويجعل كود ViewModel أنظف من خلال الربط التلقائي بدورة الحياة.

وفقًا لـ Google I/O 2024، أصبح Jetpack Compose الإطار الرئيسي لمشاريع Android الجديدة. يوفر Koin دعمًا أصليًا لـ Compose دون إعدادات إضافية، ويربط تلقائيًا النطاقات بدورة حياة ViewModel عبر koinViewModel() مع مراعاة سياق coroutine.

للاختبار، يوفر Koin دالتي koinTest و koinTestRule اللتين تنشئان حاوية اختبار معزولة مع وحدات اختبار وتغلقانها تلقائيًا بعد اكتمال الاختبار. هذا يضمن عزل الاختبارات ويمنع تسرب الحالة بين حالات الاختبار.

يتم تنفيذ دمج Koin مع Jetpack Navigation عبر وحدة koin-androidx-navigation. يتلقى ViewModel لكل شاشة التبعيات تلقائيًا عبر by viewModel() مع تمرير SavedStateHandle للحفاظ على الحالة عند تدوير الشاشة واستعادتها بعد تعليق التطبيق.

لاختبار الوحدة لـ ViewModel مع Koin، يُستخدم koinTestRule من مكتبة koin-test-junit5 أو koin-test-junit4. تنشئ القاعدة حاوية معزولة مع وحدات اختبار قبل كل اختبار وتغلقها تلقائيًا بعد الانتهاء، مما يمنع تسرب الحالة بين حالات الاختبار. يتم استبدال التبعيات الحقيقية بـ mocks عبر MockK: وحدة تحتوي على تسجيلات single { mockk() } تلغي الوحدة الرئيسية، ويحصل ViewModel في الاختبار على سلوك تبعيات يمكن التنبؤ به.

إحدى الميزات الرئيسية لـ Koin 3.x هي دعم Ktor لإنشاء تطبيقات خادم على Kotlin و Compose Multiplatform لتطبيقات سطح المكتب. هذا يجعل Koin إطار DI الوحيد الذي يغطي جميع منصات Kotlin الثلاث دون تغيير نموذج الحقن. تسمح وحدة koin-ktor بتسجيل التبعيات عبر install(Koin) في كتلة Application وحقن الخدمات في المسارات عبر by inject() تمامًا كما في Android. هذا يجعل Koin حلاً DI عالميًا لمشاريع Kotlin من أي بنية — من عميل محمول إلى خادم خلفي.

دمج Koin مع Jetpack Navigation عبر وحدة koin-androidx-navigation يلغي الحاجة إلى إنشاء ViewModelProvider.Factory يدويًا لكل شاشة. للمشاريع متعددة الوحدات، يدعم Koin التحميل البطيء للوحدات عبر loadKoinModules، مما يسمح لكل وحدة ميزة بتوصيل تكوين DI الخاص بها بشكل مستقل.

الأسئلة الشائعة

ما الفرق بين Koin و Dagger Hilt؟

Koin يعمل في وقت التشغيل بدون توليد كود أو تعليقات توضيحية، مما يسرع الترجمة ولكنه لا يتحقق من رسم التبعيات في وقت الترجمة. Hilt يولد كودًا في وقت الترجمة ويكتشف أخطاء DI مبكرًا، ولكنه يتطلب إعدادًا معقدًا ويبطئ البناء.

هل يدعم Koin Kotlin Multiplatform؟

نعم، Koin يدعم Kotlin Multiplatform بشكل كامل. مكتبة koin-core تعمل على جميع منصات Kotlin، بينما تضيف koin-android و koin-compose إمكانيات خاصة بالمنصة لـ Android و iOS على التوالي.

كيفية معالجة التبعيات الدائرية في Koin؟

التبعيات الدائرية تؤدي إلى StackOverflowError في وقت التشغيل. Koin لا يكتشفها تلقائيًا. الحل هو إعادة هيكلة البنية: استخراج واجهة مشتركة، استخدام نمط Listener/Observer، أو كسر الدورة عبر مصنع مع تهيئة مؤجلة.

هل أحتاج إلى إغلاق النطاقات يدويًا؟

في Android، يمكن ربط النطاقات بدورة حياة Activity أو Fragment عبر AndroidScope. عند تدمير المكون، يقوم Koin تلقائيًا بإغلاق النطاق المقابل. في النطاقات المخصصة (جلسة المستخدم)، يتم الإغلاق يدويًا باستدعاء scope.close.

كيفية اختبار الكود مع Koin؟

استخدم دالة koinTest من وحدة koin-test. تنشئ حاوية اختبار معزولة مع وحدات اختبار تغلق تلقائيًا بعد الاختبار. يتم استبدال التبعيات الحقيقية بـ mocks عبر وحدة باستخدام Mockito أو MockK.

الخلاصة

  • Koin هو إطار عمل DI لـ Kotlin مع DSL بدون انعكاس أو تعليقات توضيحية أو توليد كود.
  • الوحدات تجمع التسجيلات عبر single (مفرد) و factory (مثيل جديد).
  • startKoin يهيئ حاوية عامة يمكن الوصول إليها عبر by inject() و get().
  • النطاق يربط عمر التبعيات بدورة حياة مكون Android.
  • الحقن في Compose يتم عبر koinViewModel() و koinInject().
  • Koin يدعم Kotlin Multiplatform، مما يسمح بنهج DI موحد عبر جميع المنصات.
  • أخطاء DI تُكتشف في وقت التشغيل، لذلك تغطية اختبار التكوين إلزامية.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا