در جشنواره عید فطر سون لرن، 40% تخفیف ویژه هدیه بگیر!

Koin چیست؟ مقایسه Koin و Dagger

  ‏سطح پیشرفته
‏  18 دقیقه
۲۶ دی ۱۳۹۹
Koin چیست؟ مقایسه Koin و Dagger
در برنامه نویسی اندروید، مدیریت وابستگی‌ها می‌تواند یک نگرانی بزرگ باشد. اگر از Dagger2 در پرژوه‌های خود استفاده کرده باشید، می‌دانید که برای هر پروژه به کد‌های تکراری بسیار زیادی نیاز دارید. با اضافه کردن ویژگی‌های جدید به پروژه، این کدهای تکراری بیشتر  هم می‌شوند و شما مجبور هستید برای هر ماژول جدید، کدهای تکراری بنویسید. Dagger2 برای زبان جاوا نوشته شده است و Koin برای زبان کاتلین. همچنین شما می‌توانید از هر دو کتابخانه در پروژه‌ی کاتلین خود استفاده کنید. اما استفاده از Koin در جاوا می‌تواند برای شما مشکل ساز باشد. یک نکته‌ی قابل توجه در مقایسه Koin و Dagger این است که Dagger2 یک Dependency Injection است، اما Koin یک Service Locator می‌باشد. طی چند سال گذشته، عمدتا از Dagger2 برای تزریق وابستگی استفاده می‌شد. در این مقاله این به مقایسه Koin و Dagger خواهیم پرداخت. با ما همراه باشید.
Koin یک فریمورک سبک تزریق وابستگی برای توسعه دهندگان کاتلین است. Koin فقط با استفاده از عملکرد کاتلین نوشته شده است: no proxy, no code generation, no reflection.
بیشتر بدانید : آموزش dagger در اندروید

مقایسه Koin و Dagger

ما به منظور مقایسه Koin و Dagger، بر روی پروژه‌ای تمرکز خواهیم کرد که ابتدا با Dagger2 و سپس با Koin پیاده سازی شده است. معماری این پروژه MVVM است و در آن از Retrofit و LiveData استفاده شده است. این پروژه دارای یک اکتیویتی، چهار فرگمنت، پنج ViewModel، یک Repository و یک web service است، بنابراین در این پروژه فقط پایه‌های اصلی طراحی شده است.
 پکیج DI را در تصویر زیر مشاهده کنید. عکس سمت راست پیاده سازی برای Dagger و عکس سمت راست برای Koin می‌باشد.
مقایسه فایل‌های koin و dagger
همانطور که در تصویر مشاهده می‌کنید، برای استفاده از dagger2 شما به فایل‌ها و کد‌های زیادی نیاز دارید. در صورتی که همین کار با Koin خیلی ساده‌تر شده و فایل‌ها و کدهای بسیار کم‌تری لازم دارد.

مقایسه تعداد خطوط کد در koin و dagger بعد از کامپایل شدن

برای به دست آوردن این اعداد از آمار استفاده کردیم. نتیجه جالب است.
مقایسه تعداد خطوط کد koin و dagger

این تعداد کد قبل و بعد از کامپایل می‌باشد. همانطور که می‌بینید تعداد خط کد ایجاد شده توسط dagger دو برابر بیشتر از Koin است.

مقایسه آماری koin و dagger در گوگل

مقایسه آماری در گوگل را در تصویر بالا مشاهده می‌کنید.

از مشاهدات آماری فوق کاملاً مشخص است که Koin امتیاز بیشتری نسبت به Dagger2 کسب می‌کند.

مقایسه Koin و Dagger در زمان build شدن

مقایسه زمان build در koin و dagger برخی توسعه دهندگان، Koin را بر Dagger2 ترجیح می‌دهند. آن‌ها معتقد هستند که کار با Dagger2 دشوار می‌باشد اما ممکن است این مشکل، یک مسئله‌ی کوچک باشد. Dagger2 به دلیل کدهای زیاد تولید شده، برای توسعه دهندگان مشکل ایجاد می‌کند و رفع این مشکل، یک کابوس است. علاوه بر این، یادگیری Dagger2 برای تازه‌کارها دشوار می‌باشد، بنابراین اگر کسی به پروژه یا تیم شما بپیوندد باید زمان زیادی را صرف یادگیری Dagger2 کند.

چرا از Koin استفاده کنیم؟

  1.  No Reflection
  2. کد اضافی تولید نمی‌کند.
  3. برای انجام یک تزریق ساده نیازی به نوشتن کد زیاد نیست.
  4. ادغام و نگهداری بسیار آسان است.
  5. پروکسی رایگان است.
  6. نیازی به کد تکراری و کار اضافی نیست.
  7. تست با Koin بسیار آسان می‌باشد.

راه‌اندازی Dagger

