From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
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.
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:
By the way, I previously wrote about StockWatcher when I showed an RxJava 2 pattern for handling lifecycle configuration changes.
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!
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.
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"
+ }
}
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
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.boolean
primitive in favor of a Kotlin Boolean
object. Kotlin represents all primitive types with objects.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
?
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()
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()
- 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"
+ }
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.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() {
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.
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...