Experience porting an Android app to Kotlin


#1

I’m the author of CalWatch (Play Store link, Github link), an Android Wear watchface that renders your free/busy time around the twelve hours of the dial. It’s been my side project for a while and it’s all open source.

Anyway, with the move to Android 6’s new permission model, I had to make a bunch of changes to support the new world order, so I figured I might as well port my app from Java to Kotlin while I’m at it. This post documents my experiences, good and bad.

Getting started. I began with the Java -> Kotlin converter that’s built into Android Studio / IntelliJ. I did this one file at a time, cleaning up each file in turn until there were no errors, then moving onward. This process took about a day and was mostly straightforward. The Kotlin IntelliJ plugin is still a bit sensitive, and it was crashing all over the place until I straightened out all of the bugs in my translated code. This required quitting and restarting Android Studio every time, so it was a bit painful, but once I got my port compiling without errors, the Kotlin plugin has been stable ever since.

Compiling != Working. Kotlin tries to come up with inferences about the nullity of all the callbacks from Android to your Activity. Well, it’s wrong in a bunch of places. I had to add a bunch of question marks. Also, the converter tries to figure out your getters and setters and turn them into properties. This can be confusing and was the source of several bugs for me.

vars and !!'s everywhere. My code had a bunch of places where it would check if some field was null, and if not, would then start using it. This translated to Kotlin var fields, with every usage having !!'s before its dereference. It’s generally considered good Android style to null things out if the OS tells you you’re being shut down, so you can’t just use a lateinit modifier on a val. Ultimately I had to create a bunch of local val’s to copy the class var’s, so the not-null inferences would flow properly. This seems less than awesome.

One transformation that made me happy was replacing old-school list comprehensions like so:

if(stuffList != null) {
    for(stuff in stuffList!!) {
        ...
    }
}

With this:

stuffList?.forEach { ... }

Comments. As part of the automatic translation process, the Java -> Kotlin converter tries to preserve your comments. They don’t always end up in the right place, so you have to rummage through all your code and move things, by hand, to make sure they make sense.

Hex numbers. For a bunch of color constants, my Java code had hexadecimal constants. The Java -> Kotlin translator converted those to base ten. (Yuck!) Converting them back to hex, by hand, bumped me into a known bug in Kotlin. The issue is that you can’t represent a hex number whose high bit is one, because, as a signed int, that would be a negative number. The workaround is appending .toInt() to your hex number in these cases.

Mixed integer / floating point expressions. I have some fun logic that tries to make my second-hand sweep in a non-linear way, like some European rail clocks. I had it working just fine in Java, but the Kotlin translator didn’t produce an equivalent expression, resulting in incorrect results. The fix was no big deal, but if you’ve got such expressions in your Java code, pay careful attention to the output of the Kotlin translator.

Making sure Android can find your classes. Android has this AndroidManifest.xml file, which specifies a bunch of entry points for your Activity and other such things. I had to add explicit @file:jvmName annotations to the relevant Kotlin classes to make everything work. EDIT: Apparently I was wrong about this and the annotation isn’t necessary. Anyway, if you get weird logcat errors about it not being able to find your classes, pay attention to this.

Taking advantage of Kotlin features. Once I finally had things working, I started leveraging the cool features of Kotlin. The kotlinx.android.synthetic feature makes it much easier to hook into your UI widgets. That was easy. I also had some code that was perfect to rewrite using Kotlin’s functional list mapping, filtering, and sorting. That make things much more clean and concise. Similarly, I was able to leverage Kotlin data classes in a few places, again simplifying my code from the original translation.

Functional-reactive. I made a brief stab at this, since the part where I fetch calendar data from the system calendar provider is a complicated mess of AsyncTask, BroadcastReceiver, and Handler which would seem to be crying out for an application of reactive programming. The relevant “Rx” libraries seem to be in a state of flux, so I decided to put this off for later.

