Demo sử dụng Koin thay thế cho Dagger trên Kotlin

1. Introduction

Koin – một component mà được tạo ra để thay thế hoặc là một sự lựa chọn tuyệt vời thay cho Dagger khi viết app trong Kotlin . Koin được dùng để Inject các thành phần đến các nơi trong ứng dụng.

https://insert-koin.io/

So sánh với Dagger

Ưu điểm : Cấu hình đơn giản hơn , ít có code generation ,v.v

Nhược điểm : Khi phát sinh lỗi thì lỗi không detail khiến chúng ta hơi khó debug hơn.

2. What you’ll build

Ở bài viết này chúng ta sẽ demo hoàn chỉnh cách sử dụng Koin để call api.

3. Config Koin và các thư viện liên quan và dựng demo

– Thêm thư viện vào build.gradle:

  • Koin
  • Retrofit
  • Lifecycle +viewmodel
 // Koin for Android
    implementation "org.koin:koin-android:$koin_version"
    // Koin Android Scope features
    implementation "org.koin:koin-android-scope:$koin_version"
    // Koin Android ViewModel features
    implementation "org.koin:koin-android-viewmodel:$koin_version"
    // Koin Android Experimental features
    implementation "org.koin:koin-android-ext:$koin_version"
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    // For ViewModel
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

–   Cấu trúc thư mục và các file liên quan:

Tạo model UserEntity.kt

data class UserEntity(
        val id: Long,
        val login: String,
        val avatar_url: String
)

Tạo model LoadingState.kt để xử lý cho loading và error.

data class LoadingState private constructor(val status: Status, val msg: String? = null) {
    companion object {
        val LOADED =
            LoadingState(Status.SUCCESS)
        val LOADING =
            LoadingState(Status.RUNNING)
        fun error(msg: String?) = LoadingState(
            Status.FAILED,
            msg
        )
    }

    enum class Status {
        RUNNING,
        SUCCESS,
        FAILED
    }
}

Tạo class BaseViewModel.kt để dùng chung phần loading.

open class BaseViewModel : ViewModel() {
    val loadingState = MutableLiveData<LoadingState>()
}

Tạo ApiInterface.kt  chứa các API cần sử dụng (retrofit)

interface ApiInterface {

    @GET("users")
    fun getUsers(): Call<List<UserEntity>>
}

Tạo UserRepository.kt để gọi api

class UserRepository(private val apiInterface: ApiInterface) {
    fun getAllUsers() = apiInterface.getUsers()
}

Tạo UserViewModel.kt để lấy data từ UserRepository

class UserViewModel(private val repo: UserRepository) : BaseViewModel() {

    private val _data = MutableLiveData<List<UserEntity>>()
    val data: LiveData<List<UserEntity>>
        get() = _data

    fun fetchData() {
        loadingState.postValue(LoadingState.LOADING)
        repo.getAllUsers().enqueue(object : Callback<List<UserEntity>> {
            override fun onFailure(call: Call<List<UserEntity>>, t: Throwable) {
                loadingState.postValue(LoadingState.error(t.message))
            }

            override fun onResponse(
                    call: Call<List<UserEntity>>,
                    response: Response<List<UserEntity>>
            ) {
                if (response.isSuccessful) {
                    _data.postValue(response.body())
                    loadingState.postValue(LoadingState.LOADED)
                } else {
                    loadingState.postValue(LoadingState.error(response.errorBody().toString()))
                }
            }

        })
    }
}

– Phần quan trọng tạo file Module.kt : dùng Koin để tạo và các Module cho app.

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

val repositoryModule = module {
    single {
        UserRepository(get())
    }
}

val apiModule = module {
    fun provideApi(retrofit: Retrofit): ApiInterface {
        return retrofit.create(ApiInterface::class.java)
    }

    single { provideApi(get()) }
}

val retrofitModule = module {

    fun provideGson(): Gson {
        return GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create()
    }

    fun provideHttpClient(): OkHttpClient {
        val okHttpClientBuilder = OkHttpClient.Builder()

        return okHttpClientBuilder.build()
    }

    fun provideRetrofit(factory: Gson, client: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create(factory))
            .client(client)
            .build()
    }

    single { provideGson() }
    single { provideHttpClient() }
    single { provideRetrofit(get(), get()) }
}

– get() : gọi các thành phần cần thiết cho module của koin

Ex: UserViewModel(get()) Trong demo này UserViewModel cần sử dụng UserRepository và hàm get() của Koin sẽ làm điều đó.

– single: yêu cầu Koin tạo một singleton, và chỉ tạo 1 lần trong quá trình chạy ứng dụng.

2.  Khởi tạo và sử dụng Koin

Giống như Dagger ta tạo 1 file Application và khai báo ở androidmanifest.xml

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@MyApplication)
            modules(listOf(repositoryModule, viewModelModule, retrofitModule, apiModule))
        }
    }
}

Và giờ để sử dụng các thành phần viewmodel chỉ cần inject chúng vào activity hoặc fragment …

class MainActivity : AppCompatActivity() {
    private val userViewModel : UserViewModel by viewModel()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        userViewModel.fetchData()
        userViewModel.data.observe(this, Observer {
            Log.d( "userViewModel","userViewModel.data$it")
        })

        userViewModel.loadingState.observe(this, Observer {
            Log.d( "userViewModel","loadingState$it")
        })
    }
}

Tada xong rồi các bạn ạ, 1 demo đơn giản về cách sử dụng Koin, có vẻ dễ hơn nhiều so với Dagger luôn nha.

Note: Bạn có thể tự demo bằng cách tạo thêm api getRepository.

@GET("/users/{userId}/repos")
fun getRepository(@Path("userId") userId: Int): Call<List<RepoEntity>>

Tham khảo tại: https://medium.com/swlh/dependency-injection-with-koin-6b6364dc8dba

Demo github.

Nguyễn Linh

Chia sẻ để cùng tiến bộ...