Jetpack / Kotlin / KTX

KTX: Miscellaneous

KTX is a series of Kotlin extension functions for Android that first appeared in February 2018. They can simplify many repetitive tasks or those which require boilerplate code. However, they are not always easy to discover. I find it useful to periodically scan through the list here. I find this a good way of discovering extensions that I wasn’t aware of. So in this post, we’ll look at some that I find particularly useful. In this post we’ll cover various miscellaneous extensions from ktx-core.

Custom View Attributes

The first set of extensions that we’ll look at are very useful when creating custom Views. Technically they could have been included in the earlier KTX: Views post, but that was already quite long and focussed on extensions to View itself. A common use-case when creating a custom View is that we declare some custom attributes for that View and need to look up these values from styled attributes:

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int = R.attr.customViewStyle,
    defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    var customAttr: Boolean = false
    
    init {
        context.obtainStyledAttributes(
            attrs,
            R.styleable.CustomView,
            defStyleAttr,
            defStyleRes
        ).run {
            customAttr = getBoolean(R.styleable.CustomView_customAttr, false)
            recycle()
        }
    }
}

One easy to make mistake here is to forget to call recycle() which can result in leaking the TypedArray. Although there is a lint warning for this, it is an easy mistake to make. Another potential issue which can be tricky to spot is if customAttr is not specified in either that layout or style, then it will default to false

There are some KTX extensions which can help avoid this mistake. They can also help us to catch missing attribute issues early:

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int = R.attr.customViewStyle,
    defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    var customAttr: Boolean = false
    
    init {
        context.withStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, defStyleRes) {
            customAttr = getBooleanOrThrow(R.styleable.CustomView_customAttr)
        }
    }

}

This is much more compact and easy to read, but there are some other niceties going on.

Firstly, the withStyledAttributes() extension behaves a little like a Kotlin use block. It calls recycle() for us after the trailing lambda has executed.

Secondly, we use getBooleanOrThrow() instead of getBoolean(). This will throw an exception if customAttr is not defined. Essentially this will fail fast and catch any missing attributes much earlier. Rather than having to debug why something isn’t working as we expect because it is using a default value. This fail-fast behaviour is not always required, but it is nice to have it when necessary. There are similar get*OrThrow() extensions for all of the types supported by TypedArray.

Database

When retrieving nullable values from a database we must check whether the column is null before reading from it:

val str: String? = if (cursor.isNull(COLUMN_INDEX)) cursor.getString(COLUMN_INDEX) else null

There’s a KTX extension to do this for us:

val str: String? = cursor.getStringOrNull(COLUMN_INDEX)

Another common use-case when dealing directly with a database is the requirement to perform multiple operations within a transaction. These operations can be rolled back if any of the individual ones fails:

db.beginTransaction()
try {
    // Do stuff
} finally {
    db.endTransaction()
}

The KTX extension simplifies this and, as ever, makes it much easier to read and understand:

db.transaction {
    // Do stuff
}

Animation

Regular readers of Styling Android will know that animation is something very close to my heart. The KTX extensions around this make me very happy indeed!

Often we need to perform an operation when an animator finishes. For example, if we are fading out a View, we may want to adjust the alpha value during the animation to perform the fade. Once the fade completes we may then want to change the visibility of the View. If we are using ViewPropertyAnimator then this can be quite straightforward by using its withEndAction() method:

view.animate()
    .alpha(0f)
    .setDuration(resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()) 
    .withEndAction { 
        view.isVisible = false
    }
    .start()

However, if we are using a standard Animator, then we have to use an AnimatorListener
instead. For example, if we are performing multiple animations then we may need to use an AnimatorSet and have to use an AnimatorListenerAdapter:

AnimatorSet().apply {
    // Add some Animators
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator?) {
            super.onAnimationEnd(animation)
            view.isVisible = false
        }
    })
    start()
}

As you may have already guessed, KTX simplifies this for us:

AnimatorSet().apply {
    // Add some Animators
    doOnEnd { 
        view.isVisible = false
    }
    start()
}

There are also KTX extensions for all of the other animation events such as cancel, pause, repeat, resume, and start.

As well as extensions for View animators there are a set of similar extensions for the Transitions API. I won’t bother detailing them here as they are much the same as the View animator ones.

Conclusion

We’ve covered a fair few KTX extensions in this series, but it has been far from exhaustive. There are plenty of others. Also, we have only covered those in the KTX core library. There are other libraries tied to specific Jetpack libraries such as Lifecycle, Navigation, Room, Paging, etc. Please let me know either in the comments or by email if you’ve found this series useful. And if there are specific Jetpack library KTX extensions that I should cover.

I usually publish a GitHub repo containing the working code for my blog posts. However, in this case, the individual code snippets pretty much stand up on their own, so I haven’t published a repo in this case.

© 2020 – 2021, Mark Allison. All rights reserved.

Copyright © 2020 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

1 Comment

Leave a Reply to Darren Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.