All said and done. My previous APK, based entirely on the Java code, and after compression via ProGuard, was about 1.5MB. My new APK, with new features, including a new icon for saying “hey, I can’t see your calendar” and with whatever extra Kotlin libraries included, is about 1.4MB. That’s not a typo. The translation to Kotlin somehow made my final APK smaller than it originally was, despite all the new features I added. At this stage, I’m still testing it out on myself to make sure I’ve got it all working, and Google hasn’t started pushing Android 6 to wear devices yet, so I’m not going to push the APK to the public. That said, if any of you want to be beta testers, do let me know.


#2

Thanks for posting this!

One note: what problem exactly did you run into that led you to adding the @file:JvmName annotations? The annotations affect the name of the class into which top-level properties and functions defined in a file are placed. I see that you’re using this annotation also in files which only contain regular classes, which has no effect.


#3

I was getting errors when Android was trying to launch my activity – I think it was PhoneActivity – with logcat indicating that the Android internal launcher couldn’t find the class. At that point, I decided to take a scorched earth approach and add the annotations to each and every file that’s mentioned in an AndroidManifest.xml file.


#4

One last thing: you should probably annotate WeakReference<Foo>.get() to return Foo? rather than Foo. I noticed a few places where my code was type checking properly but wasn’t doing the necessary null checks.


#5

Could you please consider removing these annotations? Your app serves as a good example for other Android developers interested in Kotlin, and it would be unfortunate if they started copying this pattern because they saw it in your code.


#6

WeakReference is a Java class, and we are not providing any annotations for Java classes at the moment.


#7

So are all of my @file:jvmName annotations no-ops? I can remove them and try again.

In terms of a “good example”, I’m still finding obscure bugs. This isn’t 100% ready to rock yet…


#8

Well that’s interesting. I pulled them all out and everything’s working again. That’s weird.

Anyway, one more warning for anybody using the Java -> Kotlin converter: automatic getter/setter inference. The converter looks for methods with names like getFoo() and setFoo() and tries to just have a foo property with custom get() and set() methods. Two weird bugs that I had to track down came from this conversion. The moral of this particular story is to treat the Java -> Kotlin converter as an amazingly helpful but far from infallible tool.


#9

What a fantastic idea. I have a Huawei round Android Wear watch, so I just grabbed this watch face - thanks!

The lack of nullability info for Android/JDK frameworks is sad and makes the null safety less useful than it could have been. In the early days there was a tool that would try to infer nullability annotations but it didn’t work all that well so JetBrains ended up with their current approach.

It’d be great if post-1.0 a backwards compatible way to solve this was found. Perhaps, in fact, the OpenJDK project would accept contributions of annotations. As Android is now switching to OpenJDK classes as well, it’d help kill two birds with one stone.

The main issue is that annotating everything with @NotNull is really painful. In my own Java code I only used @Nullable but there seemed to be no way to tell IntelliJ to assume @NotNull unless otherwise specified. If there was, it’d be easier to annotate large Java codebases.


#10

It’s generally considered good Android style to null things out if the OS tells you you’re being shut down

I’ve seen this in code but it’s mostly unnecessary. The GC usually deals with this on its own and the only thing you have to be careful with is referencing Contexts in long running tasks. If you want to be extra careful, include something like LeakCanary in your debug builds and/or use the memory profiler integrated into Android Studio to check if Activities are leaked. There’s an option in the developer settings on the device to never keep Activities, which makes testing for leaks easy.


#11

That’s not true.


#12

To be clear, I’m not suggesting that you shouldn’t rely on the garbage collector to do its job. Instead, if you’ve got tasks running asynchronously that should only be running when you’re in the foreground, then it’s imperative that you stop them when you get paused. At that point, it’s all a matter of what to do with your reference to that task. Nulling it out, then making a new one when it’s time to bring it back, is exceptionally convenient. And if you take the tactic that you want to avoid passing your contexts around everywhere, you have to be prepared for them to die, which leads to a lot of weak references. Similar logic seems to apply in a variety of situations.

My own watchface project began as my “I want to learn my way around Android” project, and was initially built by pasting together bits of StackOverflow, Google reference code, and whatever else. I’ve been slowly cleaning it up as I’ve learned better ways of doing things.


