Kotlin for Android - The Integration Puzzle

Since the I/O'17 announcement of Kotlin as an officially supported language for Android, the whole community has been extremely excited about how this will affect the way we produce software.

Kotlin announcement on I/O'17

Kotlin announcement at I/O'17 (globalnerdy.com)


Although the language itself and tool support for Android development was already available, this announcement was really a game-changer. Google's statement made not just a wide range of developers, but also decision-makers to consider Kotlin as a language that is mature enough for building professional applications.

Our Android team started to evaluate the language support, to decide whether we should let Kotlin in our codebase, and our main concern was the integration with libraries and tools we already use.

In order to try that, we chose Peter Ekler's SpotifyDemo, and migrated it to Kotlin.

The reasons for choosing that app:

  • It is already public on Github
  • It utilizes most of the libraries and tools we use internally
  • It has a good unit and instrumentation test coverage
  • And it still simple enough

During this process we encountered number of issues to deal with, so we are sharing our experiences, in order to help others solving the puzzle of integrating libraries and tools.


Adding Kotlin to your project

In order to test whether the concept is already applicable for professional projects, we used the most recent, stable versions of all the tools. We used Android Studio 2.3, with Android Gradle Plugin 2.3.2 and Kotlin 1.1.2-3.

Although Android Studio 3.0 Alpha supports Kotlin by default, in order to use Kotlin with Android Studio 2.3, we have to install the Kotlin Android Studio plugin, and add the Kotlin Gradle plugin to our build script.

buildscript {  
    ext {
        kotlin_version = '1.1.2-3'
        android_plugin_version = '2.3.2'

        pluginDependencies = [
                android      : "com.android.tools.build:gradle:$android_plugin_version",
                kotlin       : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        ]
    }

    //...
    dependencies {
        classpath pluginDependencies.android
        classpath pluginDependencies.kotlin
    }
}

In the module level build.gradle, the Kotlin plugin must be applied, right after Android plugin.

apply plugin: 'com.android.application'  
apply plugin: 'kotlin-android'  

And the Kotlin standard library must be added to dependencies:

compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"  

As we used kotlin folders for Kotlin files - which is really recommended - Kotlin source folders must be added to the source set.

android{  
    //...
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        androidTest.java.srcDirs += 'src/androidTest/kotlin'
        test.java.srcDirs += 'src/test/kotlin'
    }
}

Now the project is ready for Kotlin. To convert existing Java source code to Kotlin, copy it to the Kotlin source folders and press CMD+OPTION+SHIFT+K (or CTRL+ALT+SHIFT+K for Windows/Linux).


Annotation processors

During the evaluation process, our largest concern was Annotation processors and Code generation.

Although it may seem that Annotation processors use the annotated code itself, in practice annotation processors work with a compiler model, using the Java Compiler API, including the javax.lang.model toolset.

The Kotlin Gradle Plugin offers Kapt - the Kotlin version of APT - in order to support annotation processors for Kotlin code.

Using Kapt, annotation processing is the same from the processors perspective, so existing Java Annotation Processors definitely work with Kotlin code. If your source set contains Kotlin code, it is also included in the generation process.

During the build process, Kotlin files and Java files compile separately. The task compileDebugJavaWithJavac compiles the Java source code and compileDebugKotlin compiles Kotlin sources.

With Kapt, annotation processing is executed before Kotlin compilation, which is done before the Java.

:app:kaptDebugKotlin
:app:compileDebugKotlin
:app:compileDebugJavaWithJavac

So when your source is compiling, the generated classes are already available. It does not matter the generated code is Java or Kotlin, it can be accessed in the same way due to interoperability.

It is often useful to take a glance on generated source files. As in case of APT, the generated code is inspectable at /build/generated/source/kapt

Using Kapt

Kapt is the part of the Kotlin gradle plugin, so you can just add it to your module build.gradle after applying Kotlin.

apply plugin: 'com.android.application'  
apply plugin: 'kotlin-android'  
apply plugin: 'kotlin-kapt'  

Then, at dependencies, use kapt instead of apt or annotationProcessor.

dependencies {  
    //...
    kapt "..."
}

Previous versions of Kapt may require additional configuration (like generate stubs, or disabling incremental compilation), but with version 1.1.2-3 these are not required any more.

Using Kotlin with Android Studio 3.0 Alpha, Kotlin annotation processing is part of the Android gradle plugin along with the Kotlin plugin, so using Kapt may not be required.


Dependency Injection

For Dependency Injection we use Dagger 2. This tool provides an annotation processor in order to generate the dependency graph at compile time. Use kapt for running dagger-compiler, so the required classes are being generated.

ext.dagger_version = '2.10'  
//... 

dependencies {  
    //...
   compile  "com.google.dagger:dagger:$dagger_version"
   kapt     "com.google.dagger:dagger-compiler:$dagger_version"
   provided "javax.annotation:jsr250-api:1.0"  
      // Required for @Generated annotation
}


Property Injection

Due to language differences, using dagger from Kotlin code is a bit different. Most of the injected classes should be not-nullable. Thus, we would like to define them as

@Inject
var artistsInteractor: ArtistsInteractor  

But the problem is, that non-null variables must have a value at the time of declaration, which is obviously not available in case of dependency injection.

At first, it may seem to be a simple solution, to just add a ?, and make our class nullable. However, this would affect the way we access injected classes later, and we should take care of handling nullability despite we know it is always available via injection.

Instead, we use lateinit modifier, which let us provide the value later, but still using non-nullable variables.

@Inject
lateinit var artistsInteractor: ArtistsInteractor  

But be careful, because with lateinit you have no compile time guarantee, that your non-null variable really have a value. Use it only when your are absolutely sure, that these values are supplied correctly, like in case of Dependency Injection.


