Photo by Pathum Danthanarayana on Unsplash

The Argument Over Kotlin Synthetics

Josh Feinberg
ProAndroidDev
Published in
4 min readFeb 1, 2019

--

It all started with a commit message:

kotlinx.android.synthetic is no longer a recommended practice. Removing in favour of explicit findViewById.

The outpour of anger showed up in a Reddit thread on Android shortly afterwards, wondering why Google continues to release and remove new APIs, and with no explanation as to why this is no longer a recommended practice.

But hold up, it turned out this commit message wasn’t meant to be a global broadcast of a new policy from Google — a Developer Advocate from Google jumped in to the thread to share a bit more about what was going on.

The first, synthetics were never a formally recommended practice. The API was not built by Google and is part of the Kotlin Android Extensions built by JetBrains. Google did use this in some samples, but then established it wasn’t best practice. They now have moved onto not teaching it in their Udacity courses and removing it from samples.

The second, and more important, is there is no requirement for developers to get rid of synthetics. There are currently no plans released to get rid of the extensions, and applications are free to use them as they see fit.

So, why did the commit message say that they’re not recommended? Here’s a few thoughts on the downsides to synthetics.

They Are Kotlin Only

The easiest argument is that Kotlin synthetics only work with…Kotlin. There are still plenty of Android Java developers, and to have separate styles based on the language could get confusing in a single code base.

They Don’t Expose Nullability

If a survey were taken of developers’ favorite features of Kotlin, null safety would be near the top. However, with synthetics, all views are declared as platform types (ex: TextView!) and the compiler has no idea if it should be null or not. That leaves a possibility that the view id referenced could not be inflated and the application crashes. This can become even more of a problem when working around different configurations where a developer could forget that view is now nullable and leads to crashes (though the IDE will now warn you if the view doesn’t exist in all configurations).

The Code Generated Is Not Guaranteed To Be Performant

With all the code-gens that exist now, it is easy to assume that synthetics were programmed to be as performant as possible. However, looking at the code generated, we can see some issues. From Antonio Leiva’s post about this feature, it is shown that internally all the code is changed to call findCachedViewById. When decompiling the byte-code, it looks something like this:

public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}

View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}

return var2;
}

A cache is created using a HashMap, then views are inserted by mapping their id var1 to the actual view var2. However, HashMap can only use objects, whereas view id’s are primitives, meaning that on each insert and lookup we are required to box the primitive into an object type. This may seem like a minor performance issue, but it is a reason to always look behind the “magic” of these extensions.

You also have the problem pointed out here that there is a possibility of using the cache incorrectly in a RecyclerView. By using itemView.id you will no longer get the cache and instead do a lookup each time. A simple mistake like not using the LayoutContainer interface or calling the id after itemView now introduces a potentially large performance issue.

(Edit: It was pointed out to me that you can configure synthetics to use a SparseArray as a backing to get rid rid of the boxing, but then you end up with slower lookup times, O(log n))

Everything Exists In A Global Namespace

In the IDE, if you set your content view to a layout, then type an id that only exists in a different layout, the IDE will happily let you autocomplete and add the new import statement for you. Unless the developer specifically checks to make sure their import statements only import the correct views, there is no safe way to verify that this won’t cause a runtime issue. As everything is global, one has to be careful to make sure that they only use views they are expecting and ignore the autocomplete.

Typing Isn’t Guaranteed

When creating a view for two configurations, if you specify R.id.text as TextView in one configuration, and CustomTextView in the second, you now lose all your typing information and synthetics can only assume you are a View!. You can no longer use setText even though CustomTextView extends TextView and have to remember to typecast.

In Conclusion

It is important to reiterate that if none of this matters to you, that is totally fine. An Android Developer Advocate responded to that Reddit thread stating that these issues might not apply to you and provided a few other options such as Data Binding and Butter/KotterKnife that can also be used to solve this problem. It’s possible in your application the performance hit could be negligible, it might only have a single configuration, and you might always be safe on the synthetic views you import.

Remember that the commit said it is no longer recommended, not that you should not use it. Google is there to provide guidelines, but we as developer’s can make our own choices inside our code bases.

--

--