Experience porting an Android app to Kotlin

Sorry for my poor communication skill.

I was not talking about popping up back stack. I was talking about when you add 100 fragments into the back stack, don’t press back, leave them in the back stack. Then you should have 100 fragment instances in the memory, non GCable.

Oh, I see what you mean. Yes, your solution looks quite elegant in that case. I’ve never thought of it before, as I’ve never had more than 3-4 fragments in the back stack anyway.

I decided to have a go at porting my other Android Wear app to Kotlin. This one is a stopwatch / countdown timer (Github link, Play Store link). This experience went a bit faster than the last time, since I had a much better idea what’s going on, but it also turned up a few more gotchas.

FWIW, this was all done with the latest Android Studio 2.0 Preview 9 with the latest plugins and such.

Fun with the Java to Kotlin converter: A few weird things happened this time. The translation of get/set methods made a critical error on a setter method, generating something like this:

 var isVisible: Boolean = false
 set(visible) {
     Log.v(TAG, "visible: " + visible)
     this.isVisible = visible
     ...
}

The proper code that it should have generated is field = visible. The generated code instead recurses infinitely. Oops. Also, I again had to spend a fair bit of time dealing with declaring Android callback types as possibly null. Some future world with annotations that catch this sort of thing would be highly desirable.

Callbacks from an XML layout: This app has a bunch of XML files that specify widget layouts, and uses Android’s reflective ability to call public methods. The raw XML looking something like this:

<ImageButton
        android:id="@+id/timerButton"
        android:onClick="launchTimer"
        ... />

In this case, launchTimer is a method on the Activity, as you’d expect: fun launchTimer(view: View) = { ... }. Everything runs correctly, but Android Studio highlights launchTimer in the XML with “Method ‘launchTimer’ is missing in ‘StopWatch’ activity or has an incorrect signature”. Similarly, in the activity, the method in question is greyed out and noted as not being called. The Kotlin/Android integration isn’t yet connecting this particular call edge correctly as it does with Java.

Limitations in kotlinx.android.synthetic: For Android Wear apps, it’s a standard practice to have two layouts – one for round watches and one for square. You can have widgets with all the same names, and the helper library will instantiate whichever layout is appropriate for the device. The traditional findViewById(R.id.whatever) code you might write then doesn’t have to worry about it, since you’ve got the same IDs in each layout file. So far as I can tell, the synthetic stuff can tell the difference, and insists on importing either the “round” or “square” layout rather than letting you have a “generic” layout. For now, anyway, I’m just doing things the more verbose way.

Let me tell something for future readers. A root view reference of a replaced Fragment and pushed in backstack is hold by FragmentManager and released only in Fragment.onDestroy() and it’s called only on Activity.onDestroy() or when it’s popped. Even if you unbind your bound child views it won’t be enough to release their memory. I suppose the rootView must also remove all its children to make the fragment hierarchy eligible for GC.

Hi do you mind sharing your finding that can prove fragments which get replaced/pushed in back stack are not destroying views? I read the support library code and see the rootView does get nullified, it’d be great to see if there’s another path that holds references to rootView. Cheers.

I mean a fragment that is pushed into the backstack (and replaced with another) is getting onDestroyView() call but its mView reference is not null effectively holding the entire hierarchy in memory. Fragment.mView is nullified only before onDestroy(). If you have 100 fragments in backstack all 100 view hierarchies will stay in memory (by default). I suppose manual removal of the fragment’s root view children in onDestroyView() is going to fix that. I’ve discovered this because I’ve got a fragment backstack that can grow arbitrarily large depending on user’s clicks. Each pushed fragment is showing movie details. Inside it the user can click on “similar movie” and navigate to a new fragment with a different movie. I also was bitten by Google Issue Tracker. That’s a platform memory leak where InputMethodManager holds a reference to views. In my case that was the main culprit. Think to go with Conductor and fuck off all the fragment madness!

maybe the converter should translate code with more “lateinit var x:X” instead of “var x:X? = null”