Constructor Injection

In Kotlin, default non-private constructors are often omitted. In order to support constructor injection, these constructors must be explicitly defined, and annotated.

class ArtistsInteractor  
   @Inject constructor(private var artistsApi: ArtistsApi) {
     //...
}


Providing qualified dependencies

In case of qualified dependencies we use multiple annotations in Java, in order to specify which one of the dependencies to use.

@Inject @Network
Executor networkExecutor;  

In Kotlin the situation is not so obvious, due to multiple possible generation locations. In case of a property, this could be the field, the getter and the setter too. We have to use Annotation Use-site Targets, in order to specify multiple field annotations.

@field:[Inject Network]
lateinit var networkExecutor: Executor  

But this seems to be quite an ugly solution. So we went deeper, and as it turned out, by using annotation target restrictions Kotlin keeps including all the annotations, without using @field:[...] annotation. The qualifier annotation must be restricted to Fields and Functions. The former is for the injected field, the latter is for the provider method.

@Target(AnnotationTarget.FIELD,AnnotationTarget.FUNCTION)
@Qualifier
@Retention(RUNTIME)
annotation class Network  

With this little trick we could use the qualifier annotations the way one would expect it.

@Inject @Network
lateinit var networkExecutor: Executor  

The reason why this works is that Kotlin differentiates the generated getter and setter functions and regular functions. AnnotationTarget.FUNCTION only fits the non-getter and non-setter functions, while AnnotationTarget.PROPERTY_GETTER and AnnotationTarget.PROPERTY_SETTER fits generated getters and setters.



Unit Testing

Along with code generation, automated unit and integration testing is a key aspect for us. In order to enable Dagger for unit tests, we used kaptTest to run Kapt for test sources.

dependencies {  
    //..
   testCompile  "com.google.dagger:dagger:$dagger_version"
   kaptTest     "com.google.dagger:dagger-compiler:$dagger_version"
   testProvided "javax.annotation:jsr250-api:1.0"  
}
Mocking the unmockable

In Kotlin, classes are final by default, which means that we cannot extend a class, unless it is explicitly open for extension. Mocking a class is often required during testing, we used Mockito for that purpose. But opening a class just for enabling testing, feels like the wrong direction.

At first, we tried Mockito-Kotlin library. This library lets us mock final Kotlin classes without opening them. Also, the way this library offers for using mocks and captors seems to be more appropriate than Mockito.

But we soon realised that this library depends on the Kotlin stdlib, which is a must have dependency for Kotlin development. As it is really hard to keep dependency library versions updated, the stdlib used by Mockito-Kotlin was older than the one we use. So excluding the transitive stdlib from Mockito Kotlin is required. Besides this being complicated, it foreshadows possible issues due to API differences between stdlib versions.

For this reason, we chose MockMaker, provided by Mockito itself since version 2. This tool lets us reach the same effect without having an additional library dependency. In order to switch to MockMaker, add a file to src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker with the content of mock-maker-inline

Although MockMaker does the job well, we really missed the convenient mocking and capturing tools from Mockito-Kotlin, so we borrowed some of the generic utility code from that library.

inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)!!  
inline fun <reified T : Any> mock(s: MockSettings): T = Mockito.mock(T::class.java, s)!!  
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = ArgumentCaptor.forClass(T::class.java)  
inline fun <reified T : Any> nullableArgumentCaptor(): ArgumentCaptor<T?> = ArgumentCaptor.forClass(T::class.java)  
inline fun <reified T : Any> capture(captor: ArgumentCaptor<T>): T = captor.capture()  


Instrumentation Testing

Configuring instrumentation tests with Kotlin required pretty much the same steps, so we added dagger-compiler with kaptAndroidTest keyword, and the MockMaker file to androidTest directory.

However, in case of JUnit Test Rules we faced an interesting issue. As a restriction of the JUnit library, Rules must be public. We got the error, The @Rule '...' must be public., even the property was set to be public. The reason for this is that Kotlin generated fields are always private and visibility modifiers only affect the generated getters and setters.

@Rule
val activityRule: IntentsTestRule<T> = IntentsTestRule(activityClass, false, false)

//Generated Java code
@Rule @NotNull
private final IntentsTestRule activityRule;  

To overcome this situation we used the @JvmField annotation, which makes Kotlin generate only a Java field for the property with the desired modifiers.

@Rule @JvmField
val activityRule: IntentsTestRule<T> = //...   

//Generated Java code
@Rule @JvmField @NotNull
public final IntentsTestRule activityRule;  

As you can see it is often handy to check the Java code generated from Kotlin. In order to do that, open your Kotlin class and go to Tools > Kotlin > Show Kotlin Bytecode and then hit Decompile.

Decompiling Kotlin bytecode to Java is a great tool for dealing with interoperability issues


Continuous Integration

Although developers need to install the Kotlin plugin (in case of Android Studio 2.3), this is only necessary for IDE support. For building the application without the IDE, it requires no additional tools to install.

Gradle itself downloads the Kotlin Gradle plugin, which will provide all the necessary tools for compiling Kotlin. Kotlin Gradle plugin adds the necessary tasks and configures task dependencies so even your CI build scripts remain the same.


Conclusion

During the this evaluation process we faced a number of non-trivial situations. However, during the whole process we felt that the guys at Jetbrains and the Android community have already faced similar issues and these issues affected how the language itself was evolved. Seeing things like Kapt, lateinit modifier, or the Java generation annotations it seems obvious to us that a lot of effort is made to make Kotlin ready for Android development.

The final application, KotifyDemo is migrated entirely to Kotlin. Feel free to check it out via Github.

Thanks for the AutSoft Android team for their help!