Android dependency injection koin example

The following is a walk through for setting up dependency injection using the library Koin for an Android project. The finished project will be a one screen app, where you can type in a github account name and it will fetch the account detail and display it.

The gradle file

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.android_di_koin_example"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

ext.versions = [
    "androidx_lifecycle": "2.2.0-rc03",
    'koin': '2.0.1' ,
    'retrofit': '2.6.1' ,
    'okhttp': '4.0.1'
]

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-livedata:$versions.androidx_lifecycle"
    implementation "androidx.lifecycle:lifecycle-viewmodel:$versions.androidx_lifecycle"
    implementation "androidx.lifecycle:lifecycle-extensions:$versions.androidx_lifecycle"
    kapt "androidx.lifecycle:lifecycle-compiler:$versions.androidx_lifecycle"

    //Okhttp
    implementation "com.squareup.okhttp3:okhttp:$versions.okhttp"
    implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttp"

    //Retrofit
    implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
    implementation "com.squareup.retrofit2:converter-gson:$versions.retrofit"
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

    // coroutines
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc03"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

    // Retrofit 2 for network tasks
    implementation 'com.squareup.okhttp3:logging-interceptor:4.0.1'
    implementation 'com.google.code.gson:gson:2.8.6'

    //Koin
    implementation "org.koin:koin-android:$versions.koin"
    implementation "org.koin:koin-androidx-scope:$versions.koin"
    implementation "org.koin:koin-androidx-viewmodel:$versions.koin"

}

GithubAccount.kt, the data class for modeling the github account data.

import com.google.gson.annotations.SerializedName
data class GithubAccount(
    @SerializedName("login") var login : String = "",
    @SerializedName("id") var id : Int = 0,
    @SerializedName("avatar_url") var avatarUrl : String = "",
    @SerializedName("created_at") var createdAt : String = "",
    @SerializedName("updated_at") var updatedAt : String = "")

GithubApi.kt, the Retrofit interface for the github api.

import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path

interface GithubApi {

    @GET("/users/{username}")
    fun fetchGithubAccountAsync(@Path("username") username: String): Deferred>

}

GithubRepository.kt, the repository for managing the api calls.

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext

class GithubRepository(private val githubApi: GithubApi) {

    suspend fun getGithubAccountAsync(accountName: String): Deferred {
        return withContext(Dispatchers.IO) {
            async {
                try {
                    // for demo purpose, hence no error checking
                    githubApi.fetchGithubAccountAsync(accountName).await().body() as GithubAccount
                } catch (e: Exception) {
                    e.printStackTrace()
                    null
                }
            }
        }
    }

}

The above are just for setting up the project, here starts the Koin configuration. First, the NetworkModule.kt, provides the components needed for REST services. single means provide a singleton.

val networkModule = module {

    // Provide Gson
    single {
        GsonBuilder().create()
    }

    // Provide HttpLoggingInterceptor
    single {
        HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }
    }

    // Provide OkHttpClient
    single {
        val cacheDir = File((get() as MyApp).cacheDir, "http")
        val cache = Cache(
            cacheDir,
            10 * 1024 * 1024 // 10 MB
        )

        OkHttpClient.Builder()
            .cache(cache)
            .addInterceptor(get())
            .build()
    }

    // Provide Retrofit
    single {
        Retrofit.Builder()
            .client(get())
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create(get()))
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
    }

    // Provide GithubApi
    single {
        get().create(GithubApi::class.java)
    }
}

RepositoryModule.kt

val repositoryModule = module {

    // Provide GithubRepository
    single {
        GithubRepository(get())
    }

}

ViewModelModule.kt

val vmModule = module {

    // Provide MainActivityViewModel
    viewModel { MainActivityViewModel(get()) }

}

Initializing Koin with the desired modules in the application class.

class MyApp : Application() {

    private val appModules = listOf(
        repositoryModule,
        networkModule,
        vmModule
    )

    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@MyApp)
            modules(appModules)
        }
    }

}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val mainActivityViewModel: MainActivityViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainActivityViewModel.githubAccount.observe(this, Observer {
            tv_content.text = it.toString()
        })

        btn_fetch.setOnClickListener {
            mainActivityViewModel.fetchAccount(et_account.text.toString())
        }
    }

}

MainActivityViewModel.kt

class MainActivityViewModel
constructor(private val githubRepository: GithubRepository): ViewModel() {

    private val _githubAccount = MutableLiveData()
    val githubAccount: LiveData get() = _githubAccount

    fun fetchAccount(accountName: String) {

        viewModelScope.launch {
            val githubAccount = githubRepository.getGithubAccountAsync(accountName).await()
            githubAccount?.let {
                _githubAccount.value = it
            }
        }

    }

}

Complete example in Github

Search within Codexpedia

Custom Search

Search the entire web

Custom Search