Model Injection using MVVM

This post will handle the Model of MVVM. If you don’t know about View or ViewModel, please read Basic MVVM in Android first.

Model as a Singleton

The Model of the MVVM is a singleton. In Kotlin you can declare it as an object. But, there are two problems!

  • What if the Model need a reference to the application or other static data?

  • During unit testing the Model has no access to the hardware, like the file system. A so called Fake Model simulate the hardware. How is the Model changed without update all reference in the ViewModels?

So what is a solution for these two problems. You may ask? The answer is injection!

The application uses a list of Model. The Models is then injected in the ViewModels.

Injection using Koin

The example shows a simple Todo List App that use the Koin library for the injection.

First add the Koin library to the to the project.

dependencies {
    /* … */
    implementation 'org.koin:koin-android:2.0.1'
    /* … */
}

Fake Todo Model

Create a interface for the TodoModel.

interface TodoModel {
    val todoList: LiveData<List<TodoItem>>
    fun newTodo(text: String)
    fun updateTodo(id: Int, text: String)
    fun toggleCompleted(id: Int)
    fun deleteTodo(id: Int)
    fun getTodo(id: Int): TodoItem?
}

Then create a FakeTodoModel that implements the TodoModel. It has no hardware dependencies.

class FakeTodoModel: TodoModel {

    private var nextId = 2

    override val todoList = MutableLiveData<List<TodoItem>>().apply { value = listOf(
        TodoItem(0, "Write ProdTodoModel.", false),
        TodoItem(1, "Write article about injection", true)
    )}

    override fun newTodo(text: String) {
        todoList.value = todoList.value?.plus( listOf(TodoItem(nextId++, text, false)) )
    }

    override fun updateTodo(id: Int, text: String) {
        todoList.value = todoList.value?.map {
            if(it.id == id) {
                TodoItem(id, text, it.complete)
            } else {
                it
            }
        }
    }

    override fun toggleCompleted(id: Int) {
        todoList.value = todoList.value?.map {
            if(it.id == id) {
                TodoItem(id, it.text, !it.complete)
            } else {
                it
            }
        }
    }

    override fun deleteTodo(id: Int) {
        todoList.value = todoList.value?.filter { it.id != id }
    }

    override fun getTodo(id: Int): TodoItem? {
        return todoList.value?.find { it.id == id }
    }
}

The InjectionApplication provides the FakeTodoModel.

class InjectionApplication: Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            modules(module {
                single<TodoModel> { ProdTodoModel(this@InjectionApplication) }
            })
        }
    }
}

Injection in ViewModel

We continue writing the View and ViewModel for the application. The ViewModels inject the TodoModel is like this this:

class MainViewModel: ViewModel(), KoinComponent {

    private val todoModel by inject<TodoModel>()

    val todoList = todoModel.todoList

    fun toggleComplete(id: Int) = todoModel.toggleCompleted(id)
}

Production Todo Model

After finish writing the View and ViewModel for the application with the fake model. Every thing work well. But, the Todos is not saved when the user close the app.

The ProdTodoModel save and load the data to a json file.

The InjectionApplication update the Model list:

    /* … */
    startKoin {
        modules(module {
            single<TodoModel> { ProdTodoModel(this@InjectionApplication) }
        })
    }
    /* … */

Whach up for

The CheckBox and other widgets save there state in savedInstanceState as default. This can lead to strange behavior. Make sure you add android:saveEnabled=“false” for the widget in the layout file, like this:

    <CheckBox
        
        android:saveEnabled="false" />

“Room” for Improvements

This application is an example of injection and not meant for publication. RecyclerView or using a database would improve it. I leave it up to you to make the changes.

Summary

This article explains how the Koin library inject Models in the ViewModels. It also show you an example of using a fake model while develop. The Model can then be switch to the production Model.

You can find the source files on GitHub!

Read the next post: Unit Testing ViewModel