مورد دیگر راه اندازی این کتابخانه‌ها است. فرض کنید می‌خواهید از dagger و MVVM استفاده کنید. برای راه اندازی این کتابخانه‌ها به این صورت عمل می‌کنیم:
kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"
پس از ایجاد ماژول‌ها و اجزای سازنده، باید Dagger را در کلاس Application یا متد‌های دیگر initialize کنید.
Class MyApplication : Application(), HasActivityInjector { 
  @Inject
  lateinit var dispatchingAndroidInjector:    DispatchingAndroidInjector<Activity>
override fun activityInjector() = dispatchingAndroidInjector
fun initDagger() {
   DaggerAppComponent
      .builder()
      .application(this)
      .build()
      .inject(this)
  }
}

به همین ترتیب، در همه اکتیویتی‌ها یا در BaseActivity، باید HasSupportFragmentInjector را پیاده‌سازی کنیم و DispatchingAndroidInjector را تزریق کنیم.

برای ViewModelها، باید ViewModelFactory را در BaseFragment تزریق کنیم و همچنین Injectable را پیاده سازی کنیم.

برای هر ViewMode Fragment و Activity باید نحوه‌ی تزریق آن‌ها را به DI اطلاع دهیم. همانطور که مشاهده می‌کنید ActivityModule ،FragmentModule و ViewModelModule را داریم.

ActivityModule:

@Module
abstract class ActivityModule {
    @ContributesAndroidInjector(modules = [FragmentModule::class])
    abstract fun contributeMainActivity(): MainActivity
   
    //Add your other activities here
}

برای فرگمنت‌ها:

@Module
abstract class FragmentModule {
    @ContributesAndroidInjector
    abstract fun contributeLoginFragment(): LoginFragment

    @ContributesAndroidInjector
    abstract fun contributeRegisterFragment(): RegisterFragment

    @ContributesAndroidInjector
    abstract fun contributeStartPageFragment(): StartPageFragment
}
و برای ViewModelها:
@Module
abstract class ViewModelModule {

    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(loginViewModel::class)
    abstract fun bindLoginFragmentViewModel(loginViewModel: loginViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(StartPageViewModel::class)
    abstract fun bindStartPageViewModel(startPageViewModel:  StartPageViewModel): ViewModel
    ......
}
بنابراین شما باید با افزودن فرگمنت‌ها، اکتیویتی‌ها و ViewModelهای جدید به ماژول‌های DI اهمیت دهید، همچنین باید به همه‌ی توسعه دهندگان بگویید تا به این مسئله اهمیت دهند.

راه اندازی Koin

همانطور که می‌دانید ابتدا باید وابستگی Koin را اضافه کنیم.

این خط کد را به Gradle پروژه‌ی خود اضافه کنید.

implementation "org.koin:koin-android-viewmodel:$koin_version"

