Custom Views in Android with Kotlin (KAD 06)

[et_pb_section bb_built=”1″ admin_label=”section” inner_width=”auto” inner_max_width=”none”][et_pb_row admin_label=”row” background_position=”top_left” background_repeat=”repeat” background_size=”initial” width=”80%” max_width=”1080px”][et_pb_column type=”4_4″][et_pb_text background_position=”top_left” background_repeat=”repeat” background_size=”initial” _builder_version=”3.23.1″ text_text_shadow_horizontal_length=”text_text_shadow_style,%91object Object%93″ text_text_shadow_horizontal_length_tablet=”0px” text_text_shadow_vertical_length=”text_text_shadow_style,%91object Object%93″ text_text_shadow_vertical_length_tablet=”0px” text_text_shadow_blur_strength=”text_text_shadow_style,%91object Object%93″ text_text_shadow_blur_strength_tablet=”1px” link_text_shadow_horizontal_length=”link_text_shadow_style,%91object Object%93″ link_text_shadow_horizontal_length_tablet=”0px” link_text_shadow_vertical_length=”link_text_shadow_style,%91object Object%93″ link_text_shadow_vertical_length_tablet=”0px” link_text_shadow_blur_strength=”link_text_shadow_style,%91object Object%93″ link_text_shadow_blur_strength_tablet=”1px” ul_text_shadow_horizontal_length=”ul_text_shadow_style,%91object Object%93″ ul_text_shadow_horizontal_length_tablet=”0px” ul_text_shadow_vertical_length=”ul_text_shadow_style,%91object Object%93″ ul_text_shadow_vertical_length_tablet=”0px” ul_text_shadow_blur_strength=”ul_text_shadow_style,%91object Object%93″ ul_text_shadow_blur_strength_tablet=”1px” ol_text_shadow_horizontal_length=”ol_text_shadow_style,%91object Object%93″ ol_text_shadow_horizontal_length_tablet=”0px” ol_text_shadow_vertical_length=”ol_text_shadow_style,%91object Object%93″ ol_text_shadow_vertical_length_tablet=”0px” ol_text_shadow_blur_strength=”ol_text_shadow_style,%91object Object%93″ ol_text_shadow_blur_strength_tablet=”1px” quote_text_shadow_horizontal_length=”quote_text_shadow_style,%91object Object%93″ quote_text_shadow_horizontal_length_tablet=”0px” quote_text_shadow_vertical_length=”quote_text_shadow_style,%91object Object%93″ quote_text_shadow_vertical_length_tablet=”0px” quote_text_shadow_blur_strength=”quote_text_shadow_style,%91object Object%93″ quote_text_shadow_blur_strength_tablet=”1px” header_text_shadow_horizontal_length=”header_text_shadow_style,%91object Object%93″ header_text_shadow_horizontal_length_tablet=”0px” header_text_shadow_vertical_length=”header_text_shadow_style,%91object Object%93″ header_text_shadow_vertical_length_tablet=”0px” header_text_shadow_blur_strength=”header_text_shadow_style,%91object Object%93″ header_text_shadow_blur_strength_tablet=”1px” header_2_text_shadow_horizontal_length=”header_2_text_shadow_style,%91object Object%93″ header_2_text_shadow_horizontal_length_tablet=”0px” header_2_text_shadow_vertical_length=”header_2_text_shadow_style,%91object Object%93″ header_2_text_shadow_vertical_length_tablet=”0px” header_2_text_shadow_blur_strength=”header_2_text_shadow_style,%91object Object%93″ header_2_text_shadow_blur_strength_tablet=”1px” header_3_text_shadow_horizontal_length=”header_3_text_shadow_style,%91object Object%93″ header_3_text_shadow_horizontal_length_tablet=”0px” header_3_text_shadow_vertical_length=”header_3_text_shadow_style,%91object Object%93″ header_3_text_shadow_vertical_length_tablet=”0px” header_3_text_shadow_blur_strength=”header_3_text_shadow_style,%91object Object%93″ header_3_text_shadow_blur_strength_tablet=”1px” header_4_text_shadow_horizontal_length=”header_4_text_shadow_style,%91object Object%93″ header_4_text_shadow_horizontal_length_tablet=”0px” header_4_text_shadow_vertical_length=”header_4_text_shadow_style,%91object Object%93″ header_4_text_shadow_vertical_length_tablet=”0px” header_4_text_shadow_blur_strength=”header_4_text_shadow_style,%91object Object%93″ header_4_text_shadow_blur_strength_tablet=”1px” header_5_text_shadow_horizontal_length=”header_5_text_shadow_style,%91object Object%93″ header_5_text_shadow_horizontal_length_tablet=”0px” header_5_text_shadow_vertical_length=”header_5_text_shadow_style,%91object Object%93″ header_5_text_shadow_vertical_length_tablet=”0px” header_5_text_shadow_blur_strength=”header_5_text_shadow_style,%91object Object%93″ header_5_text_shadow_blur_strength_tablet=”1px” header_6_text_shadow_horizontal_length=”header_6_text_shadow_style,%91object Object%93″ header_6_text_shadow_horizontal_length_tablet=”0px” header_6_text_shadow_vertical_length=”header_6_text_shadow_style,%91object Object%93″ header_6_text_shadow_vertical_length_tablet=”0px” header_6_text_shadow_blur_strength=”header_6_text_shadow_style,%91object Object%93″ header_6_text_shadow_blur_strength_tablet=”1px” box_shadow_horizontal_tablet=”0px” box_shadow_vertical_tablet=”0px” box_shadow_blur_tablet=”40px” box_shadow_spread_tablet=”0px” z_index_tablet=”500″]

