آموزش استفاده کردن از Koin (تزریق وابستگی) در اندروید

آموزش استفاده کردن از Koin (تزریق وابستگی) در اندروید
در این پست می‌خوانید:

Koin یکی از کتابخونه ها و روش های موجود برای تزریق وابستگی در اندروید هستش.

اندروید روش ها و کتابخونه های مختلفی رو برای تزریق وابستگی ها داره، کتابخونه هایی مثل : DaggerHiltKoinKodein و …

هر کدوم از این روش ها یکسری مزایا و معایب هایی رو دارن.

از بین این کتابخونه ها به نظر من Hilt و Koin خیلی خوب عمل کردن و الان دیگه تقریبا 90درصد پروژه ها از این روش ها استفاده میکنند.

Dagger یک کتابخونه خوب و بزرگ برای انجام تزریق وابستگی های هستش، ولی خیلی خیلی پیچیده‌ست و یادگیریش هم زمانبره و دیگه خیلی از برنامه نویس ها ازش استفاده نمیکنن.

توی این پست قراره که در مورد روش کوین آموزش بدم و توی پست های بعدی هم حتما راجب هیلت پست آموزشی درست میکنم.

دونستن این 2 تا واقعا بهتون توی اسخدام و گرفتن پروژه های اندروید کمک زیادی میکنه.

برای تزریق وابستگی Koin یک دوره جامع و کامل هم آماده شده که میتونی از لینک زیر سرفصل های اون رو ببینی.

تزریق وابستگی چیه؟

قبل از اینکه بخوام راجب کوین توضیح بدم بهتره که اول از همه بدونی که اصلا تزریق وابستگی یعنی چی؟ چرا باید ازش استفاده کنیم؟

فرض کن که 2 تا کلاس داریم، کلاس User و کلاس UserInfo.

کلاس UserInfo وظیفه نمایش دادن اطلاعات کاربر رو برعهده داره و کلاس User هم اطلاعات کاربر رو ذخیره میکنه.

برای اینکه ما بتونیم اطلاعات کاربر رو به کمک کلاس UserInfo نشون بدیم، اول از همه باید اطلاعات رو از کلاس User دریافت کنیم.

به کلاس های زیر خوب توجه کن.

کلاس User :

class User {
    val name = "Mohammad"
    val family = "Nouri"
    val site = "https://nouri.academy"
}

کلاس UserInfo :

class UserInfo(private val user: User) {
    fun userName() = "${user.name} ${user.family}"

    fun siteAddress() = user.site
}

اگه به کلاس userInfo توجه کنی میبینی که 2 تا fun داره که وظیفه نمایش نام کاربر و نام سایت رو برعهده دارن.

این اطلاعات از چه کلاسی تامین میشه؟ از کلاس User.

کلاس User در سازنده کلاس UserInfo قرار داده شده که بتونیم به اطلاعات کاربر دسترسی داشته باشیم.

این یعنی وابستگی! یعنی کلاس UserInfo برای اینکه بتونه اطلاعات کاربر رو نشون بده به کلاس User وابستگی داره.

این مثال یک وابستگی از نوع کلاس هستش.

ما انواع وابستگی ها رو داریم :

  • وابستگی به کلاس
  • وابستگی به فانکشن (متد)
  • وابستگی به متغیر

حالا که با مفهوم وابستگی آشنا شدی، بهتره که بریم ببینیم Koin چطور میتونه توی این زمینه به ما کمک منه.

اضافه کردن Koin به پروژه

برای اینکه بتونی از کوین استفاده کنی، باید اون رو به پروژه خودت اضافه کنی.

اول از همه کد زیر رو به فایل project/build.gradle اضافه کن

repositories {
    mavenCentral()
}

سپس برای اضافه کردن کوین با توجه به مواردی که توی پروژه بهشون نیاز داری باید از کدهای زیر توی module/build.gradle استفاده کنی.

برای Kotlin

// هسته اصلی کوین
implementation "io.insert-koin:koin-core:3.1.6"
// کوین برای تست نویسی
testImplementation "io.insert-koin:koin-test:3.1.6"

برای JUnit

// کوین برای JUnit 4
testImplementation "io.insert-koin:koin-test-junit4:3.1.6"
// کوین برای JUnit 5
testImplementation "io.insert-koin:koin-test-junit5:3.1.6"

برای Android

// کوین برای اندروید
implementation "io.insert-koin:koin-android:3.1.6"

// کوین برای جاوا
implementation "io.insert-koin:koin-android-compat:3.1.6"
// برای Jetpack WorkManager
implementation "io.insert-koin:koin-androidx-workmanager:3.1.6"
// برای Navigation Graph
implementation "io.insert-koin:koin-androidx-navigation:3.1.6"
// برای Jetpack Compose
implementation "io.insert-koin:koin-androidx-compose:3.1.6"
نکته
اصلا نیاز نیست که تمامی کدها رو به Gradle پروژه اضافه کنی.
در 95درصد مواقع اضافه کردن هسته اصلی کوین و کوین اندروید کافی هستش.

نحوه تزریق وابستگی ها به کمک کوین

