Dependency Injection With Koin

In this tutorial, you’ll get to know Koin, one of the most popular new frameworks for dependency injection. By Pablo L. Sordo Martinez.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Finishing Up!

Last, you’ll work on the repository module — specifically, the singleton class AppRepository. When you open it up, you will see a few TODOs to get rid off. Time to tackle them!

Start by adding these two constants at the beginning of the file, before the class definition, so that they are available when needed:

private const val MSG_DATA_SAVED_TO_DB = "Data saved to DB"
private const val MSG_DATA_SAVED_TO_PREFS = "Data saved to prefs"

Then, proceed by replacing the first method in AppRepository, turning it into this:

override fun add2Db(data: List<Student>, callback: (String) -> Unit) {
        doAsync {
            database.userDao().insertStudentList(data)
            uiThread {
                callback(MSG_DATA_SAVED_TO_DB)
            }
        }
    }

The method asynchronously inserts a list in the database and notifies it through a message in a lambda callback.

Once you add the above snippet, you’ll get an unresolved reference error because database was never injected into AppRepository. This entity refers to the Room implementation set up in the project. This entity was also defined within the app dependencies.

Add the next line right after the class header,

private val database: AppDatabase by inject()

and make sure AppRepository implements the KoinComponent interface.

object AppRepository : FeatureContract.Model<Student>, KoinComponent {

The next method to fill in allows you to save data in the app SharedPreferences. Again, it uses Anko to perform the task in the background by using doAsync and to notify it in the main thread by using uiThread:

override fun add2Prefs(data: List<Student>, callback: (String) -> Unit) {
        doAsync {
            data.forEach {
                with(sharedPreferences.edit()) {
                    val jsonString = Gson().toJson(it)
                    putString(it.name, jsonString).commit()
                }
            }
            uiThread {
                callback(MSG_DATA_SAVED_TO_PREFS)
            }
        }
    }

The last two methods implement fetching data from the database and the preferences, respectively.

override fun fetchFromDb(data: List<Student>, callback: (List<Student>) -> Unit) {
        doAsync {
            val list = database.userDao().loadAllStudents()
            uiThread {
                callback(list)
            }
        }
    }

    override fun fetchFromPrefs(data: List<Student>): List<Student> {
        data.forEach {
            val item: Student? = Gson().fromJson(sharedPreferences.getString(it.name, ""), Student::class.java)
            item?.let { persItem ->
                it.attendance = persItem.attendance
                it.grade = persItem.grade
            }
        }

        return data
    }

The only difference between the two methods above is that the database queries are performed in a worker thread, not the main one.

And with that, you have finished implementing Mark me! Congratulations!

App Performance Analysis

Once completed, you should have the app up and running as in the following Mark me! demo video:

When started, the splash screen appears for just a few seconds, and then it jumps directly to MainActivity. From this screen the user can navigate to either of the available features: Attendance or Grading. Both of them provide a list that shows certain information about a class of students.

You can gracefully scroll along these lists and check/uncheck in one or several student registers. Tapping on the corresponding buttons saves the selections. The navigation is handled by the Toolbar and Back Arrow events.

Testing: Insert Koin

To properly asses a DI framework, you need to know how well it behaves when it comes to a unit test. Good news! Koin perfectly addresses this need.

First, you’ll add some dependencies to the app module build.gradle:

dependencies {
    ...
    // Koin testing tools
    testImplementation "org.koin:koin-test:$koin_version"
    testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
}

The first library brings in Koin DSL, version 1.0.2 in this case, for testing, whereas the second adds extra features to Mockito so it can work with Kotlin.

Note: If you've never heard of Mockito, it's a mocking framework for unit tests in Java. If you're interested in adding Mockito to your project, you can read about Android Unit Testing with Mockito on our site.

Generally speaking, unit tests make sense on non-Android layers since you want to check your logic, but not the framework itself. Since this project uses MVP as the architecture pattern, presenters should be the target.

In this example, you’re only going to check that a method is called when a certain condition happens, which will prove the compatibility and convenience of using Koin for testing.

Checking the Approach

According to the above, create a file FeaturePresenterTest.kt in the test folder.

Then, add the following snippet:

class FeaturePresenterTest : KoinTest {

    private val view: FeatureContract.View<Student> = mock()
    private val repository: FeatureContract.Model<Student> by inject()
    private val presenter: FeatureContract.Presenter<Student> by inject { parametersOf(view) }

    @Before
    fun before() {
        startKoin(listOf(applicationModule))
        declareMock<FeatureContract.Model<Student>>()
    }

    @After
    fun after() {
        stopKoin()
    }

    @Test
    fun `check that onSave2DbClick invokes a repository callback`() {
        val studentList = listOf(
                Student(0, "Pablo", true, 8),
                Student(1, "Irene", false, 10))
        val dummyCallback = argumentCaptor<(String) -> Unit>()

        presenter.onSave2DbClick(studentList)
        Mockito.verify(repository).add2Db(data = eq(studentList), callback = dummyCallback.capture())
    }
}

While the code is fairly straightforward, here are some things to keep in mind:

  1. The class must implement the KoinTest interface.
  2. All the elements used must be instantiated or injected, even if you are not using them directly, like the view. Since the Android OS does not allow you to invoke Activity objects, the view has to be directly mocked.
  3. Koin needs to start normally so that injections take place, including all of the dependency modules.
  4. All objects not being tested have to be mocked. In Koin, you can use declareMock for those injected objects.
  5. It’s good practice to use descriptive names for your methods. Remember that you can use `` to enclose a name with whitespaces.
  6. argumentCaptor allows you to grab a value or variable and use it later with any Mockito expression. This is required when using Koin.
  7. Since you’re checking the presenter, invoke one of its methods with the appropriate arguments.
  8. This line is where the evaluation actually happens. In this case, you verify that the add2Db method from the repository gets called. If you remember the FeaturePresenter class, this should be the case when invoking onSave2DbClick. The eq method is also mandatory due to limitations between Kotlin and Mockito.
  9. Always remember to stop Koin.

Run the test and make sure everything passes!

Where to Go From Here?

You can download the fully finished sample project using the Download Materials button at the top or bottom of the tutorial.

While there are several good references about DI, and Koin in particular, the best source may be the official documentation, which is rather concise and self-explanatory.

Articles like this one make starting to work with Koin much easier. This article is a more practical example, which may help you on your first attempts.

If you want to keep working with the sample app you’ve just finished, try replacing the presentation layer architecture with MVVM. This pattern is becoming very popular in the field, especially after the Google I/O 2017 conference, where it was incorporated into the Android Architecture Components (AAC). Koin includes a specific DSL classes to handle this new ViewModel stuff.

I hope you enjoyed this tutorial about Koin. If you have any questions or comments, please join the forum discussion below!