#13

I think you don’t fully understand the reference chain when activities get destroyed. The framework has references to your activity which in turn has references to other stuff. If you use anonymous classes like AsyncTask, these classes also have references to your activity. That’s not a problem. A problem is that your anonymous class is referenced by a background thread which is preventing the Activity from being collected.

When onDestroy is called, the framework will drop all references to your Activity and create a completely new instance when the Activity is restarted. Your job is to make sure no static fields and other threads reference your old activity. As you correctly point out, you can cancel AsyncTasks for that matter but what is the point of nulling out the reference?

Please try not to spread wrong information especially since you said that this your learning project.


#14

I decided to have a go at eliminating as much of this nulling behavior as I could and most of it is now indeed gone. I’ve made a variety of other code simplifications and improvements to the use of idiomatic Kotlin things. I’m reasonably happy with how the code looks now. I’ve pushed all the updates to my Github project.

Now it’s all about testing, so I don’t disturb any of my users with bugs before deploying the new APK.


#15

I think it’s pretty true for fragments. Once you’ve pushed a bunch of fragments in the back stack using FragmentTransition.replace(), your fragments’ onDestroyView will be called, however, the fragment instances will be kept in the memory. Next time the fragment gets popped back, onCreateView on the same instance will be called again to create new views. Nulling old views immediately will definitely help free up UI resources (assuming you don’t reference them elsewhere than the fragment itself), especially when you have a large back stack.

ButterKnife looks helpful in this scenario, where you can call ButterKnife.unbind to nullify these views. But using lateinit on fragment views doesn’t seem right to me, as these fields are meant to be nullified later, not just initialised later. Furthermore, using generated Java code to break the ‘non-null contract’ on fields seem more wrong to me.

I do find a cleaner way of doing this: if you have a lot of views in fragment, create an internal class holding all views. By doing this, the fragment just needs to hold a nullable instance of the view holder class. Thanks to Kotlin’s compact syntax, this turns out to be a good outcome:

class MyFragment : Fragment() {
    // The view holder class
    internal class Views(rootView : View,
                         val titleView : View = rootView.findViewById(R.id.title),
                         val contentView : View = rootView.findViewById(R.id.content))

    private var views : Views? = null

    override protected fun onCreateView(....) = inflate(R.layout.fragment).apply {
        // Create views with inflated rootView
        views = Views(this).apply {
            titleView.text = "title"
            contentView.text = "content"
        }
    }

    override protected fun onDestroyView() {
        // Nullify the views object so all views can be freed up.
        views = null
    }

    // Do something with title view
    private fun func1() {
        views?.titleView?.text = "Title has changed"
    }

    // Do something with both titleView and contentView
    private fun func2() {
        views?.apply {
            titleView.text = "title has changed"
            contentView.text = "Content has changed"
        }
    }
}

#16

Sounds like your fragments don’t get garbage collected. Nulling out views is just fighting the symptoms then. You should analyze a heap dump to find what keeps a reference to the fragments.


#17

No, this is how fragments work in back stack, and in ViewPager (using FragmentPagerAdapter) as well, see documentation in FragmentTrascation.detach:

public abstract FragmentTransaction detach (Fragment fragment)

Added in API level 13
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.

Parameters
fragment The fragment to be detached.
Returns
Returns the same FragmentTransaction instance.

I know setting references to null doesn’t work most of the time, but in this case, I believe it will help, thanks to Android’s deliberately keeping the instances of fragments.


#18

Are you sure about the fragment backstack? Looking at the documentation of popBackStack(), it doesn’t sound like it only detaches the fragment from the UI.


#19

You got the opposite. I meant addToBackStack. The fragments you pushed into backtrack using replace will be made detach from UI.


#20

I can’t reproduce the behaviour you’re expecting. Running this sample and pressing the back button n times, there are only 10-n fragments in a heap dump after I’ve forced garbage collection. It’s the same if I use replace instead of add in the transaction.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 10; i++) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragmentContainer, new Fragment())
                    .addToBackStack("" + i)
                    .commit();
        }

    }

}