طبق گفته های سازنده های کوین، این کتابخونه میتونه فقط توی 3 مرحله وابستگی های مختلف رو برای ما تزریق کنه (واقعا هم میتونه همین کار رو بکنه ? ، فقط توی 3 مرحله).

  1. اولین قدم اینه که فایل Module (ماژول) رو درست کنی و چیزهایی که میخوای تزریق کنی رو اونجا قرار بدی.
  2. سپس فایل ماژول رو باید توی کلاس Application معرفی کنی.
  3. درنهایت هم کلاس ها رو به کمک دستور inject توی صفحه ای که میخوای ازشون استفاده کنی تزریق کنی.

بعد از خوندن این 3 مرحله شاید برات سوال باشه که Module و inject چیه؟ اصلا چرا باید استفاده کنیم؟

Module چیست؟

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

وابستگی های مختلف، اعم از کلاس، فانکشن و متغیر رو داخل ماژول تعریف میکنیم و ماژول هم وظیفه تامین کردن اونارو بر عهده داره.

inject چیست؟

وابستگی هایی که توسط ماژول تامین شدن رو به کمک inject (اینجکت) میتونیم توی Activity / Fragment استفاده کنیم.

آموزش استفاده کردن از Koin

همانطور که توی مثال بالا که مربوط به اطلاعات کاربر بود بهتون توضیح دادم، برای اینکه UserInfo بتونه اطلاعات رو نشون بده به کلاس User وابستگی داره.

حالا بریم ببینیم که چطور میتونیم وابستگی کلاس های بالا رو به کمک کوین تامین کنیم.

هر دو کلاس بالا یعنی User و UserInfo رو مانند مثال بالا تعریف میکنیم و کاری بهشون نداریم.

الان میخوایم با 3 مرحله ای که کوین گفته وابستگی ها رو تامین کنیم.

مرحله اول

اولین مرحله ساختن ماژول بود.

برای اینکار میتونیم یک فایل جدید درست کنیم و کلاس ها رو داخلش قرار بدیم.

من یک فایل جدید با نام UserModule درست کردم و کدهای زیر رو هم داخل قرار دادم.

val userModule = module {
    single { User() }
    single { UserInfo(get()) }
}

برای اینکه بتونی از ماژول کوین استفاده کنی باید اون رو داخل یک متغیری بریزی، برای همین هستش که من داخل فایل UserModule یک متغیر با نام userModule درست کردم و اون رو از نوع module قرار دادم.

نکته
نیازی نیست که اسم های فایل و متغیر هم نام باشن.
میتونی از هر اسمی که میخوای براشون قرار بدی.
مثلا :
نام فایل : MyModule
نام متغیر : userModule

انواع روش های تامین وابستگی در ماژول

توی کوین ما 2 روش برای تامین کردن وابستگی ها داریم :

  • single : به صورت سینگلتون تامین میکنه (یکبار در روند کل اجرای اپلیکیشن)
  • factory : در هربار صدا کردن یک آبجکت جدید ازش میسازه.

توی فایل بالا یعنی UserModule من هردو کلاس رو توسط single تامین کردم.

کلاس User چون وابستگی به چیزی نداشت برای همین در ورودی این کلاس چیزی رو قرار ندادم.

ولی کلاس UserInfo برای ساخته شدن به کلاس User وابستگی داشت.

چون وابستگی کلاس User رو تامین کردیم، پس خیلی راحت به کمک کلمه کلیدی get (جز کلمات کلیدی کوین هستش) میتونی وابستگی کلاس UserInfo رو تامین کنیم.

درواقع با نوشتن کلمه get ، کوین به صورت خودکار وابستگی مورد نیاز رو (در صورتی که قبلا تامین شده باشه) تامین میکنه.

بعد از اینکه وابستگی ها رو توی ماژول فراهم کردیم، نوبت این رسیده که اون ماژول رو توی کلاس Application تعریف کنیم.

مرحله دوم

برای اینکار باید کلاس اپلیکیشن رو درست کنی و داخل متد onCreate اون کد زیر رو قرار بدی.

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        //Koin
        startKoin {
            modules(userModule)
        }
    }
}

 

برای اینکه بتونی ماژول رو توی کلاس Application فراخوانی کنی، باید توی onCreate متد startKoin رو بنویسی.

با نوشتن startKoin درواقع به کوین میگیم که ماژول هایی که توی این متد نوشته میشن رو به اپلیکیشن تزریق کنه.

نکته
هر چندتا ماژولی که داریم رو باید داخل modules بنویسیم، فقط کافیه که با , از هم جداشون کنیم.
مثال : modules(userModule, networkModule, databaseModule)

از اونجایی که context جز مواردی هستش که خیلی توی پروژه ها ازش استفاده میشه، کوین برای تزریق اونم یک روشی رو معرفی کرده.

فقط کافیه که داخل startKoin از کد androidContext استفاده کنی.

مثالش رو میتونی توی کد زیر ببینی :

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        
        startKoin {
            //Context
            androidContext(this@MyApp)
            //User
            modules(userModule)
     }
}
یادت نره که کلاس اپلیکیشن رو توی فایل Manifest پروژه تعریف کنی.

