Android App with Kotlin, Dagger2, RxJava, based on MVVM and TDD (Part 2)

Mohsen Beiranvand
8 min readJun 10, 2019

--

This is multiple part stories about a Kotlin application from scratch.

Previous Section

In the previous section, I created a Kotlin project then I used Dagger2 as dependency injection. Also, I talked about TDD and some Git issues.

In this section, I’m going to talk about the MVVM pattern and implementing it with Dagger2.

MVVM

There are many architectures pattern for Android applications and I choose MVVM for my application. why?

Each architecture pattern has some pros and cons and I don’t want to compare all of them here. But, let’s look at the MVP pattern that was popular before MVVM.
In the MVP pattern, we have a Presenter that holds an instance of the View interface (only one!). The Presenter is contracting with Model and therefore invoking some View interface method. Thus, it’s responsible to update the View and this behavior does not fit SRP.

Android MVP architecture diagram

Just keep the last paragraph in your mind and let’s dive into MVVM pattern.

Instead of Presenter, we have ViewModel here that should extend from ViewModel or AndroidViewModel (which is Context-aware ViewModel).

ViewModel, unlike Presenter, doesn’t know anything about Views and it never keeps or referenced any instances of View.

MVVM is a variation of Martin Fowler’s Presentation Model design pattern. MVVM abstracts a view’s state and behavior in the same way, but a Presentation Model abstracts a view (creates a view model) in a manner not dependent on a specific user-interface platform.
- wikipedia

ViewModel uses the observer pattern to provide data for View, so Views can observer ViewModel data to change UI and because of Android Jetpack ViewModel library, it’s lifecycle aware and can hold a large amount of data when UI controller re-created (when rotating or changing configuration) without serialize or deserializing data and it will be cleaned up when scope (Activity or fragment) finished.
Also, it’s possible to share a ViewModel between multiple fragments and Views, hence you can share data between them.

A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
- Android

Android MVVM architecture diagram

Implementing Android ViewModel with Dagger2

You can create an Activity in Kotlin through Android studio tools (as same as Java) but you should select Kotlin as the language in the last section of the wizard and if it’s a Main Activity you should select Launcher Activity option.

Before that, I create this structure for managing View classes:

Folder structure for Android view classes (Activity, Fragments, etc)

Right click on ‘activity’ folder then select New>Activity>Empty Activity to create an Activity then select Kotlin as language and Launcher Activity because this is our MainActivity.

Creating Activity in Kotlin

Each Android application needs an Activity with CATEGORY_LAUNCHER intent filter to be visible in the launcher (if we would like to be visible).

If you don’t like to create an Activity through the wizard, you should create a class that extends Activity (or AppCompatActivity) class and it needs a resource layout and finally, you must add an activity tag into Android manifest file to make activity usable.

Android manifest after creating MainActivity
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Now, I’m going to create a ViewModel for MainActivity. Create a Kotlin class inside ‘viewmodel’ folder and name it ‘MainViewModel’:

Folder structure for Android ViewModels
class MainViewModel : ViewModel() {}

Let’s bring MainViewModel to MainActivity.

At first, we need to refactor MainActivity resource layout (activity_main.xml) from simple Layout to ‘Data Binding Layout’.

The root tag of ‘Data Binding Layout’ is ‘<layout>’ and followed by a ‘<data>’ element and a view element (such as LinearLayout, RelativeLayout and etc).

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.activity.MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

“activity_main.xml” should reactors to…

<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".view.activity.MainActivity">
<data>
<variable
name="viewModel"
type="com.imohsenb.kotlin.viewmodel.MainViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Add a ‘variable’ element into the ‘data’ element and set its ‘name’ property value to ‘viewModel’ and ‘type’ property value to the type of our ViewModel class (MainViewModel in this example).

MainActivity class should be refactored to use Data Binding facilities and observing ViewModel data. Thereafter, we should replace ‘setContentView’ method to be able to use Data Binding layout.
DataBindingUtil helps us in this way to create a ViewDataBinding.

Add lifecycle library to the project then enable data binding in Android config:

//lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0"

enabling data binding in build.gradle:

android {...
dataBinding {enabled = true}
}

then refactor the MainActivity…

--
setContentView(R.layout.activity_main)
++
val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

It’s time to create an instance of MainViewModel and assign it to ViewDataBinding.
ViewModelProviders helps us to create an instance of ViewModel and it prevents re-creating a new instance of ViewModel for the alive scope.

ViewModelProviders takes a scope with ‘.of(activity or fragment as scope)’ method and finally, you can take an instance of ViewModel from ‘.get(type of ViewModel)’ method:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//create data binding view
val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
//provide view model
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
//assign viewModel to data binding layout variable
binding.viewModel = viewModel
}
}

You can specify a factory class (that knows how to create an instance of ViewModel for us) to ViewModelProvider class and it’s very useful especially when we use DI (Dagger in this example). This factory class must implement ViewModelProvider.Factory interface and its ‘create’ method.

