Android App with Kotlin, Dagger2, RxJava, based on MVVM and TDD (Part 1)
This is multiple part stories about a Kotlin application from scratch. At first, we create a Kotlin project, make a git repository and we’ll learn about Test Driven Development.
Through these stories, we’ll learn how to create an Android application with Kotlin based on the TDD process, then we find out how to write test cases before every movement AND there is a Github project with useful commits so you can track every state of each story in brackets[].
Create a Kotlin Project
At first, we need to create a Kotlin project in Android Studio and it’s better to use the latest version (at least 3.2.1).
> Start New Android Project
> Check Include Kotlin Support
> Next to finish.
Initializing Git repository
Right after creating a project it’s good to create Git repository with “git init” command hence “.git” directory will be created in the project root directory.
Then we need to add all existing file to Git index with “git add .” command.
Finally, we need to record all changes to the repository with “git commit” command. If you want to use a remote repository (like Github, Gitlab, Microsoft TFS and etc), you should add its address to the local repository with “git remote” command then push local repository to remote with “git push” command.
> git init
> git add .
> git commit -m “init commit”
> git remote add origin https://github.com/imohsenb/kotlin
> git push -u origin --all
Now we can track all changes!
Git Branching Model
If you haven’t any idea about your project branching model, Gitflow is good one published by Vincent Driessen and there is a tool (git-flow) that makes it easy to do.
Test Drive Development (TDD)
We choose the TDD process for developing software, but Why?
There are many benefits to writing test cases, especially right before any code developing.
The only man who never makes a mistake is the man who never does anything.
- Theodore Roosevelt
Every human make mistake and test cases help us to prevent these mistakes in development. On the other hand, test cases could help us to understand the purpose and expectations of the method and it works like a document.
But, If you write a test case for an implemented method, the likelihood that you write a specific test case based on the method implementation is very high and TDD shows us this way.
TDD helps us to write testable code it 3 steps as shown below:
- (RED) Write a test case that will fail.
- (GREEN) Write sufficient code to makes the test pass.
- (REFACTOR) Refactoring the code to provide expectation in a good manner.
and we live in this cycle, all the time we are developing.
Let’s look at three roles of TDD from “Uncle Bob” :
1. You are not allowed to write any production code unless it is to make a failing unit test pass.
2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Okay, just remember that you should write a simple and one purpose test case to fail. So just write sufficient code to make the test case to the green state (success). We should continue to interact between test cases and code to meet all expectation about a unit of work.
If you start development from a Test Class, not a concrete class finally you’ll have high coverage, bug-free, testable and high-quality code and you don’t need a long process to test your application.
The development could take from 15% to 35% (!) more time — even more on hard to test projects.
But the benefits of TDD are much greater and in fact, using TDD we reduce the time it takes to get your code to the client (the one who uses your code) by having less bugs (around 40% to 91% less bugs) and more maintainable code.
- Dror Helper
Test Frameworks
There is a lot of test framework that helps us to write test cases easily and we should add them into application dependencies:
- Mockito
A mocking framework for unit tests. We should write test cases in isolation because we need to develop just one method without consideration about other methods and their functionalities. Mockito helps us to create a stub (mock) from other objects.
testImplementation 'org.mockito:mockito-core:2.10.0'
- Robolectric
Is a framework that brings fast and reliable unit tests to Android. Running instrument test cases need an emulator or device (JVM) and take a minute or more. Robolectric helps us to write just test cases and run on our workstation or a continues integration environment without an emulator.
testImplementation 'org.robolectric:robolectric:4.1'
- AssertJ
Provides a fluent interface for writing assertions. Asserts help us to check conditions in unit tests and AssertJ is the lovely one, with good syntax and various assert methods.
testImplementation 'org.assertj:assertj-core:3.11.1'
Dependency Injection with Dagger2
Based on SOLID principles your client should depend on abstraction not on implementation and on the other hand high-level module should not depend on a low-level module. So everywhere we need to use an abstraction Dependency Injector provides an implementation for us and we never instantiate an object inside a class manually.
We are going to use Dagger which is a dependency injection framework for both Android and Java, it’s fast and amazing so let makes it up.
Annotation processor works in Kotlin with kapt compiler plugin so we should add this plugin to project inside “app>build.gradle” file. without this plugin Dagger Components will not be generated:
apply plugin: 'kotlin-kapt'
It’s time to add the dependency of Dagger framework:
//dagger
implementation "com.google.dagger:dagger:$dagger2_version"
implementation "com.google.dagger:dagger-android:$dagger2_version"
implementation "com.google.dagger:dagger-android-support:$dagger2_version"kapt "com.google.dagger:dagger-android-processor:$dagger2_version"
kapt "com.google.dagger:dagger-compiler:$dagger2_version"
- we also define libraries version inside project build.gradle like this for better management :
ext {
//dependencies version
appcompat_version = "1.0.2"
assertj_version = "3.11.1"
robolectric_version = "4.1"
mockito_version = "2.10.0"
dagger2_version = "2.16"
}
Dagger Component
A component in Dagger is an abstract class or an interface annotated with ‘@Component’. Dagger will implement these class with modules and named as same as Component class prepended with Dagger.
For example, AppComponent implementation is DaggerAppComponent.
The component module defines in the Component annotation as modules parameter.
Also, each component may have a Builder which is an abstract class or interface annotated with ‘@Component.Builder’ but there is some rule:
- we should add an abstract method called (i.e. build()) without any argument and it should return component type.
- other abstract methods that use for instantiating must return Builder type, void or supertype of Builder and must take a single argument.
- each component dependency must have a setter method.
- non-abstract methods are allowed but will be ignored.
- each module dependency that Dagger can’t instantiate itself (e.g, the module doesn’t have a visible no-args constructor) must have an abstract setter method. Other module dependencies (ones that Dagger can instantiate) are allowed, but not required.
If there is an instance when we are building a component we can add it to Builder interface with ‘@BindsInstance’ annotation and it follows the rule no-2 we mentioned above. For example application method in AppComponent Builder.
Let’s look at the code of the first component and module:
@Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<KotlinApplication> { @Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder fun build(): AppComponent
}
}
Dagger use ‘@Inject’ annotation for:
- Creating an instance of the annotated class
- Obtain an instance for the annotated field
- Satisfying parameters for the annotated method
So if you want Dagger to create an instance of a class, you should annotate class constructor with ‘@inject’ then Dagger satisfying requirement parameters of class after creating a new instance, but this annotation doesn’t work everywhere because:
Interfaces can’t be constructed.
Third-party classes can’t be annotated.
Configurable objects must be configured!
There is another useful annotation ‘@provides’ and ‘@binds’ for satisfying dependencies.
If you want to create a new instance of a class manually, you should use ‘@Provides’ annotation on a method and by convention, the method is named with ‘provide’ prefix:
@Provides
fun provideCar() : Car {
return SportCar()
}
Instead of ‘@Provides’, you can simply use ‘@Binds’ annotation on an abstract method. It binds a type to its implementation without any extra code! A ‘@Binds’ method must be abstract with a single parameter whose type is assignable to the return type.
Dagger suggested using ‘@Binds’ because generated implementation will be more efficient.
@Binds abstract fun bindCar(car : SportCar) : Car
You may ask how Dagger identify dependencies if we have multiple implementations of a type? (i.e. SportCar, LuxuryCar for Car)
In this case, we should create a qualifier annotation:
@Qualifier
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
then you should apply qualifier annotation on ‘@Provides’ or ‘@Binds’ method and ‘@Inject’ annotation.
@Binds @Named("SportCar")
fun bindCar(car: SportCar) : Car
@Inject @Named("SportCar")
lateinit var car : Car
Dagger Modules
All ‘@Provides’ and ‘@Binds’ methods should belong to a class annotated with ‘@Module’ and it could be abstract or not and depends on the type of annotation you use for methods. Absolutely when we use ‘@Binds’ on methods it should be an abstract class.
By convention, module classes are named with ‘Module’ suffix.
It’s time to create a module for application and we should add this module to AppComponent as you see above.
@Module
abstract class AppModule {
@Binds
abstract fun bindContext(application: Application): Context
}
As you see in AppModule, we have a ‘@Binds’ method that provides an Application instance as Context.
DaggerApplication
finally, our Application (in this sample KotlinApplication) should extend from DaggerApplication and implement AndroidInjector method.
class KotlinApplication : DaggerApplication() {
override fun applicationInjector():
AndroidInjector<out DaggerApplication> { //Build app component
val appComponent: AppComponent = DaggerAppComponent.builder()
.application(this)
.build() //inject application instance
appComponent.inject(this)
return appComponent;
}
}
Well done! but what are the AndroidInjector and AndroidSupportInjectionModule?
AndroidInjector performs members-injection for Android core types like Activity, Fragment and etc.
AndroidInjectionModule should be installed in the component that is used to inject Application class and in our project, it's AppComponent and contains binding to ensure the usability of dagger android framework classes.
Now, we can use ‘@Inject’ annotation to resolve dependencies.