مرحله سوم

خب الان دیگه نوبت این رسیده که ازشون استفاده کنیم.

برای استفاده کردن از مواردی که وابستگی هاشون رو تامین کردیم، باید وارد Activity یا Fragment بشیم و به کمک inject اون کلاس/متد/متغیر رو تزریق کنیم.

برای اینکار میتونم از کد زیر استفاده کنیم :

private val userInfo: UserInfo by inject()

به همین راحتی دوستان. ما اومدیم کلاس UserInfo که وظیفه نمایش دادن اطلاعات رو برعهده داره رو توی Activity تزریق کردیم.

این کلاس به طور خودکار با کلاس User ارتباط برقرار میکنه و اطلاعات رو دریافت میکنه. بدون اینکه نیاز باشه خودمون به صورت دستی بخوایم کلاس User رو تعریف کنیم و اون رو به UserInfo بدیم.

کد کامل کلاس Activity :

class MainActivity : AppCompatActivity() {
    //Binding
    private lateinit var binding: ActivityMainBinding
    //Inject
    private val userInfo: UserInfo by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.infoTxt.text = userInfo.siteAddress()
    }
}

به همین راحتی دوستان. کار دیگه تمومه!

خیلی راحت ما تونستیم وابستگی که بین User و UserInfo بود رو تامین کنیم و ازش توی صفحه مورد نظر استفاده کنیم.

تزریق کردن کلاس همراه با اینترفیس (Interface)

بعضی مواقع پیش میاد که ما میخوایم کلاسمون از اینترفیس هم ارث بری کنیم.

برای تزریق این نوع کلاس ها فقط کافیه که دستورات داخل ماژول رو به شکل زیر تغییر بدی:

val bmwModule = module {
    factory { CarImpl() } bind Car::class
}

بقیه موارد دقیقا مثل روش قبل هستن.

درواقع با نوشتن bind ما اون اینترقیس رو هم معرفی میکنیم.

اعمال محدوده اجرایی (Scope) در موارد تزریق شده

کوین به 4 حالت مختلف میتونه Scope موارد تزریق شده رو برای ما فراهم کنه.

  • activityScope : به چرخه حیات Activity آگاه است.
  • activityRetainedScope : به چرخه حیات ViewModel مربوط به Activity آگاه است.
  • fragmentScope : به چرخه حیات Fragment آگاه است.
  • serviceScope : به چرخه حیات Service آگاه است.

زمانی که میخوای از Scope استفاده کنی، روش تعریف کردن و دسترسی داشتن به موارد تزریق شده یکم متفاوت میشه.

به کدهای فایل ماژولی که پایین مینویسم خوب توجه کن :

val personModule = module {
    scope<ScopesActivity> {
        scoped<PersonInfo>()
    }
}

همونظور که متوجه شدی، زمانی که میخوای از scope استفاده کنی روش تزریق کردنش یکم با حالت قبلی متفاوت میشه.

اینجا دیگه از single یا factory استفاده نمی‌کنیم و بجای اون باید از scope  و scoped استفاده کنیم.

scope درواقع اون Activity یا Fragment رو مشخص میکنه که میخوایم فایل تزریق شدمون با توجه به چرخه حیات اون صفحه آگاه باشه.

برای همین من اسم اکتیویتی خودم یعنی ScopesActivity رو داخل scope نوشتم.

بعد از اون به کمک scoped که داخل scope نوشته میشه، کلاس/متد/متغیر مورد نظرمون رو بهش معرفی میکنیم.

به توضیحات و موارد استفاده scope و scoped خوب توجه کنی.، اینا باهم یکی نیستن.

برای استفاده کردنش داخل Activity یا Fragment هم مجددا با روش قبلی یکم متفاوت هستش.

باید اول صفحه مورد نظرت رو از KoinScopeComponent ارث بری کنی.

بعد از ارث بری کردن یک متغیر با عنوان override val scope : Scope به Activity / Fragment اضافه میشه.

با توجه به 4 حالت بالا میتونی سطح دسترسی scope رو مشخص کنی، مانند کد زیر :

override val scope: Scope by activityScope()

در نهایت برای تزریق کردن کلاس هم بجای اینکه از inject استفاده کنی، باید از scope.inject استفاده کنی. مثل کد زیر :

class ScopesActivity : AppCompatActivity(), KoinScopeComponent {
    //Binding
    private lateinit var binding: ActivityScopesBinding

    //Inject
    override val scope: Scope by activityScope()
    private val person: PersonInfo by scope.inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityScopesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.infoTxt.text = person.showInfo()
    }
}

 

سعی کردم که تزریق وابستگی به کمک Koin رو خیلی ساده و کاربردی یاد بدم.

ولی اگر میخوای به صورت کامل و عملی همراه با پروژه های Room  و Retrofit نحوه کار کردن Koin رو یاد بگیری، پیشنهاد میکنم حتما یکسری به لینک زیر بزنی : ???

دیدگاه‌ها ۰
ارسال دیدگاه جدید