Koin هو إطار عمل DI لـ Kotlin يعمل بدون توليد كود أو انعكاس أو تعليقات توضيحية. تستخدم المكتبة DSL لوصف الوحدات وتحقق التبعيات من خلال حاوية خفيفة مع دعم Android وKtor وMultiplatform. وفقًا للوثائق الرسمية لـ Koin، يوفر الإطار وحدات ونطاقات ودعمًا مدمجًا لـ Jetpack Compose مع حد أدنى من boilerplate.
الرئيسية
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 كيلوبايت بسبب الكود المولد.
حاوية Koin تتم تهيئتها عبر دالة startKoin التي تقبل لامدا مع إعدادات. داخل هذه اللامدا يتم وصف الوحدات مع التسجيلات — اللبنة الأساسية لمنطق DI.
دالة startKoin تنشئ حاوية عامة يمكن الوصول إليها من أي مكان في التطبيق عبر GlobalContext، ولكن في المشاريع متعددة الوحدات يُوصى باستخدام KoinApplication لإنشاء حاويات معزولة. في Android، يتم استخدام AndroidContext للتهيئة، والذي يرتبط تلقائيًا بدورة حياة Application. يتم تسجيل الوحدات عبر معامل modules الذي يقبل قائمة من مثيلات Module.
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
تحتوي كل وحدة على تعريفات عبر single (مفرد) أو factory (مثيل جديد). يمكن للتعريفات الرجوع إلى تبعيات أخرى مسجلة عبر get()، مما يشكل رسمًا بيانيًا للحقن دون تحديد نوع صريح ودون كود boilerplate.
يستخدم Koin بنشاط دوال inline مع معاملات reified لاستنتاج النوع من السياق. هذا يسمح بكتابة تسجيلات دون تحديد الفئة صراحة: single { MyService() } يحدد النوع تلقائيًا من قيمة إرجاع اللامدا.
على عكس Dagger، لا يتحقق Koin من رسم التبعيات البياني في وقت الترجمة — يتم اكتشاف جميع الأخطاء في وقت التشغيل عند أول وصول إلى تبعية غير محلولة. هذا تنازل يبسط الكود بشكل كبير ويسرع الترجمة، ولكنه يتطلب تغطية اختبارية لتكوين DI. تختار العديد من الفرق Koin تحديدًا لسرعة التطوير والبساطة، على الرغم من عدم وجود فحوصات في وقت الترجمة.
في إصدار Koin 3.5، ظهر فحص تجريبي للرسم البياني في وقت الترجمة عبر إضافة Koin Annotations. يضيف المطور التعليقات التوضيحية @Module و @KoinComponent، ويقوم الإضافة بإنشاء كود تحقق يتم تشغيله أثناء البناء. ومع ذلك، فإن الميزة الرئيسية لـ Koin — عدم وجود توليد كود — تُفقد في هذا الوضع، لذلك تستمر معظم الفرق في استخدام نهج DSL الكلاسيكي مع فحوصات وقت التشغيل عبر الاختبارات.
Koin يوفر عدة طرق لحقن التبعيات: by inject() و get() والتمرير المباشر عبر المنشئ. يعتمد الاختيار على سياق الاستخدام.
المفوض by inject هو الطريقة الأكثر شيوعًا للحقن في ViewModel وأجزاء Android. تتم تهيئة التبعية بشكل بطيء — فقط عند أول وصول للخاصية. هذا فعال للخدمات كثيفة الموارد التي قد لا تكون مطلوبة فورًا.
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
دالة get تعيد مثيل التبعية فورًا. تُستخدم داخل لامبدا المصنع أثناء التسجيل أو عندما تكون التبعية مطلوبة في سياق متزامن دون تهيئة بطيئة. على عكس by inject()، لا تدعم get() التحميل البطيء وتتطلب أن تكون الحاوية مهيأة بالفعل في وقت الاستدعاء.
النطاق في Koin هو آلية لربط عمر التبعيات بمكون محدد، مثل Activity أو Fragment أو جلسة مخصصة. هذه وظيفة رئيسية لإدارة الذاكرة في تطبيقات Android.
دالة scope داخل وحدة تنشئ نطاقًا يعيش طالما يعيش المكون المرتبط. جميع التبعيات المسجلة في النطاق يتم تدميرها عند إغلاقه، مما يمنع تسرب الذاكرة.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
دالة scoped تسجل تبعية ستوجد فقط داخل النطاق. عند إغلاق النطاق، تصبح جميع الكائنات scoped متاحة لمجمع القمامة.
single تسجيل مثيل واحد للتطبيق بأكمله مع تهيئة بطيئة. يُستخدم للخدمات عديمة الحالة: عملاء الشبكة، مخازن التخزين المؤقت، السجلات.
factory ينشئ مثيلًا جديدًا في كل استدعاء get(). يُستخدم لـ ViewModel والمستودعات والكائنات ذات الحالة حيث يكون المثيل الجديد مهمًا في كل وصول.
دمج 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
إحدى الميزات الرئيسية لـ 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 يعمل في وقت التشغيل بدون توليد كود أو تعليقات توضيحية، مما يسرع الترجمة ولكنه لا يتحقق من رسم التبعيات في وقت الترجمة. Hilt يولد كودًا في وقت الترجمة ويكتشف أخطاء DI مبكرًا، ولكنه يتطلب إعدادًا معقدًا ويبطئ البناء.
نعم، Koin يدعم Kotlin Multiplatform بشكل كامل. مكتبة koin-core تعمل على جميع منصات Kotlin، بينما تضيف koin-android و koin-compose إمكانيات خاصة بالمنصة لـ Android و iOS على التوالي.
التبعيات الدائرية تؤدي إلى StackOverflowError في وقت التشغيل. Koin لا يكتشفها تلقائيًا. الحل هو إعادة هيكلة البنية: استخراج واجهة مشتركة، استخدام نمط Listener/Observer، أو كسر الدورة عبر مصنع مع تهيئة مؤجلة.
في Android، يمكن ربط النطاقات بدورة حياة Activity أو Fragment عبر AndroidScope. عند تدمير المكون، يقوم Koin تلقائيًا بإغلاق النطاق المقابل. في النطاقات المخصصة (جلسة المستخدم)، يتم الإغلاق يدويًا باستدعاء scope.close.
استخدم دالة koinTest من وحدة koin-test. تنشئ حاوية اختبار معزولة مع وحدات اختبار تغلق تلقائيًا بعد الاختبار. يتم استبدال التبعيات الحقيقية بـ mocks عبر وحدة باستخدام Mockito أو MockK.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.