Upcoming and OnDemand Webinars View full list

Migrating an Android App from Java to Kotlin

Josh Skeen

Now that Google has announced official support for Kotlin on Android, Kotlin is widely viewed as the first viable alternative to Java on Android.

If you haven’t yet heard of Kotlin, it’s a modern JVM language in use at companies like Pinterest, Trello, Square, Kickstarter and Google, to list just a few.

On Android, Kotlin enables a modern programming experience without requiring third-party workarounds that would exclude large percentages of users (Java 8 support on Android requires a minSdk of 24 which excludes 95% of devices) or that would introduce the risk of using Java 8 language features the Android toolchain doesn’t support.

My colleague David wrote a great introduction to Kotlin programming, and in this series, we’ll learn what Kotlin offers by migrating a 100% Java Android app to a 100% Kotlin Android app. In this first post, we’ll get our project configured to use Kotlin and run through converting our first file.

Starting the Migration

The project we’ll migrate is called StockWatcher. Given a ticker symbol, StockWatcher looks up the current stock price using a REST API. StockWatcher also includes many of the popular patterns and libraries you’re likely to find in a modern (2017) Android and Java app:

  • RxJava 2 for event propagation, background work scheduling, and data manipulation
  • Retrofit for networking
  • Data binding for generating view binding classes
  • Retrolambda to backport a subset of Java 8 features to Java 6, enabling support for older API levels
  • Dagger 2 for dependency injection
  • Gson for JSON parsing and serialization
  • Timber for logging

By the way, I previously wrote about StockWatcher when I showed an RxJava 2 pattern for handling lifecycle configuration changes.

Kotlin IDE Plugin

To get started, we first need to add the Kotlin IDE plugin in Android Studio. To add it, select Android Studio > Preferences > Plugins > Install Jetbrains Plugin. Type in “kotlin” and click “Install”. Restart Android Studio before continuing; otherwise, it won’t have loaded your new Kotlin plugin!

Updating Gradle

Next up, we’ll update the project’s build.gradle to support Kotlin. Here’s what I changed to get Kotlin wired up in the legacy project:

build.gradle

buildscript {
+    ext.kotlin_version = '1.1.1'
+    ext.gradle_plugin_version = '2.3.0'
      repositories {
          jcenter()
      }
      dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.0-beta3'
-        classpath 'me.tatarka:gradle-retrolambda:3.4.0'
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
+        classpath "com.android.tools.build:gradle:$gradle_plugin_version"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
      }
  }

app/build.gradle

apply plugin: 'com.android.application'
-apply plugin: 'me.tatarka.retrolambda'
+apply plugin: 'kotlin-android'

 android {
     compileSdkVersion 25
     buildToolsVersion "25.0.2"
     defaultConfig {
         applicationId "com.bignerdranch.stockwatcher"
         minSdkVersion 19
     }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
    dataBinding {
        enabled = true
    }
 }

 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])

     //support libraries
     compile 'com.android.support:appcompat-v7:25.1.0'
     compile 'com.android.support:design:25.1.0'
     //dependency injection
     compile 'com.google.dagger:dagger:2.8'
-    annotationProcessor 'com.google.dagger:dagger-compiler:2.8'
+    kapt 'com.google.dagger:dagger-compiler:2.8'
+    kapt "com.android.databinding:compiler:$gradle_plugin_version"
     provided 'org.glassfish:javax.annotation:10.0-b28'
     compile 'com.google.auto.factory:auto-factory:1.0-beta3'
-    //code generation
-    provided 'org.projectlombok:lombok:1.16.12'
-    annotationProcessor 'org.projectlombok:lombok:1.16.12'
     //networking
     compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
     compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
     compile 'com.squareup.okhttp3:logging-interceptor:3.4.2'
     compile 'com.squareup.retrofit2:converter-gson:2.1.0'
     //logging
     compile 'com.jakewharton.timber:timber:4.3.1'
     //kotlin stdlib
+    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
+
+kapt.generateStubs = true

I notice several improvements in the Gradle configuration changes to add Kotlin.

The first is that we get to drop the gradle-retrolambda dependency and compileOptions block, since the Kotlin compiler will generate bytecode that is 100% compatible with the Java bytecode the Android toolchain supports.

Notice that I also removed the lombok dependency? Lombok makes writing Java easier by generating boilerplate code for you. This comes at the cost of direct manipulation of the bytecode in your compiled Android code. I anticipate being able to put Kotlin’s Data Class to use in place of what Lombok enabled. Note that this will break the build, but I’m biasing towards removing this dependency now as we’ll remove the Lombok annotations in the scope of the migration to take place in the next couple of articles. If you’re following along step-by-step, you may opt to leave Lombok to allow the build to work correctly.