When we saw the article about classes, you may remember that in general only one constructor is used. This is a problem for creating custom views.

The Android framework expects to have several constructors available based on where and how the view is being created (by code, using XML, if a theme is set…), so we can not ignore this case.

For this, Kotlin team provided the ability of having several constructors in the same class, just for cases like this.

Compatibility with Java is an essential requirement for Kotlin, so whenever you see yourself blocked, think there must be some way to achieve what you need.

Creating a Custom View in Kotlin

Even if you already have experience creating custom views and some Kotlin knowledge, it’s possible that the first time you create a custom view in Kotlin, you fin it a bit complicated.

Implementing several constructors is one of the most convoluted things in Kotlin, precisely because it’s a rare use case.

But don’t worry, once you’ve seen it once, the rest are very similar.

Note: this article is still valid to understand how to use multiple constructor in a Kotlin class, but as Kirill Rakhman mentions in the comments, there’s a better way. Read up to the end.

Create a class that extends View

To do this, create a class as we saw earlier. Make it extend from View for instance, but do not indicate any constructor:

class KotlinView : View {
}

You’ll see that this code won’t compile, because it requires calling the constructor of the parent class.

If you know that you are only inflating your view from Kotlin code, for example, you could use the unique constructor form we already saw:

class KotlinView(context: Context?) : View(context) {
}

But be careful, because if someone decides to add this view to an XML, it will fail.

Spoiler: Do you see that question mark right after the Context? In Kotlin, if we want a variable or parameter to be null, we have to explicitly specify it using a question mark. Then the compiler will force us to check it’s not null before doing anything with it. We’ll see it in next articles.

Implement the multiple constructors

The constructors use the constructor reserved word, and need to define in their declaration which constructor they call. It can be another constructor of the same class (using this) or the one of the parent class (using super).

This is how you define the constructors for an Android view:

class KotlinView : View {

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        ...
    }

}

An easier implementation

The alternative that Kirill mentions in the comments (thanks for that!) is much simpler and easier to read. It’s based assigning default values for constructor arguments, but needs a little tweak.

The thing is that when you create a constructor (or any function) with default values for some arguments, the generated bytecode only allows to use those default values in Kotlin. If you use that constructor in Java, you will be forced to specify a value for all arguments.

