کوین (Koin) یک چارچوب DI برای Kotlin است که بدون تولید کد، بازتاب (reflection) و انوتاسیون کار میکند. این کتابخانه از DSL برای توصیف ماژولها استفاده میکند و وابستگیها را از طریق یک کنتینر سبک با پشتیبانی از Android، Ktor و Multiplatform وارد میکند. به استناد مستندات رسمی Koin، این چارچوب ماژولها، اسکوپها (scope) و پشتیبانی داخلی Jetpack Compose را با حداقل boilerplate فراهم میکند.
نکات کلیدی
Koin — چارچوب DI برای Kotlin است که به زبان خالص و بدون استفاده از بازتاب، انوتاسیون یا تولید کد نوشته شده است. بر خلاف Dagger Hilt که به پردازشگر انوتاسیون و تولید کد در مرحله کامپایل نیاز دارد، Koin صرفاً در runtime با استفاده از یک DSL سبک برای توصیف ماژولها کار میکند.
ایده اصلی Koin ارائه یک API ساده برای ثبت و حل وابستگیها بدون نیاز به یادگیری مفاهیم پیچیده گرافهای وابستگی و درختهای کامپوننت است. توسعهدهنده توصیف میکند که چه کلاسهایی برای کنتینر در دسترس هستند، و Koin آنها را از طریق سازنده یا دلگاتهای تنبل 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 گراف وابستگی را در مرحله کامپایل بررسی نمیکند — تمام خطاها در runtime و در بار اول دسترسی به یک وابستگی حل نشده کشف میشوند. این یک مصالهه است که کد را به طور قابل توجهی سادهتر و ساخت را سریعتر میکند، اما به پوشش تستی پیکربندی DI نیاز دارد. بسیاری از تیمها Koin را دقیقاً به خاطر سرعت توسعه و سادگی انتخاب میکنند، علیرغم عدم وجود بررسیهای زمان کامپایل.
در نسخه Koin 3.5، بررسی آزمایشی گراف در زمان کامپایل از طریق افزوده Koin Annotations اضافه شد. توسعهدهنده انوتاسیونهای @Module و @KoinComponent را اضافه میکند و افزوده کد اعتبارسنجی را که در زمان ساخت اجرا میشود تولید میکند. اما مزیت اصلی Koin — عدم تولید کد — در این حالت از دست میرود، بنابراین اکثر تیمها از رویکرد کلاسیک DSL با بررسیهای runtime از طریق تست استفاده میکنند.
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() از بارگیری تنبل پشتیبانی نمیکند و نیاز دارد کنتینر در زمان فراخوانی مقداردهی شده باشد.
Scope در 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 را بدون تنظیمات اضافی فراهم میکند، اسکوپها را به طور خودکار از طریق koinViewModel() به چرخه زندگی ViewModel با توجه به سیاق coroutine متصل میکند.
برای آزمایش، Koin توابع koinTest و koinTestRule را ارائه میدهد که یک کنتینر آزمایشی ایزوله با ماژولهای آزمایشی ایجاد کرده و پس از پایان آزمایش آن را به طور خودکار میبندد. این ایزولاسیون تستها را تضمین کرده و از نشت وضعیت بین موارد آزمایش جلوگیری میکند.
انتگراسیون Koin با Jetpack Navigation از طریق ماژول koin-androidx-navigation انجام میشود. ViewModel هر صفحه به طور خودکار وابستگیها را از طریق by viewModel() با انتقال SavedStateHandle برای حفظ وضعیت در چرخش صفحه و بازیابی پس از کوچک شدن برنامه دریافت میکند.
برای آزمایش واحد ViewModel با Koin از koinTestRule از کتابخانه koin-test-junit5 یا koin-test-junit4 استفاده میشود. این قاعده قبل از هر آزمایش یک کنتینر ایزوله با ماژولهای آزمایشی ایجاد کرده و پس از پایان آن را به طور خودکار میبندد و از نشت وضعیت بین موارد آزمایش جلوگیری میکند. وابستگیهای واقعی از طریق 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 در runtime بدون تولید کد و انوتاسیون کار میکند که ساخت را سریعتر میکند، اما گراف وابستگی را در زمان کامپایل بررسی نمیکند. Hilt در زمان کامپایل کد تولید کرده و خطاهای DI را زودتر کشف میکند، اما نیاز به تنظیمات پیچیده دارد و ساخت را کند میکند.
بله، Koin از Kotlin Multiplatform پشتیبانی کاملی میکند. کتابخانه koin-core در همه پلتفرمهای Kotlin کار میکند، و koin-android و koin-compose به ترتیب قابلیتهای مخصوص پلتفرم را برای Android و iOS اضافه میکنند.
وابستگیهای چرخشی منجر به StackOverflowError در runtime میشوند. Koin آنها را به طور خودکار کشف نمیکند. راه حل — بازنویسی معماری: جداسازی یک رابط مشترک، استفاده از الگوی Listener/Observer یا شکستن چرخه از طریق یک کارخانه با مقداردهی تأخیری.
در Android، اسکوپها را میتوان از طریق AndroidScope به چرخه زندگی Activity یا Fragment متصل کرد. پس از نابودی کامپوننت، Koin به طور خودکار اسکوپ مرتبط را میبندد. در اسکوپهای سفارشی (نشست کاربر)، بستن به صورت دستی با تماس scope.close انجام میشود.
از تابع koinTest از ماژول koin-test استفاده کنید. آن یک کنتینر ایزوله با ماژولهای آزمایشی ایجاد میکند که پس از آزمایش به طور خودکار بسته میشود. وابستگیهای واقعی از طریق یک ماژول با Mockito یا MockK با ماک جایگزین میشوند.
نتیجهگیری
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید