Yesterday Google announced android-ktx, which is a set of Kotlin extensions for Android app development. It looks like the aim of the library is to continue with the advantages that kotlin brings to our project — less code, more fun and a simpler to understand project.

Now, the library is only in preview so it is likely to change — it will probably up offering more capabilities when it is released. I’ve written this article whilst looking through the documentation for KTX and thought I’d write some notes on it as an overview. It’s likely you will just use the documentation when it comes to it, but hopefully this post will give you a look at what it currently has to offer. And if you’re not already using kotlin, you’ll see what you’re missing out on 🙂

The documentation for KTX can be found here:

and the library itself, here:

There’s a collection of extensions related to animations within the library, let’s take a quick look at what we have available in the current release!

Animation listener

To begin with, in KTX we can set an animation listener on an animator instance like so:

This allows us to receive the callbacks for an animation event. We can even add function for specific callbacks of the listener, you’ll only need to pass functions for the callbacks that you want to receive data for:



onEnd = {},

onStart = {},

onCancel = {},

onRepeat = {}

) animator. addListener onEnd =onStart =onCancel =onRepeat =

This itself is a massive reduction in the code for listener callbacks that we may not need and use.

Individual animation event listeners

We have the ability to listen for individual events in android, with KTX adding a pause listener can be done in the same way that the addListener() function:

{ handleAnimation(it) } animator. addPauseListener handleAnimation( // or

onPause = {},

onResume = {}

) animator. addPauseListener onPause =onResume =

Again, only requiring to pass in the functions that we require to be used for the callbacks.

We can also listen for individual animation events in a single line of code:

If you’re currently using java code for animations in your Android projects, you’ll notice how much less code (and how much easier to read) this is. In each of these calls above, it represents the Animator instance that is in use.

Content

There are a bunch of extension functions that have been added within the Content package. If we need to retrieve system service then there is an extension available for us to do so:

val alarmManager = systemService ()

Styled attributes can also be worked with using the provided extensions:



// block to execute

} context. withStyledAttributes (set = someAttributeSet, attrs = attributes, defStyleAttr = ..., defStyleRes = ...) {// block to execute

attributes) {

// block to execute

} context. withStyledAttributes (set = someAttributeSet, attrs =attributes) {// block to execute

Performing write operations to shared preferences is now super nice with the use the edit function:

We can also create a new ContentValues instance using the contentValuesOf function, passing in varargs of Pair instances:

val contentValues = contentValuesOf (somePairs...)

KTX also offers a collection of operations related to Time. Let’s take a look at what there is currently on offer.

We can now access the DayOfWeek, Month and Year instances as an Int value with a simple call:

The Duration class also has a bunch of functions available:

// Retrieve values from destructuring

val (seconds, nanoseconds) = Duration.ofSeconds(1) // Perform multiplication

val result = Duration.ofSeconds(1) * 2 // Perform division

val result = Duration.ofSeconds(2) / 2 // Perform negation

val result = -Duration.ofSeconds(2)

The Instant, LocalData, LocalDateTime, LocalTime properties can also be accessed by these functions:

// Retrieve values from destructuring

val (seconds, nanoseconds) = Instant.now() // Retrieve values from destructuring

val (year, month, day) = LocalDate.now() // Retrieve values from destructuring

val (localDate, localTime) = LocalDateTime.now() // Retrieve values from destructuring

val (hour, minute, second, nanosecond) = LocalTime.now()

As well as these, accessing properties of the MonthDay, OffsetDateTime and OffsetTime classes can easily be done via these component calls:

// Retrieve values from destructuring

val (month, day) = MonthDay.now() // Retrieve values from destructuring

val (localDataTime, ZoneOffset) = OffsetDateTime.now() // Retrieve values from destructuring

val (localTime, ZoneOffset) = OffsetTime.now()

If you’re using the Period class, the KTX library contains some functions to access properties as well as perform some operations:

// Retrieve values from destructuring

val (years, month, days) = Period.ofDays(2) // Perform multiplication

val result = Period.ofDays(2) * 2 // Perform negation

val result = -Period.ofDays(2)

There are also some other component functions which can be used to retrieve desired values:

// Retrieve values from destructuring

val (year, month) = YearMonth.now() // Retrieve values from destructuring

val (localDateTime, ZoneId) = ZonedDateTime.now()

These following functions are really cool additions, these allow us to easily take an Int value and retrieve the corresponding representation for the given function call:

The same goes for a Long instance also, using these functions the following representations can be retrieved:

OS

There are a collection of functions provided for the android OS package. This includes some extensions to working with the handler class:



// some action

} handler. postAtTime (uptimeMillis = 200L) {// some action

// some action

} handler. postDelayed (delayInMillis = 200L) {// some action

Creating new instance of the Bundle class is now also a lot nicer:

val bundle = bundleOf("some_key" to 12, "another_key" to 15) val bundle = persistableBundleOf("some_key" to 12, "another_key" to 15)

And if you’re writing trace events for the Systrace tool, writing trace messages can be done nice and easily:

Utils

Within the Util package there are a collection of functions related to files, arrays and other general data types.

To begin with, if you’re working with AtomicFiles you’ll be able to make use of the following functions:



val text = atomicFile.

atomicFile.{

// some write operations

}

atomicFile.

atomicFile. val fileBytes = atomicFile. readBytes ()val text = atomicFile. readText (charset = Charset.defaultCharset())atomicFile. tryWrite // some write operationsatomicFile. writeBytes (byteArrayOf())atomicFile. writeText ("some string", charset = Charset.defaultCharset())

For the LongSparseArray, SparseArray, SparseBooleanArray, SparseIntArray, SparseLongArray types we have all of the following functions available:

array.contains(someKey) array.containsKey(someKey) array.containsValue(someValue) array.forEach { key, value -> doSomething(key, value) } array.getOrDefault(key = keyValue, defaultValue = defaultValue) array.getOrElse(key = keyValue, defaultValue = defaultValue) array.isEmpty() array.isNotEmpty() val keyIterator = array.keyIterator() val valueIterator = array.valueIterator() array.plus(anotherArray) array.putAll(anotherArray) array.remove(key = keyValue, value = value) array.set(key = keyValue, value = value) array.size

Working with the Pair class now becomes a little easier:

val pair = android.util.Pair("dsfn", "sdihfg") // Retrieve values from destructuring

val (key, value) = pair

val kotlinPair = pair. // Convert an Android framework pair to the kotlin Pairval kotlinPair = pair. toKotlinPair ()

We can also convert a Kotlin Pair directly to an Android Pair:



val androidPair = pair. val pair = Pair("dsfn", "sdihfg")val androidPair = pair. toAndroidPair ()

If you’re working with the Half class, it’s now simpler to convert other data types to this:

An instance of the ClosedRange class can now be converted to a Range using a provided function:

val range = closedRange. toRange ()

We can also perform a bunch of operations on a Range instance with provided extension functions:

val range = closedRange. toClosedRange () // returns the intersection of the two ranges

val result = closedRange and someOtherRange // returns the smallest range that includes the two ranges

val result = closedRange += someOtherCloseRange // returns the intersection of the range and the given value

val result = closedRange += someValue

Both the Size and SizeF classes can be worked with using the provided functions:

val size = Size(5, 5) // Retrieve values from destructuring

val (width, height) = size

There are a collection of functions available for the Cursor class. For each of the function groupsstate below:

The first get function returns a non-null type

The second function returns a data type (or null) using the given column name

The third function returns a data type (or null) using the given index

For SQLite there is a single extension function available at this time. Even so, it’s a pretty handy addition that allows us to perform a transaction using the given SQL statement.

{ "some SQL statement" } sqLiteDatabase. transaction "some SQL statement"

When it comes to resources in our android applications, there have been a collection of functions added for easing the process of working with the TypedArray class. Here is what is currently on offer to us:

val boolean = typedArray. getBooleanOrThrow (0) val int = typedArray. getColorOrThrow (0) val colorStateList = typedArray. getColorStateListOrThrow (0) val float = typedArray. getDimensionOrThrow (0) val int = typedArray. getDimensionPixelOffsetOrThrow (0) val int = typedArray. getDimensionPixelSizeOrThrow (0) val drawable = typedArray. getDrawableOrThrow (0) val float = typedArray. getFloatOrThrow (0) val typeface = typedArray. getFontOrThrow (0) val int = typedArray. getIntOrThrow (0) val int = typedArray. getIntegerOrThrow (0) val string = typedArray. getStringOrThrow (0) val charSequenceArray = typedArray. getTextArrayOrThrow (0) val charSequence = typedArray. getTextOrThrow (0)

Note: All of these throw a IllegalArgumentException if a value at the given index doesn’t exist

Most of the applications we work in are going to use text somewhere throughout the project and thankfully, KTX provides some extension functions when it comes to these parts. For text we essentially have some functions available for the SpannableStringBuilder class.

For example, after instantiating a Builder instance we can use the build methods to append some bold text:



.bold { append("hi there") } val builder = SpannableStringBuilder (urlString)append("hi there") // or even some bold / italic / underlined text if you want!

.bold { italic { underline { append("hi there") } } }

val builder = SpannableStringBuilder(urlString)append("hi there")

There are also build functions to set the background color or wrap the text that you’re appending in spans:



// builder action

} backgroundColor (color = R.color.black) {// builder action

// builder action

} inSpans (spans = someSpans) {// builder action

There is also a buildSpannedString extension function that allows us to build a string and use the provided builder actions to provide our styling:

Net

Within the .net package of KTX we have a single extension function that allows us to easily convert a string into a URI representation, nice!

val uri = urlString. toUri ()

Graphics

The Graphics related packages within KTX are pretty beefy, which is a great move to make all of the visual niceties in our apps more enjoyable to code.

To begin with we now have some handy functions to easily perform operations that convert bitmaps to the following types:



val drawable = bitmap.

val icon = bitmap.

val drawable = someInt.

val icon = someByteArray.

val icon = someUri.toIcon()

val colorDrawable = someColor. val adaptiveIcon = bitmap. toAdaptiveIcon ()val drawable = bitmap. toDrawable (resources)val icon = bitmap. toIcon ()val drawable = someInt. toDrawable ()val icon = someByteArray. toIcon ()val icon = someUri.toIcon()val colorDrawable = someColor. toDrawable () val bitmap = drawable. toBitmap (width = someWidth, height = someHeight, config = bitMapConfig)

When it comes to Bitmaps we can now easily perform some key operations using extension functions:

val bitmap = someBitmap. applyCanvas (block = { }) val colorInt = someBitmap. get (x, y) val bitmap = someBitmap. scale (width, height, filter = true) someBitmap. set (x, y, color)

And in relation to that, working with the Canvas class has been made simpler too:

We also have some additions for when it comes to operations performed on Color instances:

// retrieve values from destructuring

val (r, g, b, a) = color // Compose two color instances togehter

val color += someColor

The plus() function is really cool and allows us to add two colors together, resulting in a Color instance where the two instances have been blended!

Some functions on Matrices now make it easier for us to perform multiplication operations and also retrieve the values contained within the Matrix:

// perform multiplication

val result = matrix * someOtherMatrix val values = matrix. values ()

We can now perform operations on a Picture instance via the use of the record extension function, using the block parameter to execute commands for the instance:

{

// do stuff with canvas

} val result = picture. record (width = someWidth, height = someHeight)// do stuff with canvas

If we have a drawable instance which we wish to update the bounds for then we can do so with the updateBounds extension function — passing in the desired bounds we wish to be updated:

drawable. updateBounds (left = 16, top = 16, right = 16, bottom = 16)

Need to perform a transform operation on a Shader?

There are also some operations available for working with the PorterDuff class:

val porterDuffColorFilter = mode. toColorFilter (someColor) val porterDuffXfermode = mode. toXfermode ()

When working with the Region class we can now make use of these functions:

// Return union of the someRegion with a Rect

val region = someRegion and someRect // Return union of the someRegion with a Region

val region = someRegion and someRegion // Return difference of the someRegion and a Rect

val region = someRegion - someRect // Return difference of the someRegion and another Region

val region = someRegion - someRegion // Return intersection of the someRegion and a Rect

val region = someRegion or someRect // Return intersection of the someRegion and another Region

val region = someRegion or someRegion // Return union of the someRegion with a Rect

val region = someRegion + someRect // Return union of the someRegion with a Region

val region = someRegion + someRegion

val region = someRegion // Return union minus intersection of someRegion and a Rectval region = someRegion xor someRect

val region = someRegion // Return union minus intersection of someRegion and another Regionval region = someRegion xor someRegion val boolean = someRegion. contains (somePoint) someRegion. forEach { doSomethingWithEachRect(it) } val iterator = someRegion. iterator () // Return negation of someRegion as a new Region

val region = -someRegion

The PointF class has also been given some functions to ease the process when working with them:

val (x, y) = s omePointF someOtherPointF

val pointF = someFloat val pointF = somePointF val pointF = somePointF somePointF

val pointF = someFloat val pointF = somePointF val pointF = somePointF val point = somePointF.toPoint() val pointF = - somePointF

Similar functions are also available for the Point class:

val (x, y) = s omePoint val point = somePoint - somePoint

val point = somePoint - someFloat val point = somePoint +somePoint

val point = somePoint + someFloat val point = somePoint. toPointF () val point = -somePoint

The same goes for the Rect class with some similar functions available:

val rect = someRect and anotherRect val (left, top, right, bottom) = someRect someRect. contains (somePoint) val region = someRect - anotherRect val rect = someRect - someInt val rect = someRect - somePoint val rect = someRect or someRect val rect = someRect + someRect val rect = someRect + someInt val rect = someRect + somePoint val rectF = someRect. toRectF () val region = someRect. toRegion () val region = someRect xor someRect

The RectF class offers pretty much the same capabilities:

val rectF = someRectF and anotherRectF val (left, top, right, bottom) = somerectF someRectF. contains (somePoint) val region = someRectF - anotherRectF val rectF = someRectF - someInt val rectF = someRectF - somePoint val rectF = someRectF or someRect val rectF = someRectF + someRect val rectF = someRectF + someInt val rectF = someRectF + somePoint val rect = someRectF. toRect () val region = someRectF. toRegion () val reactF = someRectF. transform (someMatrix) val region = someRectF xor someRect

When working with the Path class, we can make use of the below functions:

val path = somePath and anotherPath val path = somePath. flatten (error = 0.5f) val path = somePath - anotherPath val path = somePath or anotherPath val path = somePath + anotherPath val path = somePath xor anotherPath

When working with graphics, chances are we will be working with the Int and Long data types. The Int type offers us a collection of functions in KTX:

val alpha = int. alpha val blue = int. blue val green = int. green val red = int. red val luminance = int. luminance val (alphaComp, redComp, greenComp, blueComp) = someInt val color = someInt. toColor () val color = someInt. toColorLong ()

The Long type on the other hand offers us a few differences:

val alpha = long. alpha val blue = long. blue val green = long. green val red = long. red val luminance = long. luminance val (alphaComp, redComp, greenComp, blueComp) = someLong val color = someLong. toColor () val color = someLong. toColorInt () long. isSrgb long. isWideGamut long. colorSpace

when it comes to the Transition class we have a bunch of extension functions available for use. Just like the animation listeners, we can now easily listen for changes in our Transition by using the addListener() function call.

{ doSomethingWithTransition(it) }



transition.{}, onStart = {}, onCancel = {},

onResume = {}, onPause = {}) transition. addListener doSomethingWithTransition(transition. addListener (onEnd =, onStart =, onCancel =onResume =, onPause =

Or on the other hand, we can use the individual functions for each of the transition callbacks:

We have some similar functions available for the View class also. Setting callbacks for layout events is super clean:

The postDelayed method is now available as a function:

view. postDelayed (delayInMillis = 200) { // some action }

The same goes for the postOnAnimationDelayed method:

view. postOnAnimationDelayed (delayInMillis = 200) { // some action }

Updating the padding for a view is now a lot cleaner and easier to do so, there are several functions available for when it comes to this topic:



view.

view.

start = 16, end = 16, top = 16, bottom = 16) view. setPadding (16)view. updatePadding (left = 16, right = 16, top = 16, bottom = 16)view. updatePaddingRelative start = 16, end = 16, top = 16, bottom = 16)

And if you need to convert a View instance to a Bitmap, you can do so with this single line of code!

val bitmap = view. toBitmap (config = bitmapConfig)

ViewGroup

There are some handy ViewGroup related functions that you’ll likely be using in your projects! For example, checking a if a viewgroup contains a view:

val doesContain = viewGroup. contains (view)

Looping through the children of a viewgroup (where it represents the child):

Accessing the child at a desired position in kotlin style:

Retrieving a MutableIterator instance for the desired viewgroup:

val viewGroupIterator = viewGroup. iterator ()

And performing other various viewgroup related operations:



viewGroup.

viewGroup. viewGroup. isEmpty ()viewGroup. isNotEmpty ()viewGroup. size // Remove a view from the given viewgroup

viewGroup -= view // Add a view to the given viewgroup

viewGroup += view

Margins

Similar to setting the padding for View instances, we can now alter the margins for our layout param instances in a similar manner with the following functions:



params.

params.

start = 16, end = 16, top = 16, bottom = 16) params. setMargins (16)params. updateMargins (left = 16, right = 16, top = 16, bottom = 16)params. updateMarginsRelative start = 16, end = 16, top = 16, bottom = 16)

Conclusion

As we can see, KTX offers us some awesome extensions for when it comes to using kotlin in our Android applications. I’m excited to use these in my projects from now and am looking forward to seeing what else gets added soon 🙂