That is because Java doesn’t have this language feature. In Java, you would usually solve it by generating function overloads with all the variations you need.

You can get this code automatically generated by using the @JvmOverloads annotation in Kotlin.

The code would be like this:

class KotlinView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)

Conclusion

Once you see it, it’s not so complicated to generate your custom views thanks to the existence of multiple constructors, and even much easier using the @JvmOverloads annotation.

This can be useful for any class where you need more than one constructor. But in general, as you can assign default values to the parameters (and thus avoid overloads), it’s not so common to need more than one.

Still not convinced about Kotlin for Android? Start using it as soon as possible! Thanks to the previous articles you can learn more about Kotlin, or I recommend that you sign up to my free training here.

[/et_pb_text][/et_pb_column][/et_pb_row][/et_pb_section]

28 thoughts on “Custom Views in Android with Kotlin (KAD 06)”

  1. There’s a better way:

    class Foo @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr)

    1. Oh! Didn’t know that one, thanks! I’ll review the article and add your alternative.

    2. I am afraid this way could break some View subclasses such as TextView subclasses where the shortest constructor has to call the one argument TextView’s constructor to have it’s style set properly (hence, shouldn’t call the longer super constructors).

      1. As of android api 27. TextView has a simple overload pattern, so it should work fine wiht @JvmOverloads

      2. Anyway, what’s the point of declaring the constructor with three parameters if your custom view is final (which is the default in Kotlin)?

  2. @JvmOverloads crashes for me every second run with Illegal class access: (…) attempting to access ‘kotlin.jvm.internal.DefaultConstructorMarker’

    I suspect it doesn’t work well with Instant run

  3. Arslan K Charyyev

    Good day. Why is Context in the first 3 constructors is nullable, but in the JvmOverloads constructor is not? In what cases can context be null at all?

    1. That’s because the Android framework is not well annotated, and they don’t have the `@NonNull` annotation set to the context, so when you auto-generate the constructors, it writes the less restrictive option. You can remove them, because the context can’t really be null when you create a view, and everything will keep working. In fact, I’m going to change that, it’s a bit confusing. Thanks for writing!

  4. “Some of them (as TextView for instance) need to call super parent to initialize the style.”
    Has anyone figured out how to call the super parent to get proper styles?

  5. Probably everyone knows it by now, but this article (https://android.jlelse.eu/building-custom-component-with-kotlin-fc082678b080?source=userActivityShare-fdb1e1d70e8f-1504076470) points to a workaround for this which is:

    class MyTextView: TextView {
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
    ) : super(context, attrs, defStyleAttr)

    @TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int)
    : super(context, attrs, defStyleAttr, defStyleRes)

    }

    1. But I think this is solving a different issue? This looks like a way to also support the new constructor, but I’d say there are some views that would still have issues with the first constructor.

  6. Christian Würthner

    Cool article! I just stumbled across on issue which was already noted by Louis above: When subclassing TextView or EditText, one must use the implementation with the three separate constructors as mentioned in the article. But your implementation did not work for me. Instead of calling this(context, null) in the first and this(context, attrs, 0) in the second, you must call super(context) in the first and super(context, attrs) in the second constructor. Otherwise the TextView/EditText will have no styles applied. Somewhat confusing…

    The correct implementation for TextView subclasses looks like this:

    class MyEditText : EditText {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    }

    1. Yeah, exactly. The solution posted here is the equivalent to the shorter one, but it still has issues with some views (`AppCompatTextView` and its subclasses are the only ones I’m aware of). You need to call super for those, as you did.

    1. Yeah, that’s exactly the problem. I’m not sure if there are more views like this, but `TextView` is the perfect example of why one should be cautious when using this.

      1. Of course there are, most of the views have default style attribute which you can define in the Theme, if you override it with 0, setting that attribute in the Theme won’t take any effect.

    2. Thank you so much for this article. It exactly cleared why my CustomEditText with Single Constructor using @JvmOverloads wasn’t working. Now i extend the actual defStyle instead.

Comments are closed.