 شروع به کد نویسی

این نسخه از Koin با ViewModel استفاده می‌شود، وابستگی‌های دیگری نیز برای استفاده از Koin وجود دارد، اما ما می‌خواهیم از آن با MVVM استفاده کنیم.
پس از افزودن وابستگی Koin، ما می‌توانیم اولین ماژول خود را پیاده سازی کنیم، دقیقاً مانند Dagger می‌توانیم همه‌ی ماژول‌ها را در یک فایل جداگانه پیاده سازی کنیم، اما به دلیل سادگی کد، تصمیم گرفتیم همه‌ی ماژول‌ها را در یک فایل پیاده سازی کنیم. بعداً می‌توانید آن‌ها را جدا کنید.
ابتدا باید برخی از نکات مفید درباره‌ی سینتکس koin را بدانیم:
()get: برای حل یک نمونه در یک ماژول Koin، فقط از تابع ()get، به عنوان مولفه‌ی مورد نیاز استفاده کنید. تابع ()get معمولا برای تزریق مقادیر constructor در constructor استفاده می‌شود.
()factory: با هر بار درخواست از مولفه‌ی ()factory، شما نمونه جدیدی را از آن دریافت خواهید کرد.

()single: یک Singleton را برای شما فراهم می‌کند.

name = : برای نامگذاری تعاریف استفاده می‌شود. این مورد زمانی لازم است که بخواهید چندین نمونه از یک کلاس را با انواع مختلفی داشته باشید.
appinjector.kt:
package com.7learn.sevenlearn.di

import com.farshidabz.gettingstartkoin.data.remote.ImagesApi
import org.koin.dsl.module
import retrofit2.Retrofit

private val retrofit: Retrofit = createNetworkClient()


private val IMAGES_API: ImagesApi = retrofit.create(ImagesApi::class.java)

val networkModule = module {
    single { IMAGES_API }
}
NetworkMadule.kt:
package com.7learn.sevenlearn.di

import com.farshidabz.gettingstartkoin.BuildConfig
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

private val sLogLevel =
    if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE

private const val baseUrl = "http://www.splashbase.co/api/v1/"

private fun getLogInterceptor() = HttpLoggingInterceptor().apply { level = sLogLevel }

fun createNetworkClient() =
    retrofitClient(baseUrl, okHttpClient(true))

private fun okHttpClient(addAuthHeader: Boolean) = OkHttpClient.Builder()
    .addInterceptor(getLogInterceptor()).apply { setTimeOutToOkHttpClient(this) }
    .addInterceptor(headersInterceptor(addAuthHeader)).build()

private fun retrofitClient(baseUrl: String, httpClient: OkHttpClient): Retrofit =
    Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(httpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

fun headersInterceptor(addAuthHeader: Boolean) = Interceptor { chain ->
    chain.proceed(
        chain.request().newBuilder()
            .addHeader("Content-Type", "application/json")
            .also {
                if (addAuthHeader) {
//                    it.addHeader("Authorization", wrapInBearer(UserInfoPref.bearerToken))
                }
            }
            .build()
    )
}

private fun setTimeOutToOkHttpClient(okHttpClientBuilder: OkHttpClient.Builder) =
    okHttpClientBuilder.apply {
        readTimeout(30L, TimeUnit.SECONDS)
        connectTimeout(30L, TimeUnit.SECONDS)
        writeTimeout(30L, TimeUnit.SECONDS)
    }
RepositoryMadule.kt:
package com.7learn.sevenlearn.di

import com.farshidabz.gettingstartkoin.data.repository.ImagesRepo
import com.farshidabz.gettingstartkoin.utils.LocationHandler
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

val repositoryModule = module {
    single { ImagesRepo(androidContext(), imagesApi = get()) }

    single { LocationHandler() }
}
ViewModelMadule.kt:
package com.7learn.sevenlearn.di

import com.farshidabz.gettingstartkoin.view.main.MainActivityViewModel
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module



val viewModelModule = module {
    viewModel { MainActivityViewModel(get()) }
}

توضیح کد‌ها:

private val retrofit: Retrofit = createNetworkClient()
createNetworkClient تابعی برای ایجاد نمونه از Retrofit است. تنظیم baseUrl، افزودن ConverterFactory و Interceptor نیز در آنجا انجام می‌شود.
private val generalApi: GeneralApi =  retrofit.create(GeneralApi::class.java)
private val authApi: AuthApi = retrofit.create(AuthApi::class.java)
AuthApi و GeneralApi رابط‌های endpoints هستند. ما فقط endpoint خود را در اینجا اضافه می‌کنیم تا endpoint خود را از هم جدا کنیم.
val viewModelModule = module {
    viewModel { LoginFragmentViewModel(get()) }
    viewModel { StartPageViewModel() }    
}

کلاس viewModels را به عنوان viewModel در یک ماژول تعریف می‌کنیم. Koin یک ViewModel به چرخه‌ی حیات ViewModelFactory می‌دهد و به اتصال آن به current component کمک می‌کند.

همانطور که می‌بینید ما یک متد ()get به سازنده LoginFragmentViewModel داریم. این تابع ساخت نمونه‌ای از LoginFragmentViewModel را تصمیم گیری می‌کند. اینجا AuthRepo است.

در کلاس Application خود در متد ()onCreate کد را بنویسید:
startKoin(this, listOf(repositoryModule, networkModule, viewModelModule))
در اینجا به سادگی متد startKoin را صدا می‌کنیم و در یک context و لیستی از ماژول‌هایی که می‌خواهیم Koin را با آن‌ها initialize کنیم، قرار می‌دهیم.
استفاده از ViewModel بسیار ساده است. در کلاس View خود (Fragment، Activity) این مورد را اضافه کنید:
private val startPageViewModel: StartPageViewModel by viewModel()
با این کد، koin یک شی از StartPageViewModel برای شما ایجاد می‌کند و startPageViewModel را با آن فراخوانی می‌کند. اکنون می‌توانید از ViewModel خود در View استفاده کنید.
جمع بندی:
در این مقاله، به مقایسه Koin و Dagger پرداختیم و آموختیم که koin مزایای خیلی بیشتری نسبت به dagger دارد و کار با آن بسیار ساده‌ است. در صورتی که کار با Dagger بسیار سخت و وقت گیر است. امیدواریم از این مقاله لذت برده باشید. اگر درباره‌ی مقایسه Koin و Dagger سوال و نظری دارید، خوشحال می‌شویم که آن را با ما و کاربران سون لرن به اشتراک بگذارید.

اگر به یادگیری بیشتر در زمینه‌ی برنامه نویسی اندروید علاقه داری، با شرکت در دوره‌ی آموزشی متخصص اندروید در کمتر از یکسال به یک توسعه‌دهنده اندروید همه فن حریف تبدیل می‌شوی که آماده‌ی استخدام، دریافت پروژه و حتی پیاده‌سازی اپلیکیشن خودت هستی.

چه امتیازی به این مقاله می دید؟
ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :

 

نظرات کاربران

پوریا

سلام
مقاله عالی بود
تشکر از شما

وحید گروسی

سلام وقتتون بخیر ممنون از شما برای تهیه این محتوا
اخیرا کتابخانه ای به نام Dagger Hiltمنتشر شده که به مراتب راحت تر از این مورد هست لطفاْ این مورد رو هم بررسی کنید

نازنین کریمی مقدم

درود.
حتما، یه مقاله مقایسه dagger و hilt هم در تقویم محتوایی آینده مون قرار داره.
ممنون که با ما همراه هستید.