📜 ⬆️ ⬇️

Learning KTX for Android

Hi, "Habr"! It has been almost 9 months since Google announced Google Kotlin on Google I / O 2017 as the official development language for Android. Someone uses it as the main tool for much longer, considering that it was possible to write on it since the middle of 2014. In the Google documentation, examples of implementation on Kotlin began to appear. During this time, developers were able to "touch" and appreciate all the advantages of this language. And many, including me, thought: what is the next step? Support Library on Kotlin? Or something new? And now, meet: Android KTX! And we present to your attention the translation of an article devoted to his analysis.



On February 5, Google announced the release of the Android KTX library, which is a set of Kotlin extensions for developing Android applications. This step looks like a logical continuation of integrating Kotlin into Android development and adding all its benefits: less code, more fun and easier understanding of the project code.

Now the library is only available in preview and is open to new ideas. Most likely, it will have much more features and capabilities when it is brought into a stable version. I wrote this article, while looking through the documentation, and thought it would be good to take a closer look at some points. Most likely, you yourself will begin to study the documentation only when you need it. But I hope that this article will give you an idea of ​​what can be used now. And if you are still not using Kotlin, you will understand what you are missing.
')
KTX documentation can be found here: core-ktx and the library itself here: android / android-ktx .

Animator functions


Expansion functions related to animation are collected here. Let's quickly go over what is available in the current release.

Animation listener


First, install the animation listener on animator:

animator.addListener { handleAnimation(it) } 

This design allows us to receive callbacks for animation events. We can also use extension functions for certain callbacks from the listener, we only need to implement the functions you want to receive:

 animator.addListener( onEnd = {}, onStart = {}, onCancel = {}, onRepeat = {} ) 

This is a significant decrease in the code due to the lack of implementation of those callbacks that we do not need and which we do not use.

Separate listeners for animation events.


We have the opportunity to set a listener on individual events: for example, you can add a listener to the pause event in the same way as the addListener () function:

 animator.addPauseListener { handleAnimation(it) } //  animator.addPauseListener( onPause = {}, onResume = {} ) 

We can also “hang” the listener on an animation event using a single-line syntax:

 animator.doOnPause { handleAnimation(it) } animator.doOnCancel { handleAnimation(it) } animator.doOnEnd { handleAnimation(it) } animator.doOnRepeat { handleAnimation(it) } animator.doOnStart { handleAnimation(it) } animator.doOnResume { handleAnimation(it) } 

If you are currently using Java, you will notice how much less code is required for implementation and how much easier it all is read.

Content


Here we look at the extension functions added to the Content package. If we need to get a system service, the extension allows us to do this:

 val alarmManager = systemService<AlarmManager>() 

Styled attributes will also work when using extension functions:

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

Writing to SharedPreferences is now as simple as possible using the edit function:

 sharedPreferences.edit { putBoolean(key, value) } 

We can also create an instance of ContentValues using the contentValuesOf function, passing in Pair instances as arguments:
 val contentValues = contentValuesOf(somePairs...) 

Time operations


KTX also proposes using time-related methods. Let's see what we have here.
Now you can get DayOfWeek, Month and Year as an Int-value by a simple call:

 DayOfWeek.FRIDAY.asInt() Month.APRIL.asInt() Year.now().asInt() 

The Duration class also has several extension functions available:

 //   val (seconds, nanoseconds) = Duration.ofSeconds(1) //  val resultValue = Duration.ofSeconds(1) * 2 //  val resultValue = Duration.ofSeconds(2) / 2 //  val resultValue = -Duration.ofSeconds(2) 

The properties Instant, LocalData, LocalDateTime, LocalTime can be obtained by the following extension functions:

 //   val (seconds, nanoseconds) = Instant.now() //   val (year, month, day) = LocalDate.now() //   val (localDate, localTime) = LocalDateTime.now() //   val (hour, minute, second, nanosecond) = LocalTime.now() 

As in the methods listed above, access to the properties of the classes MonthDay, OffsetDateTime and OffsetTime can be obtained through calls to the following methods:

 //   val (month, day) = MonthDay.now() //   val (localDataTime, ZoneOffset) = OffsetDateTime.now() //   val (localTime, ZoneOffset) = OffsetTime.now() 

If you use the Period class, the KTX library contains several extension functions for accessing the properties and operations of this class:

 //   val (years, month, days) = Period.ofDays(2) //  val resultValue = Period.ofDays(2) * 2 //  val resultValue = -Period.ofDays(2) 

There are several other extension functions that can be used to obtain the necessary values:

 //   val (year, month) = YearMonth.now() //   val (localDateTime, ZoneId) = ZonedDateTime.now() 

