koin چارچوب تزریق وابستگی است که کاملاً در کاتلین ساخته شده است. در مقایسه با Dagger2 ، می توانیم بسیاری از ویژگی های توسعه دهنده را در Koin پیدا کنیم. منحنی یادگیری برای Koin با Dagger2 کمتر مقایسه می شود.
بسیاری از ویژگی های قابل بحث وجود دارد. از آنجا که koin تازه کار است و هنوز در حال رشد است ، مقایسه با غول Dagger ناعادلانه خواهد بود! بیایید جزئیات اجرای Koin را طی کنیم.
Koin طبق اسناد رسمی ، یک چارچوب تزریق وابستگی سبک و عملگرا برای توسعه دهندگان Kotlin است. فقط با استفاده از functional resolution نوشته شده در Kotlin خالص: بدونه proxy، بدونه code generation، بدونه reflection
جالب به نظر می رسد ، اما آیا واقعاً به یک توسعه دهنده کمک می کند تا با سهولت در building application متمرکز شود.
قبل از ادامه کار ، توضیح خواهیم داد که دقیقاً چگونه Dependency Injection (DI) به توسعه کمک می کند. DI با Responsibility Principle و Dependency Inversion ارتباط نزدیکی دارد.
- Single Responsibility Principle: کلاس / گروه کلاسها / ماژول در یک single program وظیفه خاصی را بر عهده دارد که مشکل single problem را برطرف می کند.
- Dependency Inversion: ماژول های سطح بالا نباید به ماژول های سطح پایین تر بستگی داشته باشند.
اگر این اصول را رعایت کنیم ، برنامه دارای ماژول های کاملاً مشترک است. حالا باید ببینیم چگونه شروع می شود!
وابستگی ها را در build.gradle با نسخه مربوطه اضافه کنید.
dependencies {
//Koin
implementation "org.koin:koin-android:2.1.6"
implementation "org.koin:koin-androidx-viewmodel:2.1.6"
}
کلمات کلیدی و توابع مهم در koin
- startKoin: نمونه KoinApplication را ایجاد و ثبت میکند
- modules: ماژول های استفاده شده را اعلام میکند
- androidContext : از android context استفاده میکند
- by inject(): اجازه میدهد تا instance ها را با lazily بازیابی کنید
- get(): تابعی برای باز یابی مستقیم یک instance (non lazy)
- koinComponent: برای استفاده از فیچر های koin کلاس را با همان نام برچسب بزنید تا بتوانید به توابع آن دسترسی داشته باشید
Koin Scopes
- single: یک شیء پایدار را ایجاد می کند
- factory: هر بار یک شی جدید ایجاد می کند
- scoped: شئ را ایجاد میکند که به طول عمر scope وابسته است
تمام وابستگی ها باید در کلاس Application با startKoin و providing Android context(بر اساس مستندات رسمی) شروع شوند.
class MainApplication : Application(){
override fun onCreate() {
super.onCreate()
initKoin()
}
private fun initKoin() {
startKoin {
androidContext(this@MainApplication)
modules(provideDependency())
}
}
//List of Koin dependencies
open fun provideDependency() = appComponent
}
برای شروع Koin باید به Android Context و لیست ماژول ها توجه کنیم. اکنون یک لیست مشترک برای وابستگی ها ایجاد میکنیم (appComponent). می توانید تمام وابستگی های مورد نیاز را از اینجا اضافه کنید. برای شروع ، بگذارید کلاس Network و Utility را به عنوان وابستگیهای هدفمند در نظر بگیریم. هنگامی که مراحل و رویه Koin را درک کردیم ، می توانیم به وابستگی های پیچیده تری برویم. درمجموع در این آموزش وابستگی های زیر را پوشش خواهیم داد.
val appComponent = listOf(
NetworkDependency,
AppUtilDependency,
UseCaseDependency,
viewModelDependency
)
برای Network dependency کلاس NetworkDependency.kt را ایجاد میکنیم. مطابق گفته Koin ، تمام وابستگی ها در ماژول های Koin باید single شرح داده شود. برای همین ، بگذارید نحوه ایجاد یک ماژول Koin را ببینیم. سپس در مورد چگونگی شروع آن در Android صحبت خواهیم کرد. ماژول Network dependency مانند زیر خواهد بود.
Network Dependency
val NetworkDependency = module {
single {
Retrofit.Builder().addConverterFactory
(GsonConverterFactory.create
(GsonBuilder().create()))
.addCallAdapterFactory
(CoroutineCallAdapterFactory())
.baseUrl(BuildConfig.BASE_URL).build()
}
single { get<Retrofit>().create(SampleService::class.java)}
}
بگذارید جزئیات را توضیح دهم. همانطور که در بالا نشان داده شده است ، ما حداقل جزئیات موردنیاز نمونه Retrofit را ایجاد خواهیم کرد. به عنوان مثال ، برای تجزیه و تحلیل response با GsonConverter به مبدل json نیاز داریم. بعد به adapter factory نیاز داریم. در صورت استفاده از کوروتین ها ، می توانیم از CoroutineCallAdapterFactory استفاده کنیم. اگر از Rx استفاده می کنید ، می توانید به جای یک کوروتین از RxJava2CallAdapterFactory استفاده کنید. بعدی برای پیکربندی اولیه به baseUrl می رود. و سپس ما باید سرویس API مربوط به Retrofit را بنویسیم که به صورت زیر است
interface SampleService{
@GET("/loginapi")
suspend fun getRemoteData(@Query("parameter") param:String)
:DemoDataClass
}
Utility Class
کلاس Utility را مانند زیر مینویسیم. در بعضی موارد ، ما به روش های مختلف نیاز به context داریم.
بنابراین برای ایجاد کلاس Utility ، ما به عنوان یک ورودی اجباری به context نیاز خواهیم داشت ، به طوری که هر متد قادر به ادامه همین روش خواهد بود. بگذارید ببینیم چگونه این کار را می کنیم
class AppUtility(var context: Context) {
//Use the context object as common reusable throughout methods.
//Utility method which needs context
fun utilityMethodOne(){
}
}
در حال حاضر برای ایجاد utility Koin dependency ، به روش زیر عمل میکنیم.
این امر ضمن ایجاد وابستگی ، context را هم برای کلاس فراهم می کند
koin در هنگام تنظیم DI مراقب تنظیم context خواهد بود. ما لازم نیست که هر context را تنظیم کنیم.
val AppUtilDependency = module {
single { AppUtility(androidContext()) }
}
UI (Base Fragment)
به منظور تزریق AppUtils در یک بخش ، می توانیم موارد زیر را انجام دهیم.
class BaseFragment : Fragment(){
//Inject the app utility in base fragment
val mAppUtils: AppUtility by inject()
}
همانطور که utility در BaseFragment موجود است سایر fragment ها میتوانند به متد های app utility دسترسی داشته باشند.
بگذارید به کلاسهایی برویم که تحت کلاسهای مرتبط با رابط کاربری قرار نگیرد. در صورت نیاز به وابستگی در کلاسهای غیر UI ، می توانیم از KoinComponent استفاده کنیم. (طبق اسناد Koin ) می توانید موارد زیر را بیابید.
هنگامی که کلاس خود را به عنوان KoinComponent نشان کردید ، به این موارد دسترسی پیدا خواهید کرد:
by inject(): lazy instance ارزیابی شده از koin container
get():fetch instance مشتق شده از koin container
getProperty()/setProperty() - get/set property
Use Case
برای UseCase (مطابق معماری clean) ، ما می توانیم یک BaseUseCase استفاده کنیم که KoinComponent را گسترش می دهد.
interface BaseUseCase : KoinComponent
اکنون برای UseCase ، این کلاس BaseUseCase را گسترش دهید. همانطور که می بینید ، ما میتوانیم Utility را به KoinComponent تزریق کینم.
class LandingUseCase : BaseUseCase {
//Utility is available for usage in this class
val mAppUtils: AppUtility by inject()
fun someFunction(){
}
}
ما باید وابستگی به koin ایجاد کنیم که می تواند به همراه سایر وابستگی ها آغاز شود.
val UseCaseDependency = module {
factory {
LandingUseCase()
}
}
View Model
برای Viewmodel ، یک BaseViewModel abstractایجاد خواهیم کرد که به گسترش AndroidViewModel و KoinComponent میپردازد.
abstract class BaseViewModel(appContext:Application)
: AndroidViewModel(appContext), KoinComponent
اکنون ، LandingViewModel ما از BaseViewModel ارث بری میکند که برای شروع نیاز به context دارد. از این رو می توانیم مانند زیر LandingViewModel را ایجاد کنیم.
class LandingViewModel(context:Application) : BaseViewModel(context) {
//Inject use case using Koin
private val landingUseCase : LandingUseCase by inject()
fun someFunction(){
}
}
Landing Use Case با همان الگو تزریق می شود. در مرحله بعد ، وابستگی ViewModel را ایجاد کنید که می تواند context را در سازنده بگیرد و VM را آغاز کند.
val viewModelDependency = module {
viewModel { LandingViewModel(androidApplication()) }
}
با تمام لایه ها و مفاهیم توضیح داده شده ، می توانیم وابستگی های مورد نیاز را در fragment بنویسیم
class LandingFragment : BaseFragment() {
//View model injection using Koin way
private val viewModel by viewModel<LandingViewModel>()
//Utility is available for usage in this class
val mAppUtils: AppUtility by inject()
override fun onViewCreated(view: View,
savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnTwo.setOnClickListener {
viewModel.someFunction()
}
}
}
همانطور که مشاهده می کنید ، فراخوانی یک تابع در View Model (استفاده از تزریق وابستگی) از Fragment بسیار ساده است. و متد ViewModel با استفاده از توابع Use Case نیز به کار می رود.به همین ترتیب می توانید از دیگر لایه های معماری Clean نیز استفاده کنید. ما موارد استفاده از تزریق وابستگی را در هر دو بخش UI و سایر کلاسها که در سناریوهای معمول استفاده می شود را آموزش دادیم امیدواریم که مفید واقع شده باشند.
برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربریتان شوید