Migrating an Android project to Kotlin
A while ago we open sourced Topeka, an Android quiz app.
It is tested, using integration tests and unit tests.
And it is purely written in Java. Well… it was
What’s the name of the island off the coast of St Petersburg? _ _ _ _ _ _
At Google I/O 2017 we announced official support for the Kotlin programming language. That’s when I started migrating the code base away from Java, learning Kotlin on the way.
Not that this migration was necessary from a technological standpoint. The app is solid as it stands, but mainly to satisfy my curiosity; Topeka serving as my vehicle to learn a new language.
If you’re curious you can jump straight into the source code on GitHub.
For a while the code was on a separate branch, but now it’s merged into master.
This post gathers some of the key parts I have found during the migration. It also shows some of the things I found useful while learning a new programming language for Android development.
🔑 Key take aways
- Kotlin is a fun, powerful language
- Testing gives peace of mind
- Platform specific idioms are scarce
Initial migration to Kotlin
Steps 1 and 2 are kind of valid for getting started with Kotlin.
I’ll figure out how that 3rd step will play out, though.
For Topeka the route was more like this:
- Read up on the basic syntax of Kotlin
- Go through the Koans to gain basic familiarity with the language
- Convert files, one by one, via “⌥⇧⌘K”, make sure tests still pass
- Go over the Kotlin files and make them more idiomatic
- Repeat step 4 until you and your code reviewers are happy
- Ship it
Interoperability
Going step by step is a sensible approach.
Kotlin compiles down to Java byte code and the two languages are interoperable. Also it’s possible to have both languages within the same project. So it’s not necessary to migrate all code to another language.
But if that’s your goal, it makes sense to do so iteratively. This way it’s more feasible to maintain a stable application throughout the migration process and learn as you go along.
Tests ease your mind
Having a suite of unit and integration tests has many benefits.
In most cases they are there to provide confidence that changes have not broken existing behaviour.
Starting off with the less complex data classes was the clear choice for me.
They are being used throughout the project, yet their complexity is comparatively low. This makes them an ideal starting point to set off the journey into a new language.
After migrating some of these using the Kotlin code converter, which is built into Android Studio, executing tests and making them pass, I worked my way up until eventually ending up migrating the tests themselves to Kotlin.
Without tests, I would have been required to go through the touched features after each change, and manually verify them.
By having this automated it was a lot quicker and easier to move through the codebase, migrating code as I went along.
So, if you don’t have your app tested properly yet, there’s one more reason to do so right here. 👆
Generated code is not always nice to look at ‼️
After an initial round of mostly automated migration, I went on to read up on the Kotlin style guide. This page made it clear to me that there’s still a long way ahead.
The converter does a good job, overall. There are a lot of language idioms and features which are not being taken into account during the automated process, though. Which is probably for the better, since translating is tricky. Especially if one language contains more features or achieves similar things in a different way.
For further reading on the Kotlin converter, Benjamin Baxter has written about his experience:
‼️ ⁉
After the automatic conversion I ended up with a lot of ?
and !!
.
These are used to make a value nullable and assert that something is not null. Which in turn can lead to a NullPointerException
.
And I couldn’t help but think of a very fitting quote:
‘Multiple exclamation marks,’ he went on, shaking his head, ‘are a sure sign of a diseased mind. — Terry Pratchett
In many cases a value doesn’t have to be nullable, so null checks can be removed. It’s not even necessary to initialise all values directly within a constructor. Instead lateinit
or delegate initialisation can be used.
This doesn’t work for everything though:
So I had to go back and make my view members nullable.
In these and other cases you will still have to check, whether something is null
. Using supportActionBar?.setDisplayShowTitleEnabled(false)
only executes the part after the question mark if there is a supportActionBar
.
This means a lot less if
statement based null checks. 🔥
Also executing code with some of the stdlib functions directly on the non-null variable can be handy:
Incrementally becoming more idiomatic
Going through the generated code and making it more idiomatic, as well as getting reviewer feedback made it obvious that Kotlin is a powerful language. It made things readable and concise.
Let’s take a look at some examples that I came across.
Reading less is not always a bad thing
Let’s take an adapter’s getView
as example:
These two code snippets do the same thing:
Check, whether convertView
is null
and either create a new view within createView(...)
or return convertView
. Both also call bindView(...)
.
Both snippets, are equally legible. And boiling things down from 8 lines to mere 2 lines? Render me impressed.
Data classes are magical 🦄
To make it even more obvious how concise Kotlin can be, data classes easily manage to get rid of some verbosity:
Now, let’s look at that in Kotlin:
Yes, that’s 55 lines of code less, expressing the same thing. That’s the magic of data classes.
Extending functionality
This is where things get slightly weird from a traditional Android developer’s point of view. Kotlin allows creating your own DSL within a given scope.
Let’s see how that works
At times within Topeka it makes sense to pass around booleans in a Parcel
. This is not directly supported by the Android Framework APIs.
In the initial implementation it was necessary to explicitly call a utility class’ method like such ParcelableHelper.writeBoolean(parcel, value)
.
With Kotlin, extension functions solve that once and for all:
Having this written in one place, makes it possible to call parcel.writeBoolean(value)
and parcel.readBoolean()
directly, as if it were part of the framework. If Android Studio would not highlight extension functions differently, they were almost not noticeable.
Extending functionality makes things easier to read. Let’s take a look at another example: replacing a Fragment in a view hierarchy
In the Java world this would look something like this:
That’s actually not too bad. But you’ll have to write this code, at every single location where a fragment will be replaced. Or create a method somewhere, for example in yet another Utils class.
With Kotlin, an extension function makes it possible to simply call replaceFragment(R.id.container, MyFragment())
to replace a fragment within any FragmentActivity
within the project, by adding this code:
Less ceremony, more functionality
Higher order functions blew my mind.
Yes, I know that this is not a new concept in general. But for the old fashioned Android developer, it actually is. I had heard of them before and have seen them written, but making use of them within my own code is a different story.
Within several places in Topeka, I am relying on an OnLayoutChangeListener
to inject behaviour. In a pre-Kotlin world this would usually result in an anonymous class, with some duplicated code.
After the migration, all that’s required to call is:view.onLayoutChange { myAction() }
The ceremony around that has been encapsulated in this extension function:
Giving another example, this behaviour can also be applied to things like database transactions:
Now, instead of performing the whole dance to begin and end a transaction, all the user of this API has to call is db.transact { operation() }
.
Update via Twitter: UsingSQLiteDatabase.()
instead of just ()
to pass in a function allows working on the database directly within the operation()
. 🔥
I could go on, but you get the gist.
Higher order functions and extensions are handy to make a project easier to read and more fun to work with by removing unnecessary verbosity, adding functionality and hiding implementation details.
Things to discover
Throughout the conversion I have not come across many best practices for Android development just yet. So far I have mostly been sticking to the style guide and code conventions.
That may be because I am still new to the language or because there hasn’t been that much investment in gathering and publicising these yet. Maybe there is a collection which I am yet to come across, but it seems that there is quite some space for platform specific idioms.
If you’re aware of collections like this, please add them to the comments.