Create a Kotlin class inside ‘viewmodel’ directory named ‘ViewModelFactory’ and simply override ‘create’ method to create and return an instance of each ViewModel:

class ViewModelFactory @Inject constructor() : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MainViewModel() as T
}
throw IllegalArgumentException("Unknown ViewModel type")
}
}

then pass it to the ‘ViewModelProviders.of’ method as the second parameter. Because of Dagger, we should add this provider to DaggerModule consequently Dagger would be able to resolve dependencies of ViewModels. MainActivity should be refactored into this implementation:

class MainActivity : DaggerAppCompatActivity() {@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//create data binding view
val binding : ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
//provide view model
val viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MainViewModel::class.java)
//assign view model to viewModel data binding layout variable
binding.viewModel = viewModel
}
}

we also add ViewModelFactory ‘@Binds’ method to AppModule class:

@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory) : ViewModelProvider.Factory

Dagger introduces another annotation that makes life easier and it’s ‘@ContributesAndroidInjector’ annotation.
Activity, Fragment and other Android framework class instantiating by OS.
Because of that, If we have field injection in these classes, they need member injection to resolve their dependencies.

Let’s Add a new module into DI for our activity classes. Create a class into “di/module” folder and named it ‘ActivityModule’ and don’t forget to add the new module to AppComponent modules. Then add a bind method for MainActivity:

@Module(includes = [AndroidInjectionModule::class])
abstract class ActivityModule {
@ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
}

Better Implementation of Dagger for MVVM

Consider that we have multiple Activity and ViewModel in our project then we need to write many boilerplate codes. Let’s reduce these boilerplates by refactor them into a generic base class:

abstract class BaseActivity<VB : ViewDataBinding, VM : ViewModel>(
private val viewModelClass: Class<VM>,
@LayoutRes val layoutRes: Int
) :
DaggerAppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
protected lateinit var binding: VB
private set
protected lateinit var viewModel: VM
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//create data binding view
binding = DataBindingUtil.setContentView(this, layoutRes)
//provide view model
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(viewModelClass)
//assign view model to viewModel data binding layout variable
binding.setVariable(BR.viewModel, viewModel)
//execute binding changes
binding.executePendingBindings()
}
}

also, we should change MainActivity to extend BaseActivity class:

class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>(MainViewModel::class.java, R.layout.activity_main) {}

Dagger Multibind

It’s time to use another Dagger annotations and learn more about them.

@IntoMap’, ‘@IntoSet’ and ‘@ElementIntoSet’ annotations help us to use dagger multibind feature.

for example IntoSet annotation:

@Provides
@IntoSet
fun provideStringOne() : String {
return "ABC"
}
@Provides
@IntoSet
fun provideStringTwo() : String {
return "DEF"
}

then dagger resolve them as Set<String>

@Inject
lateinit var strings : Set<String>
//println(strings)
//System.out: [ABC, DEF]

IntoMap’ annotation is a little bit different because it needs a key at compile time and the key should be assigned by an annotation. Simply we can use ‘@StringKey’ or ‘@ClassKey’ annotations or create a custom annotation that annotated with ‘@MapKey’ annotation:

@Provides
@IntoMap
@StringKey("One")
fun provideStringOne() : String {
return "ABC"
}
@Provides
@IntoMap
@StringKey("Two")
fun provideStringTwo() : String {
return "DEF"
}

dagger inject them as Map<String, String> :

@Inject
lateinit var strings : Map<String, String>
//println(strings)
//System.out: {One=ABC, Two=DEF}

refactor time…
I’m going to refactor ViewModelFactory and makes it more elegant. before that create a new module for ViewModels in module package and named it ViewModelModule:

@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class)
abstract fun bindMainViewModel(vm: MainViewModel) : ViewModel
}

and create ‘@ViewModelKey’ annotation in “di/annotation” folder

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
@MapKey
annotation class ViewModelKey(val kClass: KClass<out ViewModel>)

then add the new module to AppComponent modules list

@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class,
ViewModelModule::class
]
)
interface AppComponent : AndroidInjector<KotlinApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(instance: KotlinApplication?)
}

now, do the final refactor on ViewModelFactory

class ViewModelFactory
@Inject
constructor(
private val viewModelMap: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
viewModelMap.let {
it
[modelClass]?.get()?.let { viewModel ->
return viewModel as T
}
throw IllegalArgumentException("Unknown ViewModel type")
}
}
}

Please notice to the ‘@JvmSuppressWildcards’ annotation that I added to Provider type argument. This annotation prevents compiler to add a wildcard (? extends) to type arguments. Without this annotation, you get ‘Dagger/MissingBinding’ error when you try to build your code.

Finally, we should refactor ViewModels and add ‘@Inject’ annotation on constructor hence dagger able to create instances of them:

class MainViewModel @Inject constructor() : ViewModel() {
}

With these changes, instead of instantiating a ViewModel manually Dagger will create it for us, therefore, it resolves ViewModel dependencies.

[Github: Dagger MVVM final implementation]

Please see the next section of this article to find more about Kotlin.

--

--