![Внедрение зависимостей в Android с помощью Koin Внедрение зависимостей в Android с помощью Koin](https://nuancesprog.ru/wp-content/uploads/2024/05/Android-pattern-1-696x392.png)
![](https://nuancesprog.ru/wp-content/uploads/2024/05/image-139.png)
Внедрение зависимостей (Dependency Injection, DI) можно определить как один из пяти принципов SOLID. Этот принцип помогает управлять зависимостями. Dagger-Hilt — первое, что приходит на ум большинству из нас в связи с этой темой. Dagger-Hilt, разработанный Google, позволяет эффективно внедрять зависимости в Android-приложениях. Однако Koin — инструмент, о котором здесь пойдет речь, — справляется с этой задачей довольно хорошо.
Что такое Koin
Koin — это легковесный DI-фреймворк для Kotlin-разработчиков. Написанный на языке Kotlin, он поддерживает функцию Kotlin DSL (domain-specific language — предметно-ориентированный язык). Освоение этого простого DI-фреймворка не требует много сил и времени. По сравнению с Dagger-Hilt, Koin гораздо проще в использовании. Главное отличие между этими фреймворками заключается в том, что в Dagger-Hilt внедрение зависимостей происходит во время компиляции, а в Koin — во время выполнения. Именно поэтому Dagger-Hilt более широко используется на практике. Однако, учитывая, что Koin также довольно популярен в среде разработчиков, его следует изучить.
Итак, погрузимся в Koin и посмотрим пример на его основе. Напишем простое приложение для получения данных из API JSONPlaceholder.
Вот пример.
Шаг 1. Добавление зависимости в build.gradle
// Корутины
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
// Koin
implementation "io.insert-koin:koin-android:$koin_version"
implementation "io.insert-koin:koin-androidx-navigation:$koin_version"
testImplementation "io.insert-koin:koin-test-junit4:$koin_version"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
Шаг 2. Создание класса данных MyModelItem
Создадим класс модели для ответа. Он представляет собой ответ от API.
data class MyModelItem(
@SerializedName("body")
val body: String,
@SerializedName("id")
val id: Int,
@SerializedName("title")
val title: String,
@SerializedName("userId")
val userId: Int
)
Шаг 3. Определение интерфейса MyApi
Создадим GET-запрос, а также функцию приостановки (suspend function).
interface MyApi {
@GET("posts")
suspend fun doNetworkCall(): Response<List<MyModelItem>>
}
Шаг 4. Создание файла AppModule
По сравнению с Dagger-Hilt, создание синглтона в Koin выполняется намного проще с помощью функции области видимости (scope function). Внутри AppModule можно напрямую получить ранее внедренный объект с помощью метода get(). Можно также использовать viewModel для создания области видимости ViewModel.
val appModule = module {
// область видимости синглтона
single {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MyApi::class.java)
}
single<MyRepository> {
MyRepositoryImpl(get())
}
viewModel {
MyViewModel(get())
}
}
Кроме того, при использовании области видимости фабрики, можно создавать новый экземпляр при каждом внедрении.
val appModule = module {
factory{
...
}
}
Шаг 5. Класс данных Resource
Мы создали класс данных Resource, который позволяет возвращать статус любого типа. Теперь можно изменить возврат в репозитории (Repository).
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
enum class Status {
SUCCESS,
ERROR,
LOADING
}
Шаг 6. Интерфейс MyRepository
interface MyRepository {
suspend fun doNetworkCall(): Resource<List<MyModelItem>>
}
Шаг 7. Класс MyRepositoryImpl
class MyRepositoryImpl(private val api: MyApi) : MyRepository{
override suspend fun doNetworkCall(): Resource<List<MyModelItem>> {
return try {
val response = api.doNetworkCall()
if(response.isSuccessful){
response.body()?.let {
return@let Resource.success(it)
} ?: Resource.error("Error", null)
}else{
Resource.error("Error", null)
}
}catch (e: Exception){
Resource.error("No Data!", null)
}
}
}
Шаг 8. Класс MyViewModel
Можем получать данные и наблюдать их в Activity с помощью LiveData.
class MyViewModel(
private val repository: MyRepository
) : ViewModel() {
val myModelItem = MutableLiveData<Resource<List<MyModelItem>>>()
fun doNetworkCall(){
CoroutineScope(Dispatchers.IO).launch {
val resource = repository.doNetworkCall()
withContext(Dispatchers.Main){
resource.data?.let {
myModelItem.value = resource
}
}
}
}
}
Шаг 9. Создание класса Application
Мы еще не инициализировали Koin. Когда приложение будет создано, проведите его инициализацию. Сначала нужно предоставить модуль.
class MyApplication: Application(){
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}
Не забудьте добавить класс Application в файл AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApplication"
.
.
.
</application>
</manifest>
Шаг 10. MainActivity
Теперь остается только внедрить ViewModel и наблюдать за данными!
class MainActivity : AppCompatActivity(), AndroidScopeComponent {
override val scope: Scope by activityScope()
private val viewModel by viewModel<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.doNetworkCall()
observeLiveData()
}
private fun observeLiveData(){
viewModel.myModelItem.observe(this) { item ->
item?.let {items->
items.data?.forEach {item->
println("id: ${item.id}")
println("body: ${item.body}")
println("title: ${item.title}")
println("userId: ${item.userId}")
}
}
}
}
}
В общем, по сравнению с Dagger-Hilt, Koin действительно прост в использовании. Однако, как уже упоминалось, у него есть и некоторые недостатки. Решение за вами!
Читайте также:
- MVVM на Android с компонентами архитектуры + библиотека Koin
- Релиз Koin 1.0.0
- 10 советов по созданию чистого кода для мобильной разработки на Kotlin в 2024 году
Читайте нас в Telegram, VK и Дзен
Перевод статьи Metehan Gokbel: Dependency Injection In Android With Koin