Also, notice the addition of the kapt statements? The annotationProcessor support built-in to recent versions of Android Studio (which made android-apt obsolete) didn’t support Kotlin. kapt is therefore needed to get the legacy dagger and databinding functionality to work correctly. The generateStubs = true line was also required to allow kapt to generate stubs that enable generated Java and Kotlin to work together correctly.

Automated Migration Tools

The Android Studio Kotlin plugin ships with a nice feature: an automated conversion tool that will rewrite our Java classes for us, which will serve as a good start. Another advantage Kotlin provides: Java and Kotlin can exist side-by-side in the same project. We are free improve the codebase one file at a time, instead of paying upfront to migrate everything at once.

For the first file to auto-migrate, I chose RxFragment.java, the base class for all Fragments in StockWatcher that make use of RxJava 2. To run the migration, select Code > Convert Java File to Kotlin File.

Let’s see what changed:

- public abstract class RxFragment extends Fragment {
+ abstract class RxFragment : Fragment() {
+
-        private static final java.lang.String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS";
-       @Getter
-       @Setter
-       private boolean requestInProgress;
+       private var requestInProgress: Boolean = false
-       private CompositeDisposable compositeDisposable;
+       private var compositeDisposable: CompositeDisposable? = null

-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
+        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
-           compositeDisposable = new CompositeDisposable();
+           compositeDisposable = CompositeDisposable()
            if (savedInstanceState != null) {
                requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false)
            }
        }

        //... onResume & onPause methods which resembled the same thing here

-        @Override
-        public void onSaveInstanceState(Bundle outState) {
+        override fun onSaveInstanceState(outState: Bundle) {
             super.onSaveInstanceState(outState);
-            outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress
+            outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress)
        }

+       companion object {
+           private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"
+       }
}

After the Automatic Migration: What Changed?

I noticed several things about the automatically applied changes to RxFragment.java (running from top to bottom of the changes, roughly):

- public abstract class RxFragment extends Fragment {
+ abstract class RxFragment : Fragment() {
-       private boolean requestInProgress;
+       private var requestInProgress: Boolean = false
  • Usage of the var keyword. As noted in the Properties and Fields page of the language guide, there are two ways to define properties: var for mutable or val for read-only.
  • Removal of a boolean primitive in favor of a Kotlin Boolean object. Kotlin represents all primitive types with objects.
    The Boolean had to be initialized for the class to compile—in other words, no default value is provided (like a Java boolean primitive’s behavior).
-       private CompositeDisposable compositeDisposable;
+       private var compositeDisposable: CompositeDisposable? = null
  • Note that the ? symbol was attached to CompositeDisposable. To allow a field to be null, you must explicitly declare so with ?.
-      @Override
-      public void onCreate(@Nullable Bundle savedInstanceState) {
+      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
-         compositeDisposable = new CompositeDisposable();
+         compositeDisposable = CompositeDisposable()
  • No more new keyword (or semicolons).
  • fun keyword for defining functions. This adds specificity and perhaps clarity since constructor calls could look a lot like method calls now that the new keyword is gone, eg: DoStuff() vs doStuff() -> fun doStuff() This seems like a nice measure to avoid any confusion.
-    compositeDisposable = new CompositeDisposable();
+    compositeDisposable = CompositeDisposable()
  • Semicolons have been nixed. Kotlin’s linter considered them “redundant” in most cases, but you can still use them if you absolutely must jam multiple statements on the same line.
-  private static final java.lang.String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS";
+  companion object {
+      private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"
+  }
  • The companion object has been added as a replacement for private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS";. Note that there is no static keyword in Kotlin. In the Kotlin world, you have the object keyword instead (but, there is no Object class as you might be familiar with from Java), which is what to use whenever you require a single instance of something—acting very similarly to a Java static. Read up on it here.
    This design choice appears to be inherited from Scala.
  • companion appears to make any values, even those marked private on the companion object, available as if they were local to the class you are using them from. We’ll explore cleaning this up further in the next article, because I think we can do better than what the conversion tool outputs here.
- public abstract class RxFragment extends Fragment {
+ abstract class RxFragment : Fragment() {
  • Support for extending a plain old Java class from a Kotlin class. As you can see, we extended Fragment, a legacy Java class, without any extra effort. This is great for interoperability with legacy Android apps written in Java because it offers a gradual migration path.

Up Next

So, after converting our first file, we’ve seen features Kotlin that we can put to work for us as we complete the migration. Keep in mind, this is just what we could complete automatically with the Kotlin conversion tool.

Now that the automatic converter is done, we will improve on its work by hand.
We’ll revisit this in the next article and make it more Kotlin-esque than the automatic converter could do.

Spoiler: For those who can’t wait, here’s a sneak peak at the completed StockWatcher Kotlin migration.

Learn more about what Kotlin means for your Android apps. Download our ebook for a deeper look into how this new first-party language will affect your business.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project