Do not always trust @JvmOverloads

Mateusz Młodawski
Nov 15, 2018 · 5 min read
Bugs hall of fame

You can lose your custom view style when subclassing a TextView, Button or other components. Here’s a cool way to do this in Kotlin.

It’s common when writing in Kotlin, to combine many constructors into one by @JvmOverloads annotation, especially when subclassing Android views. Usually, it’s ok, but sometimes you can run into some unexpected issues.

Let’s take a look at the definition:

Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.

If a method has N parameters and M of which have default values, M overloads are generated: the first one takes N-1 parameters (all but the last one that takes a default value), the second takes N-2 parameters, and so on.

Sounds good, so we usually combine all those constructors:

into one:

Step 0: The problem

Let’s take a look at TextInputEditText from the Design library.

In our custom class

we can replace all those constructors with only one:

This is also the code that will be generated by Android Studio.

So let’s have an Activity that has two TextInputEditText components, first of them with all three constructors, and the second one with @JvmOverloads annotation.

Will they look and behave the same at the end?

As you can see, the second one with @JvmOverloads is not working at all.

What happened? Why do we have some styling issues?

Step 1: Understanding the @JvmOverloads annotation

Let’s go back to the @JvmOverloads definition for a second. We know that two overloads will be generated (in our case N=3 and M=2) by the Kotlin compiler. So we will end up having three constructors similar to:

So always in our custom class, we will call the three-param constructor from TextInputEditText.

Step 2: Understanding View’s constructors

Now let’s focus on View’s constructors for a while.

When a View is inflated from an XML file, its’ second constructor is called. This constructor then calls a three-param constructor.

So why does this three-param constructor even exist if it usually takes 0 as the third parameter? The answer is in the docs.

This constructor of View allows subclasses to use their own base style when they are inflating.

Classes subclassing the View class can pass their own style to modify all of the base view attributes. Easy.

Now we are onto something.

Step 3: Understanding what went wrong

At this point, from Step 1, we know that because of @JvmOverloads annotation we always call a three-param constructor, and from Step 2, that classes subclassing the View class can use this three-param constructor to pass their own style.

Let’s go back to our TextInputEditText and take a look at its constructors, especially the second one:

This is exactly what is happening there — it passes a style, which we are loosing, when calling the three-param constructor by ourselves.

Step 4: Fixing

From the implementation of TextInputEditText we know that it passes as a third parameter, so we can do a fix by setting it as our default value of the defStyleAttr param:

From now on, everything will work as planned unless… the constructor implementation of TextInputEditText in the Design library will change, for example by passing some other style there.

Another example. The subclassed component we are using, that worked perfectly fine, suddenly looks different than in other parts of our app, because it’s new version started passing a style, and we are subclassing it in one place only.

How to stay safe?

Just do not use @JvmOverloads when it can be potentially risky. Implement all constructors and write some explanation comment so it could be understood in future.

Some final notes

I’ve picked TextInputEditTextas an example but the same situation will occur with Button, EditText, RadioButton, Switch, and many other components.

You can find an example implementation showcasing those issues, at my Github repo.

A key takeaway from this article — if you are getting some unexpected styling issues with a subclassed view, it’s worth to check if you are using @JvmOverloads. Maybe there’s the bug.

Please let me know if you have any thoughts on this topic.

Thanks for reading!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store