Android KTX Jetpack

Even Sweeter Android development with Android KTX

Posted on by Gabor Bakos

At I/O 2018, Google introduced the Jetpack framework collection to make the life of developers easier. Jetpack is both a library and a collection of guidelines about how Google imagines a testable, robust mobile development in modern times. One of its key components is the Android KTX library which I raved about since it's first alpha releases.

It is a small extension library which leverages Kotlin's capabilities to make development on Android with Kotlin even more concise, pleasant and idiomatic, without adding new features to the core API. Under the hood, it uses the feature called extension functions, allowing us to add new functionality to exostong objects without inheritance, even if we don't have access to the object. The library also uses operator overloading (you can modify the behavior of existing operators) and declaration destructuring (allows you to declare an object's field as variables in one line).

To get started, you have to add the Google repository to your repositories:

repositories {
    google()
}

Then you have to add the library to your dependencies:

dependencies {
    implementation 'androidx.core:core-ktx:1.0.0-alpha1'
}

From here on you're ready to roll! Android KTX is following the best practice of the Google libs regarding granularity as it is broken down to 13 distinct libraries, where you can include the one you need (you can find them here).

As I mentioned before, Android KTX is a library bringing a more idiomatic and pleasant way of working, giving you more of a Kotlin feeling. How is that possible? They created excellent DSL descriptors to eliminate boilerplate codes, where you can focus purely on what is essential: the problem itself. The result is a more readable and concise codebase, with less boilerplate codes.

The library offers a wide variety of tools, so to get a grasp on the features I want to share few prominent examples, and ones that I'm actively using.

SharedPreferences

SharedPreferences requires a vast builder, plus it is easy to forget the apply() call at the end. Never again:

Before

sharedPreferences.edit()
    .putBoolean("key", value)
    .apply()

After

sharedPreferences.edit { putBoolean("key", value) }

Displaying a toast message

This traditionally requires too many unnecessary parts - KTX offers a more comfortable way to display a toast, with the message as the single parameter.

Before

Toast.makeText(context, R.string.toast_message, Toast.LENGTH_SHORT).show()

After

toast(R.string.toast_message)

Fragment transactions

Fragment transactions require yet another colossal builder to use. Not anymore:

Before

supportFragmentManager
    .beginTransaction()
    .replace(R.id.container, customFragment, CustomFragment.TAG)
    .commitAllowingStateLoss()

After

supportFragmentManager.transaction(allowStateLoss = true) {
            replace(R.id.container, customFragment, CustomFragment.TAG)
}

Global change listener for the View tree

This is one of my favorites. When using a ViewTreeObserver, you have to add a listener, wait for the callback to fire, then remove it. It is much simpler to call doOnPreDraw and then do the thing that you originally wanted, without the extra ceremony.

Before

view.viewTreeObserver.addOnPreDrawListener(
    object : ViewTreeObserver.OnPreDrawListener {
        override fun onPreDraw(): Boolean {
            viewTreeObserver.removeOnPreDrawListener(this)
            doSomething()
            return true
        }
    }
)

After

view.doOnPreDraw { doSomething() }

More natural Bundle creation

KTX presents an API similar to using listOf(), which creates a List. Especially useful for fragment creation.

Before

fun newInstance(input: String): CustomFragment {
  val fragment = CustomFragment()
  val bundle = Bundle()
  bundle.putString(ARG_INPUT, input)
  fragment.arguments = bundle
  return fragment
}

After

fun newInstance(input: String): CustomFragment {
  val fragment = CustomFragment()
  fragment.arguments = bundleOf(ARG_INPUT to input)
  return fragment
}

Listening for Animation events

Animation callback handling can be a true callback hell. If you want to be notified about the end of an animation, you needed to use an interface with four methods. Isn't it easier to use just one?

Before

animator.addListener(object: Animator.AnimatorListener{
    override fun onAnimationRepeat(animator: Animator?) {}

    override fun onAnimationEnd(animator: Animator?) {}

    override fun onAnimationCancel(animator: Animator?) {}

    override fun onAnimationStart(animator: Animator?) {}
})

After

animator.doOnEnd { 
    // onAnimationEnd event
}

Bitmap creation

To create a bitmap, you have to configure it deeply, even if you don't have any special needs. Fortunately, this is the past: if you only require a regular setup, all you need to do is to call the drawToBitmap() function and tada, your bitmap is created.

Before

private fun viewToBitmap(view: View): Bitmap {
  val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
  val canvas = Canvas(bitmap)
  view.draw(canvas)
  return bitmap
}

After

private fun viewToBitmap(view: View): Bitmap {
  return view.drawToBitmap()
}

I believe that after reading these examples, you already have a feeling how convenient it is to use these features.

Kotlin already helped us a lot to focus only on what really matters, but that's not how the story ends. Newer and newer extensions are appearing to push the boundaries further. The best thing is that they can coexist. It is easy to mix Android KTX with for example Anko, another great library published by JetBrains. But don't forget that you can go back to the good old ways of doing things. Nothing is enforced, everything is possible.

Gabor Bakos

Co-founder of the startup called BitRaptors with six years of experience in mobile engineering and product development. Likes to pet dogs, playing video games, and bring ideas to life.