The following expansion functions are a really good addition and allow us to easily translate an Int-value into the required class provided by a function call:

 someInt.asDayOfWeek() //   DayOfWeek someInt.asMonth() //   Month someInt.asYear() //   Year someInt.days() //   Period someInt.hours() //   Duration someInt.millis() //   Duration someInt.minutes() //   Duration someInt.months() //   Period someInt.nanos() //   Duration someInt.seconds() //   Duration someInt.years() //   Period 

This also works for Long values:

 someLong.asEpochMillis() //   Instant someLong.asEpochSeconds() //   Instant someLong.hours() //   Duration someLong.millis() //   Duration someLong.minutes() //   Duration someLong.nanos() //   Duration someLong.seconds() //   Duration 

OS


Here are assembled extension functions aimed at interacting with the Android OS package.
They include several extension functions for working with the Handler class:

 handler.postAtTime(uptimeMillis = 200L) { // -  } handler.postDelayed(delayInMillis = 200L) { // -  } 

Creating an instance of the Bundle class now looks much 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 record trace events for the Systrace tool, recording trace messages will be simpler and more beautiful:

 trace("section_name") { } 

Utils


The Util package contains extension functions for working with files, arrays, and other basic data types.
If you work with AtomicFiles , then you can use the following functions:

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

For LongSparseArray , SparseArray , SparseBooleanArray , SparseIntArray , SparseLongArray types became available to us:

 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 has become a bit easier:

 val pair = android.util.Pair("dsfn", "sdihfg") //   val (key, value) = pair //  Android Pair  Kotlin Pair val kotlinPair = pair.toKotlinPair() 

We can also convert Kotlin Pair to Android Pair:

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

If you work with the Half class, thanks to KTX, it is easier to convert other types of data into it:

 short.toHalf() string.toHalf() float.toHalf() double.toHalf() 

Using extension functions, you can now convert an instance of the ClosedRange class to Range :

 val range = closedRange.toRange() 

Above the instance of the Range class, you can perform the following actions:

 val range = closedRange.toClosedRange() //     val resultValue = closedRange and someOtherRange //   ,    val resultValue = closedRange += someOtherCloseRange //       val resultValue = closedRange += someValue 

Both Size and SizeF can use extension functions:

 val size = Size(5, 5) //   val (width, height) = size 

Database cursor


This section contains the extension functions available for the Cursor class. Each group of functions is arranged in the following order:

 cursor.getBlob(columnName = "some_column") cursor.getBlobOrNull(columnName = "some_column") cursor.getBlobOrNull(index = 0) cursor.getDouble(columnName = "some_column") cursor.getDoubleOrNull(columnName = "some_column") cursor.getDoubleOrNull(index = 0) cursor.getFloat(columnName = "some_column") cursor.getFloatOrNull(columnName = "some_column") cursor.getFloatOrNull(index = 0) cursor.getInt(columnName = "some_column") cursor.getIntOrNull(columnName = "some_column") cursor.getIntOrNull(index = 0) cursor.getLong(columnName = "some_column") cursor.getLongOrNull(columnName = "some_column") cursor.getLongOrNull(index = 0) cursor.getShort(columnName = "some_column") cursor.getShortOrNull(columnName = "some_column") cursor.getShortOrNull(index = 0) cursor.getString(columnName = "some_column") cursor.getStringOrNull(columnName = "some_column") cursor.getStringOrNull(index = 0) 

Sqlite


Currently there is only one function for SQLite, but it is very useful. It allows us to perform transactions using specified SQL statements.

 sqLiteDatabase.transaction { "some SQL statement" } 

Resources


As for resources, only those extension functions have been added that simplify working with the TypedArray class.

 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 throws throw an IllegalArgumentException if the specified index does not exist.

Text


Most of the applications that we (the developers) are working on use text in different places of these same applications. Fortunately, KTX has several functions for working with it, in particular for the SpannableStringBuilder class.
For example, right after the builder initialization, we can use these functions to add some bold text to the end of the original line:

 val builder = SpannableStringBuilder(urlString) .bold { append("hi there") } //  bold / italic / underlined ,  . val builder = SpannableStringBuilder(urlString) .bold { italic { underline { append("hi there") } } } 

There are also build functions that can set the background color or add padding to the text:

 .backgroundColor(color = R.color.black) { //   builder } .inSpans(spans = someSpans) { //   builder } 

And the last function is buildSpannedString, which allows you to create a string using the expansion functions listed above:

 textView.text = buildSpannedString { bold { append("hitherejoe") } } 

Net


In the .net package, we have one function that makes it easy to convert a string to a URI. Exactly what is needed!

 val uri = urlString.toUri() 

Graphics


The Graphics package in KTX came out pretty massive, but it gives us the ability to easily implement all the visual subtleties of the application.
First of all, I would like to mention the functions that convert Bitmap (and not only) into the following types:

 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) 

Next, we consider the key operations for working with Bitmap:

 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 working with Canvas has become much easier:

 canvas.withRotation(degrees, pivotX, pivotY) { //  } canvas.withSave { //  } canvas.withScale(x, y, pivotX, pivotY) { //  } canvas.withSkew(x, y) { //  } canvas.withTranslation(x, y) { //  } 

There are also several innovations for Color :

 //   val (r, g, b, a) = color //   val color += someColor 

The plus () function is really cool and allows us to mix two colors and get mixed Color as a result!
In addition, it has become easier to work with matrices. Now you can multiply two matrices and as a result get one Matrix object:

 //  val resultMatrix = matrix * someOtherMatrix val values = matrix.values() 

We can also work with Picture through the record function, using the parameter block to perform the appropriate actions:

 val resultField = picture.record(width = someWidth, height = someHeight) { // -   Canvas } 

If we want to change the drawable bounds, we can simply call the updateBounds function and pass dimensions to it as parameters:

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

Need to transform the Shader ? No problem!

 shader.transform { //  } 

There are several extension functions for working with the PorterDuff class:

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

Working with the Region class, we can now use these functions:

 //   someRegion  Rect val region = someRegion and someRect //   someRegion  Region val region = someRegion and someRegion //   someRegion  Rect val region = someRegion - someRect //   someRegion   Region val region = someRegion - someRegion //   someRegion  Rect val region = someRegion or someRect //   someRegion   Region val region = someRegion or someRegion //   someRegion  Rect val region = someRegion + someRect //   someRegion  Region val region = someRegion + someRegion //     someRegion  Rect val region = someRegion xor someRect //     someRegion   Region val region = someRegion xor someRegion val boolean = someRegion.contains(somePoint) someRegion.forEach { doSomethingWithEachRect(it) } val iterator = someRegion.iterator() //   someRegion   Region val region = -someRegion 

The PointF class has also been added some features to simplify:

 val (x, y) = somePointF val pointF = somePointF - someOtherPointF val pointF = somePointF - someFloat val pointF = somePointF + somePointF val pointF = somePointF + someFloat val point = somePointF.toPoint() val pointF = -somePointF 

The same is available for the Point class:

 val (x, y) = somePoint val point = somePoint - somePoint val point = somePoint - someFloat val point = somePoint +somePoint val point = somePoint + someFloat val point = somePoint.toPointF() val point = -somePoint 

And for the class Rect too:

 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 

You will not be surprised, but for RectF they are also available:

 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 use the following options:

 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 

Chances are good that when working with graphics we will work with data types Int and Long . The Int type offers us the following 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() 

On the other hand, the Long type contains slightly more functions:

 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 

Transitions


So, having reached the Transition class, we see that here you can use extension functions similar to animation listeners:

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

But there is a slight difference in the syntax of the method for individual callbacks:

 transition.doOnCancel { } transition.doOnEnd { } transition.doOnPause { } transition.doOnResume { } transition.doOnStart { } 

Views


Similar functions have also been added for the View class. Installing callbacks is very clear:

 view.doOnLayout { } view.doOnNextLayout { } view.doOnPreDraw { } 

The postDelayed method is now available as a function:

 view.postDelayed(delayInMillis = 200) { // -  } 

The same with the postOnAnimationDelayed method:

 view.postOnAnimationDelayed(delayInMillis = 200) { // -  } 

Updating paddingov for View is now much easier and more understandable, for this we were provided with several functions:

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

If you need to convert View to Bitmap, now it can be done in one line of code!

 val bitmap = view.toBitmap(config = bitmapConfig) 

ViewGroup


Some pretty cool extension features have been added for the ViewGroup. I think you will like it! For example, checking whether the ViewGroup contains a specific View:

 val doesContain = viewGroup.contains(view) 

Cycle for child ViewGroup (where it is a child):

 viewGroup.forEach { doSomethingWithChild(it) } viewGroup.forEachIndexed { index, view -> doSomethingWithChild(index, view) } 

Accessing a child in a particular Kotlin style position:

 val view = viewGroup[0] 

Getting an instance of MutableIterator :

 val viewGroupIterator = viewGroup.iterator() 

And several other operations with ViewGroup:

 viewGroup.isEmpty() viewGroup.isNotEmpty() viewGroup.size //  view   viewgroup viewGroup -= view //  view   viewgroup viewGroup += view 

Margins


Just like the paddings for View, we can add margins for LayoutParams using the following functions:

 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 powerful tools for using Kotlin in the development of Android applications. I am very pleased to be able to use them in my projects and I look forward to what will be added in the near future.

Source: https://habr.com/ru/post/349014/


All Articles