Dependency Injection with Koin

Dependency Injection with Koin

Figure 1

What is dependency Injection ?

Suppose we have two classes, the Class A and Class B, when the class A refers to class B using some method of class B this operation directly creates a dependency between the two classes and before using the method of a class it will be necessary to create the instance of this class, in our case, the class A will have to create an instance of the class B before to accessing to its methods

For a simple project the instantiation of the objects is often an operation that can be done manually, but in some cases it will be necessary to instantiate the objects automatically by using for example a framework

So, transferring the task of creating the object to someone else and directly using the dependency is called dependency injection.

Figure 2Figure 2

Why is important to use dependency injection ?

The fifth principle of S.O.L.I.D — the five basic principles of object-oriented programming and design by Uncle Bob — which states that a class should depend on abstraction and not upon concretions (in simple terms, hard-coded).

Robert Cecil Martin, colloquially known as “Uncle Bob”, is an American software engineer and instructor. He is best known for being one of the authors of the Agile Manifesto and for developing several software design principles. It means that a class should not configure its dependencies statically but should be configured by some other class from outside.

By respecting these principles your code will be easy to test, the inheritance between different class will be easy, the component of your application will have loose coupling, which is important in application programming.

You can learn more about SOLID principle here

What is Koin ?

Koin is a pragmatic lightweight dependency injection framework for Kotlin developers to whom we will give the responsibility to instantiate the different objects of our application.

Use Koin in Android project

To fully understand how to use Koin, we will develop a small android application that displays a list of github users available here

We will develop our applications with an architecture below, even if we are not going to use SQLite database in our case, the application will directly interact with data coming from web service without save it in a local database

Figure 3Figure 3

If we observe the Figure 3, we will see that the Activity will communicate directly with the View-model that will interact directly the Repository that have the responsibility the determine the source of the data, in our case the repository will only have one source.

As you can see, Figure 3 determines the main components of our application as well as dependencies between them, the Activity will have a reference of the ViewModel which will in turn have the reference of the Repository that will use retrofit to retrieve the data on the server, and during this tutorial we’ll see how Koin can help us manage these dependencies effectively.

Step 1: Adding needed dependencies in the gradle file

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

// We will use it for loading the images
implementation 'com.squareup.picasso:picasso:2.71828'

// For ViewModel
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0"

// Koin 
implementation "org.koin:koin-android:2.0.1"
implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
implementation 'org.koin:koin-androidx-scope:2.0.1'

Step 2: Create a class that represent a user on Github

For the sake of simplicity we will keep only three user information, the id, the login and the image of the user

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

Step 3: Create the components of the application

A interface that represent the Web Service

interface GithubApi {

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

The repository class will look like this, it has the GithubApi as parameter

class UserRepository(private val api: GithubApi) {
    fun getAllUsers() = api.getUsers()
}

The View Model take a instance of UserRepository as parameter

class UserViewModel(
    private val repo: UserRepository
) : ViewModel(), Callback<List<GithubUser>> {

    private val _loadingState = MutableLiveData<LoadingState>()
    val loadingState: LiveData<LoadingState>
        get() = _loadingState

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

    init {
        fetchData()
    }

    private fun fetchData() {
        _loadingState.postValue(LoadingState.LOADING)
        repo.getAllUsers().enqueue(this)
    }

    override fun onFailure(call: Call<List<GithubUser>>, t: Throwable) {
        _loadingState.postValue(LoadingState.error(t.message))
    }

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

Note: I use a helper class that allow me to manage the state of the loading

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
    }
}

Step 4: Define a Koin Module

In Koin a module is a component in which we are going to declare all the dependencies that will be injected in another components, for example the View Model will be use in the activity, so we have declare a module that will create for us an instance of a View Model. we will see that it’s operation that koin will do for us by reducing the Boiler plate code we create when we want to create an instance of a View Model by default, thank to Koin for that

For creating module you have to use a function called module that take a lambda as parameter and we have to keep the reference of the module in the variable

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

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

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

    single { provideUseApi(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()) }
}

In the code above, we use several koin function to manage the different dependencies of our application

  1. viewModel : declare a ViewModel component and bind it to an Android Component lifecycle.

  2. get() : In our case, the UserViewModel classe need a instance of UserRepository as parameter so we use the get() to tell koin to retrieve it for us, it will work only if there is a module that provide this dependence, otherwise Koin will not found the dependency and your application will crash

  3. single : tell to Koin to create a singleton, the instance will be created only once during the execution of the application

Step 5: Start Koin

Now that we have our modules, let’s start it with Koin. Open your application class, or make one (don’t forget to declare it in your manifest.xml). Just call the startKoin() function that take a lambda in which we define a list of module using modules(module: List&lt;Module&gt;)function

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

Step 6: Injecting dependencies

The UserViewModel component will be created with UserRepository instance. To get it into our Activity, let’s inject it with the by viewModel() delegate injector:

class MainActivity : AppCompatActivity() {

    private val userViewModel by viewModel<UserViewModel>()

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

        userViewModel.data.observe(this, Observer {
            // Populate the UI
        })

        userViewModel.loadingState.observe(this, Observer {
            // Observe the loading state
        })
    }
}

Conclusion

From mobile to web application, Koin bring easy dependency injection for your app, don’t hesitate to use it’s a powerful framework that can help you to increase your productivity with Kotlin.

If you have any kind of feedback, feel free to connect with me on Twitter.

References

A quick intro to Dependency Injection: what it is, and when to use it by Bhavya Karia A quick intro to Dependency Injection: what it is, and when to use it Photo by rawpixel…freecodecamp.org

Guide to app architecture | Android Developers This guide encompasses best practices and recommended architecture for building robust, production-quality apps. This…developer.android.com

insert-koin.io A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional…insert-koin.io

Did you find this article valuable?

Support Eric Ampire by becoming a sponsor. Any amount is appreciated!