تزریق وابستگی در Koin — به زبان ساده
فرض کنید دو کلاس به نامهای A و B داریم که کلاس A با استفاده از متدی در کلاس B به آن اشاره میکند. این عملیات مستقیماً یک وابستگی بین دو کلاس ایجاد میکند، چون پیش از فراهم آمدن امکان استفاده از متد یک کلاس در کلاسهای دیگر لازم است که وهلهای از آن کلاس ایجاد شود. در این مورد لازم است که کلاس A وهلهای از کلاس B را پیش از دسترسی به متدهای آن ایجاد کرده باشد. در یک پروژه ساده، وهلهسازی از اشیا کاری است که میتوان به صورت دستی انجام داد، اما در برخی موارد لازم است که اشیا به صورت خودکار با استفاده از مثلاً یک فریمورک وهلهسازی شوند. بدین ترتیب انتقال وظیفه ایجاد یک شیء به فرد دیگر و استفاده مستقیم از وابستگی همان تزریق وابستگی نامیده میشود.
استفاده از تزریق وابستگی چه اهمیتی دارد؟
اصل پنجم S.O.L.I.D بیان میکند که یک کلاس باید به تجرید وابسته باشد و نه کد سخت. S.O.L.I.D شامل پنج اصل برنامهنویسی شیءگرا است که از سوی Uncle Bob طراحی شده است. معنی گفته فوق این است که یک کلاس، نباید وابستگیهای خود را به صورت استاتیک پیکربندی کند؛ بلکه باید آنها را به وسیله کلاسهایی از خارج از خود پیکربندی کند.
با استفاده از این اصول، کد شما میتواند به روش سادهای تست شود و وراثت بین کلاسهای مختلف آسانتر میشود و کامپوننتهای اپلیکیشن «تزویج سست» (loose coupling) خواهند داشت که در برنامهنویسی اپلیکیشن نکته مهمی محسوب میشود.
Koin چیست؟
Koin (+) یک فریمورک تزریق وابستگی برنامهنویسی شده سبک برای توسعهدهندگان کاتلین است که مسئولیت وهلهسازی اشیای مختلف را در اپلیکیشن بر عهده میگیرد.
استفاده از Koin در پروژههای اندروید
برای درک کامل شیوه استفاده از Koin یک اپلیکیشن کوچک اندرویدی میسازیم که فهرستی از کاربران گیتهاب را که در این آدرس (+) موجود است نمایش میدهد.
معماری اپلیکیشن ما به صورت تصویر زیر خواهد بود. ما قصد نداریم از پایگاه داده SQLite استفاده کنیم و اپلیکیشن مستقیماً با دادههایی که از سرویس وب میآید، بدون این که آنها را در پایگاه داده محلی ذخیره کند، تعامل خواهد کرد.
اگر به تصویر فوق دقت کنید، میبینید که Activity مستقیماً با View-model تعامل مییابد که آن نیز به نوبه خود مستقیماً با Repository تعامل دارد که مسئول تعیین منبع داده است. در مورد این مثال ریپازیتوری تنها یک منبع دارد.
چنان که در تصویر فوق میبینید، کامپوننتهای اصلی اپلیکیشن ما به همراه وابستگیهای بینشان مشخص شدهاند. Activity یک ارجاع به ViewModel دارد و آن نیز به نوبه خود ارجاعی به Repository دارد که از retrofit برای بازیابی دادهها از سرور استفاده میکند. در طول این راهنما با روش مدیریت مؤثر این وابستگیها از سوی Koin آشنا خواهیم شد.
گام 1: افزودن وابستگیهای مورد نیاز در فایل gradle
فایل build.gradle
1// Retrofit
2implementation 'com.squareup.retrofit2:retrofit:2.6.0'
3implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
4
5// We will use it for loading the images
6implementation 'com.squareup.picasso:picasso:2.71828'
7
8// For ViewModel
9implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
10implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0"
11
12// Koin
13implementation "org.koin:koin-android:2.0.1"
14implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
15implementation 'org.koin:koin-androidx-scope:2.0.1'
گام 2: ایجاد کلاسی که کاربر را روی گیتهاب نمایش میدهد
به منظور سادگی اپلیکیشن، تنها سه مورد از اطلاعات کاربر یعنی id، نام کاربری و تصویر او را ذخیره میکنیم:
فایل User.kt
1data class GithubUser(
2 val id: Long,
3 val login: String,
4 val avatar_url: String
5)
گام 3: ایجاد کامپوننتهای اپلیکیشن
اینترفیسی که وبسرویس را نمایش میدهد:
فایل GithubApi.kt
1interface GithubApi {
2
3 @GET("users")
4 fun getUsers(): Call<List<GithubUser>>
5}
کلاس ریپازیتوری به صورت زیر است. یک پارامتر به نام GithubApi دارد.
فایل UserRepository.kt
1class UserRepository(private val api: GithubApi) {
2 fun getAllUsers() = api.getUsers()
3}
مدل نما یک وهله از UserRepository را به عنوان پارامتر میگیرد:
فایل UserViewModel.kt
1class UserViewModel(private val repo: UserRepository) : ViewModel(), Callback<List<GithubUser>> {
2
3 private val _loadingState = MutableLiveData<LoadingState>()
4 val loadingState: LiveData<LoadingState>
5 get() = _loadingState
6
7 private val _data = MutableLiveData<List<GithubUser>>()
8 val data: LiveData<List<GithubUser>>
9 get() = _data
10
11 init {
12 fetchData()
13 }
14
15 private fun fetchData() {
16 _loadingState.postValue(LoadingState.LOADING)
17 repo.getAllUsers().enqueue(this)
18 }
19
20 override fun onFailure(call: Call<List<GithubUser>>, t: Throwable) {
21 _loadingState.postValue(LoadingState.error(t.message))
22 }
23
24 override fun onResponse(call: Call<List<GithubUser>>, response: Response<List<GithubUser>>) {
25 if (response.isSuccessful) {
26 _data.postValue(response.body())
27 _loadingState.postValue(LoadingState.LOADED)
28 } else {
29 _loadingState.postValue(LoadingState.error(response.errorBody().toString()))
30 }
31 }
32}
نکته: ما از کلاس کمکی استفاده کردیم تا بتوانیم حالت بارگذاری را مدیریت کنیم.
فایل LoadingState.kt
1data class LoadingState private constructor(val status: Status, val msg: String? = null) {
2 companion object {
3 val LOADED = LoadingState(Status.SUCCESS)
4 val LOADING = LoadingState(Status.RUNNING)
5 fun error(msg: String?) = LoadingState(Status.FAILED, msg)
6 }
7
8 enum class Status {
9 RUNNING,
10 SUCCESS,
11 FAILED
12 }
13}
گام 4: تعریف یک ماژول Koin
منظور از ماژول در Koin یک کامپوننت است که در آن همه وابستگیهایی که در کامپوننتهای دیگر تزریق میشوند را اعلان میکنیم. برای نمونه مدل نما که در اکتیویتی استفاده میشود، در این ماژول اعلان خواهد شد. بنابراین یک ماژول اعلان میکنیم که برای ما وهلهای از یک «مدل نما» (View Model) میسازد. در ادامه خواهیم دید که عملیاتی که Koin برای ما انجام میدهد، کاهش کد Boiler plate است که در زمان ایجاد وهلهای از مدل نما به صورت پیشفرض ایجاد میکردیم.
برای ایجاد ماژول باید از تابعی به نام module استفاده کنیم که یک لامبدا به عنوان پارامتر میگیرد و باید ارجاعی به ماژول در متغیر داشته باشیم.
فایل Module.kt
1val viewModelModule = module {
2 viewModel {
3 UserViewModel(get())
4 }
5}
6
7val repositoryModule = module {
8 single {
9 UserRepository(get())
10 }
11}
12
13val apiModule = module {
14 fun provideUseApi(retrofit: Retrofit): GithubApi {
15 return retrofit.create(GithubApi::class.java)
16 }
17
18 single { provideUseApi(get()) }
19}
20
21val retrofitModule = module {
22
23 fun provideGson(): Gson {
24 return GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create()
25 }
26
27 fun provideHttpClient(): OkHttpClient {
28 val okHttpClientBuilder = OkHttpClient.Builder()
29
30 return okHttpClientBuilder.build()
31 }
32
33 fun provideRetrofit(factory: Gson, client: OkHttpClient): Retrofit {
34 return Retrofit.Builder()
35 .baseUrl("https://api.github.com/")
36 .addConverterFactory(GsonConverterFactory.create(factory))
37 .client(client)
38 .build()
39 }
40
41 single { provideGson() }
42 single { provideHttpClient() }
43 single { provideRetrofit(get(), get()) }
44}
در کد فوق از چند تابع Koin برای مدیریت وابستگیهای مختلف اپلیکیشنمان بهره میگیریم.
- ViewModel: یک کامپوننت ViewModel اعلان میکنیم و آن را به چرخه عمر کامپوننت اندروید اتصال میدهیم.
- ()Get: در این مثل کلاس UserViewModel باید وهلهای از UserRepository را به عنوان پارامتر بگیرد و از این رو از get() در Koin استفاده میکنیم تا آن را به دست آوریم. این متد تنها زمانی کار میکند که یک ماژول وجود داشته باشد که این وابستگی را ارائه کند، در غیر این صورت Koin وابستگی را پیدا نمیکند.
- Single: به Koin اعلام میکند که یک سینگلتون ایجاد کند. این وهله تنها در زمان اجرای اپلیکیشن ایجاد خواهد شد.
گام 5: آغاز به کار Koin
اکنون که ماژولها آماده شدهاند، میتوانیم شروع به کار با Koin بکنیم. ابتدا کلاس اپلیکیشن را باز و یا آن را ایجاد کنید. توجه داشته باشید که باید آن را در manifest.xml اعلان کنید. کافی است تابع startKoin() را که یک لامبدا میگیرد فراخوانی کنید و در آن لیستی از ماژولها را با استفاده از تابع modules(module: List<Module>) تعریف کنید:
فایل App.kt
1class App : Application() {
2 override fun onCreate() {
3 super.onCreate()
4 startKoin {
5 androidLogger(Level.DEBUG)
6 androidContext(this@App)
7 modules(listOf(repositoryModule, viewModelModule, retrofitModule, apiModule))
8 }
9 }
10}
گام 6: تزریق وابستگیها
کامپوننت UserViewModel با استفاده از وهله UserRepository ساخته میشود. برای این که آن را وارد اکتیویتی بکنید، باید آن را با استفاده از تزریق کننده واسط by viewModel() تزریق کنید:
فایل MainActivity.kt
1class MainActivity : AppCompatActivity() {
2
3 private val userViewModel by viewModel<UserViewModel>()
4
5 override fun onCreate(savedInstanceState: Bundle?) {
6 super.onCreate(savedInstanceState)
7 setContentView(R.layout.activity_main)
8
9 userViewModel.data.observe(this, Observer {
10 // Populate the UI
11 })
12
13 userViewModel.loadingState.observe(this, Observer {
14 // Observe the loading state
15 })
16 }
17}
سخن پایانی
Koin در همه جا از اپلیکیشنهای موبایل تا وب موجب سهولت تزریق وابستگی میشود و نباید در مورد استفاده از آن نگرانی داشته باشید. Koin یک فریمورک قوی است که به افزایش بهرهوری کار با کاتلین کمک میکند.
اگر این نوشته برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی اندروید
- گنجینه برنامهنویسی اندروید (Android)
- مجموعه آموزشهای برنامهنویسی
- ساخت اپلیکیشن ضبط صدا با کاتلین (Kotlin) — به زبان ساده
- برنامهنویسی اندروید با کاتلین — راهنمای شروع به کار
==
سلام
یک سوال داشتم.
در پیکاسو ورژن جدید دیگر ما کانتکست بهwith پاس نمی دهیم.یعنی دیگر تزریق وابستگی نداریم؟
توی کدهای شما نگاه کردم این تزریق وابستگی انجام نشده بود.
لطفا تزریق وابستگی ورژن جدید پیکاسو را توضیح دهید.
سوال بعدی اینکه در
startKoin{
androidLogger(Level.DEBUG)}
چه کاری انجام می دهد ؟
توضیح ندادید.وجود این متد لازمه؟
ممنون میشم راهنمایی کنید