This is the third and last part of Android ktx tutorial. In this part, we’re gonna explore the core ktx library extension functions. The core ktx have so much cool extension function which makes the Android coding much concise, simple and idiomatic. The core ktx currently cover some of the most used classes in Android like Animation, Content, Graphics, Drawables, Net, OS, Preference, Text, Transition, Util, View, Widget and many more to come.

Note:  I hope you guy’s have seen my previous blogs. In previous blogs of Android ktx, I briefly explain with examples how we can use the fragment ktx, SQLite ktx, palette ktx, collection ktx, lifecycle view-model ktx and work-runtime ktx.

To get started using the core ktx dependency add in your app level build.gradle.

implementation 'androidx.core:core-ktx:1.0.0-alpha3'

7. Core-ktx:

The core ktx include many packages.  Let’s see them one by one.

Animation Package:

There are so many extension functions that have added to the Animation package. Let’s take a look at what we have available in this release of core ktx. For the animation, there are a bunch of listeners added to the Animator class.

Now you can add the animation listener with extension function and don’t need to override all of the abstract methods.

animator.addListener(onStart = {

}, onEnd = {

}, onRepeat = {

}, onCancel = {
    
})

The onStart, onEnd, onRepeat, and onCancel listener are the default values. If you do not want, you can just use the addListener method like below.

animator.addListener {
    val myAnimator: Animator = it
    handleAnimationResult(myAnimator)
}

We can also listen to the animation pause and animation resume events. In animation ktx, we have an addPauseListener extension function.

animator.addPauseListener(onResume = {
    
},onPause = {
    
})

Again the only requiring callback we need to pass in the function. The onResume and onPause are the default callbacks.

animator.addPauseListener {
    val myAnimator: Animator = it
    handleAnimationResult(animator)
}

We can also listen to the individual animation events.

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

You see how much less code we require to listen to the animation events compares to java. Most of all it is easier to read.

Core Graphics Package:

Most of the application we work in are going to use graphics package somewhere throughout the project. So, the good news is ktx added so many extension functions in the graphics package and it also has the top-level function for creating Bitmap and Matrix.

Graphics Package Top-level Functions:

To begin with, we have a handy top-level function to create the bitmap.

val bitmap = createBitmap(50, 50)

It returns a mutable bitmap with specified width and height. You can also set bitmap default values config, alpha, and colorSpace when creating a bitmap.

val bitmap = createBitmap(50, 50,config = Bitmap.Config.ARGB_8888,hasAlpha = false,colorSpace = ColorSpace.get(ColorSpace.Named.SRGB))

Note: The above top-level function is only used when min SDK is 26 or higher.

The Matrix class also give some top-level function to create a matrix. Let’s see how we can create a matrix with some rotation.

val matrix = rotationMatrix(degrees = 50.0f, px = 0.1f, py = 0.5f)

Creates a rotation matrix defined by rotation angle in degrees. The px and py have the default values 0.0f and 0.0f respectively. The same goes for scaleMatrix top-level function.

val matrix = scaleMatrix(sx = 1.0f,sy = 1.0f)

Creates a scale matrix with the scale factor of sx and sy on the x-axis and y-axis. The sx and sy also have the default values of 0.1f and 0.1f respectively.

You can also set translation when creating Matrix. Let’s see an example.

val matrix = translationMatrix(tx = 0.1f,ty = 0.1f)

Creates a translation matrix with the translation amounts tx and ty on the x-axis and y-axis. The tx and ty have the default values of 0.0f and 0.0f respectively.

Graphics Package Extension Functions:
graphics.Bitmap

When it comes to Bitmap we have some key operations using extension functions.

bitmap.applyCanvas {
            
        }

It creates a new Canvas on this bitmap and executes the specified block. If you want the value of a pixel at the specified location then you have the bitmap extension function for this.

val pixelLocation : Int = bitmap.get(x = 1, y = 2)

Returns the value of the pixel at the specified location. You can also set the pixel color with x and y coordinate into the bitmap.

bitmap[50, 50] = Color.MAGENTA

Writes the specified color int into the bitmap at the specified x and y coordinates. Scaling a bitmap with extension function is look more concise.

val newBitmap : Bitmap = bitmap.scale(50, 50, filter = true)

Returns a new bitmap, scaled from this bitmap. If the specified width and height are the same as the current width and height of this bitmap, this bitmap is returned and no new bitmap is created.

graphics.Canvas

Working with Canvas has been made simpler too.

// wrap the specified block in calls to canvas.save and canvas.restoreToCount.
canvas.withSave {            
        }

// Wrap the specified block in calls to Canvas.save, Canvas.translate and Canvas.restoreToCount. The x and y have default 0.0f and 0.0f values respectively. 
canvas.withTranslation(x = 0.0f, y = 0.0f) {            
        }

// Wrap the specified blocks in call to Canvas.save, Canvas.rotate and Canvas.restoreToCount. The degrees, pivotX and pivotY have default values 0.0f, 0.0f and 0.0f respectively. 
canvas.withRotation(degrees = 0.0f, pivotX = 0.0f, pivotY = 0.0f) {            
        }

// Wrap the specified block in calls to Canvas.save, Canvas.scale and Canvas.restoreToCount. The x, y, pivotY and pivotX have default values 1.0f, 1.0f, 0.0f and 0.0f respectively.
canvas.withScale(x = 1.0f, y = 1.0f, pivotY = 0.0f, pivotX = 0.0f) {            
        }

// Wrap the specified block in calls to Canvas.save, Canvas.skew and Canvas.restoreToCount. The x and y have default values 0.0f and 0.0f respectively.
canvas.withSkew(x = 0.0f, y = 0.0f) {             
        }

// Wrap the specified block in calls to Canvas.save, Canvas.concat and Canvas.restoreToCount. The matrix is default parameters with default Constructor[Matrix()].
canvas.withMatrix(matrix = Matrix()) {
            
        }
graphics.Color

The Color class also have some additional extension functions. Getting the RGB value of color with the extension function.

// Returns the first component of color. When the color model of color is RGB, then the first component is red.
val redColor = color.component1()
// Return the second component which is green.
val greenColor = color.component2() 
// Return the third component which is blue.
val blueColor = color.component3() 
// Return the alpha of color.
val alpha = color.component4()

You can also get RGB value from the destructing method.

val (red ,green ,blue,alpha) = color

The plus extension function is really cool and allows us to add two colors and return the result of both colors.

val resultColor = color.plus(otherColor)
               OR
val resultColor = color + otherColor
Kotlin.int

The Int type in Kotlin offers us a collection of extension functions when working with the color class.

// Return the alpha component of a color int. This is equivalent to calling Color.alpha(someInt).
val alpha: Int = int.alpha 
// Return the blue component of a color int. This is equivalent to calling Color.blue(someInt).
val blue: Int = int.blue 
// Return the green componenr of a color int. This is equivalent to calling Color.green(someInt).
val green: Int = int.green 
// Return the red component of a color int. This is equivalent to calling Color.red(someInt).
val red: Int = int.red 
// Return the realitive luminance of a color int, assuming sRGB encoding. This equivalent to calling Color.luminance(someInt).
val luminance: Float = int.luminance 
// Destructing the values from int.
val (alpha, red, green, blue): Int = intValue 
// Creates a new instance from a color int. The resulting color is in the sRGB color space.
val color: Color = someInt.toColor()
// Converts the specified ARGB int color RGBA long color.
val longColor: Long = someInt.toColorLong()
Kotlin.Long

The Long type in kotlin also offers us the same extension functions as Kotlin.Int.

// Return the alpha component of a color long. This is equivalent to calling Color.alpha(someInt).
val alpha: Float = long.alpha 

// Return the blue component of a color long. This is equivalent to calling Color.blue(someInt).
val blue: Float = long.blue 

// Return the green component of a color long. This is equivalent to calling Color.green(someInt).
val green: Float = long.green 

// Return the red component of a color long. This is equivalent to calling Color.red(someInt).
val red: Float = long.red 

// Return the relative luminance of a color long. This equivalent to calling Color.luminance(someInt).
val luminance: Float = int.luminance 

// Destructing the values from int.
val (alpha, red, green, blue): Int = intValue 

// Creates a new instance from a color int. This is equivalent to calling the Color.value(longValue).
val color: Color = songLong.toColor()

// Converts the specified ARGB int color ARGB long color. This is equivalent to Colot.toArgb()
val intColor: Long = someInt.toColorInt()

// Indicates whether the color is sRGb. This is equivalent to Color.isSrgb(someLong)
val isSrgbResult: Boolean = longValue.isSrgb

// Returns the color space encoded in the specified color long. This is equivalent to Color.colorSpace(someLong)
val isSrgbResult: Boolean = longValue.isSrgb
graphics.Point

The Point class has been also given some extension functions to ease the process when working with them.

// Returns the x coordinates of point class.
val x = point.component1()

// Returns the y component of point class.
val y = point.component2()

// Getting x and y component using destructing declarations when working with the points.
val (x , y) = point

// Add the offsets this point by the specified point and returns the result as a new point.
val newPoint = point.plus(otherPoint)

// Add the offsets this point by the specified amount on both X and Y axis and returns the result as a new point.
val newPoint = point.plus(xy : someInt)

// Subtract the offsets this point by the negation of the specified point and returns the result as a new point.
val newPoint = point.minus(otherPoint)

// Subtract the offsets this point by the negation of the specified amount on both X and Y axis and returns the result as a new point.
val newPoint= point.minus(xy : someInt)

// Returns a PointF representation of this point.
val pointF : PointF = point.toPointF()
graphics.PointF

The PointF class has been offering the same extension function as Point.

// Returns the x coordinates of pointF class.
val x = pointF.component1()

// Returns the y component of pointF class.
val y = pointF.component2()

// Getting x and y component using destructing declarations when working with the points.
val (x , y) = pointF

// Add the offsets this pointF by the specified pointF and returns the result as a new pointF.
val newPointF = pointF.plus(otherPointF)

// Add the offsets this pointF by the specified amount on both X and Y axis and returns the result as a new pointF.
val newPointF = pointF.plus(xy : someFloat)

// Subtract the offsets this pointF by the negation of the specified pointF and returns the result as a new pointF.
val newPointF = pointF.minus(otherPointF)

// Subtract the offsets this pointF by the negation of the specified amount on both X and Y axis and returns the result as a new pointF.
val newPointF = pointF.minus(xy : someFloat)

// Returns a Point representation of this pointF.
val point : Point = pointF.toPoint()
graphics.Rect

When working with the Rect class, we can make use of the below extension functions.

// Returns 'left' the first component of the rectangle.
val left : Int = rect.component1()

// Returns 'top' the second component of the rectangle.
val top : Int = rect.component2()

// Returns 'right' the third component of the rectangle.
val right : Int = rect.component3()

// Returns 'bottom' the fourth component of the rectangle.
val bottom : Int = rect.component4()

// Rect class allows using destructuring declarations when working with rectangles.
val (left, top, right, bottom) = rect

// Performs the union of this rectangle and the specified rectangle and returns the result of the new rectangle.
val newRect : Rect = rect.plus(otherRect)

// Returns a new rectangle representing this rectangle offset by the specified amount on both X and Y axis.
val newRect : Rect = rect.plus(xy : someInt)

// Returns a new rectangle representing this rectangle offset by the specified point.
val newRect : Rect = rect.plus(xy : point)

// Returns the difference of this rectangle and the specified rectangle as a new region.
val region : Region = rect.minus(otherRect)

// Returns a new rectangle representing this rectangle offset by the negation of the specified amount on both X and Y axis.
val newRect : Rect = rect.minus(xy : someInt)

// Returns a new rectangle representing this rectangle offset by the negation of the specified point.
val newRect : Rect = rect.minus(xy : point)

// Returns the union of two rectangles as a new rectangle.
val newRect : Rect = rect.and(otherRect)

// Returns the intersection of two rectangles as a new rectangle. If the rectangles do not intersect, returns a copy of the left-hand side rectangle. By left-hand means the calling rectangle.
val newRect : Rect = rect.or(otherRect)

// Returns the union minus the intersection of two rectangles as a new region.
val region : Region = rect.xor(otherRect)

// Returns true if the specified point is inside the rectangle. The left and top are considered to be inside, while the right and bottom are not. This means that for a point to be contained: left <= x < right and top <= y < bottom. An empty rectangle never contains any point.
val boolValue : Boolean = rect.contains(point)

// Returns a RectF representation of this rectangle.
val rectF : RectF = rect.toRectF()

// Returns a Region representation of this rectangle.
val region : Region = rect.toRegion()
graphics.RectF

The same goes for RectF class with a similar function available.

// Returns 'left' the first component of the rectangle.
val left : Float = rectF.component1()

// Returns 'top' the second component of the rectangle.
val top : Float = rectF.component2()

// Returns 'right' the third component of the rectangle.
val right : Float = rectF.component3()

// Returns 'bottom' the fourth component of the rectangle.
val bottom : Float = rectF.component4()

// RectF class allows using destructuring declarations when working with rectangles.
val (left, top, right, bottom) = rectF

// Performs the union of this rectangle and the specified rectangle and returns the result of the new rectangle.
val newRectF : RectF = rectF.plus(otherRectF)

// Returns a new rectangle representing this rectangle offset by the specified amount on both X and Y axis.
val newRectF : RectF = rectF.plus(xy : someFloat)

// Returns a new rectangle representing this rectangle offset by the specified point.
val newRectF : RectF = rectF.plus(xy : pointF)

// Returns the difference of this rectangle and the specified rectangle as a new region. This rectangle is first converted to a Rect using RectF.toRect extension function.
val region : Region = rectF.minus(otherRectF)

// Returns a new rectangle representing this rectangle offset by the negation of the specified amount on both X and Y axis.
val newRectF : RectF = rectF.minus(xy : someFloat)

// Returns a new rectangle representing this rectangle offset by the negation of the specified point.
val newRectF : RectF = rectF.minus(xy : pointF)

// Returns the union of two rectangles as a new rectangle.
val newRectF : RectF = rectF.and(otherRectF)

// Returns the intersection of two rectangles as a new rectangle. If the rectangles do not intersect, returns a copy of the left-hand side rectangle. By left-hand means the calling rectangle.
val newRectF : RectF = rectF.or(otherRectF)

// Returns the union minus the intersection of two rectangles as a new region. The two rectangles are first converted to Rect using RectF.toRect extension function.
val region : Region = rectF.xor(otherRectF)

// Returns true if the specified point is inside the rectangle. The left and top are considered to be inside, while the right and bottom are not. This means that for a point to be contained: left <= x < right and top <= y < bottom. An empty rectangle never contains any point.
val boolValue : Boolean = rectF.contains(pointF)

// Returns a Rect representation of this rectangle. The resulting rect will be sized such that this rect can fit within it.
val rect : Rect = rectF.toRect()

// Returns a Region representation of this rectangle. The resulting rect will be sized such that this rect can fit within it.
val region : Region = rectF.toRegion()

// Transform this rectangle in place using the supplied Matrix and returns this rectangle.
val rectF : RectF = rectF.transform(m : matrix)
graphics.Path

There is also some operation available for working with Path class.

// Returns the union of two paths as a new Path.
val newPath : Path = path.plus(otherPath)

// Flattens (or approximate) the Path with a series of line segments. The acceptable error for a line must be positive and is set to 0.5f by default.
val newPath: Iterable<PathSegment> = path.flatten(error = 0.5f)

// Returns the difference of two paths as a new Path.
val newPath : Path = path.minus(otherPath)

// Returns the intersection of two paths as a new Path. If the paths do not intersect, returns an empty path.
val newPath : Path = path.or(otherPath)

// Returns the union minus the intersection of two paths as a new Path.
val newPath : Path = path.xor(otherPath)
graphics.Region

When it comes to Region class we have a bunch of available extension functions available to use. Just like Rect and RectF class, we can now easily perform plus, minus, or, and, and xor function to Region class.

// Return the union of this region and the specified region as a new region.
val newRegion : Region = region.plus(otherRegion)

// Return the union of this region and the specified Rect as a new region.
val newRegion : Region = region.plus(rect)

// Return the difference of this region and the specified region as a new region.
val newRegion : Region = region.minus(otherRegion)

// Return the difference of this region and the specified Rect as a new region.
val newRegion : Region = region.minus(rect)

// Return the intersection of this region and the specified region as a new region.
val newRegion : Region = region.or(otherRegion)

// Return the intersection of this region and the specified Rect as a new region.
val newRegion : Region = region.or(rect)

// Return the union minus the intersection of this region and the specified region as a new region.
val newRegion : Region = region.xor(otherRegion)

// Return the union minus the intersection of this region and the specified Rect as a new region.
val newRegion : Region = region.xor(rect)

// Returns the negation of this region as a new region.
val newRegion : Region = region.unaryMinus()

// Return true if the region contains the specified Point.
val boolValue : Boolean = region.contains(point)

// Returns the negation of this region as a new region.
val newRegion : Region = region.not()

// Performs the given action on each rect in this region.
region.forEach{ 
    // handleEachRect(it)
}

// Returns an Iterator over the Rects in this region.
val iterator : Iterator<Rect> = region.iterator()
Kotlin.String

For String, there is a single extension available at this time. With string, you can easily convert it to Color.

// Return a corrresponding int color of this String. This extenion function supported formats are RRGGBB, AARRGGBB. The following names can accepted : "red", "blue", "green", "black", "white","gray", "cyan", "magenta", "yellow", "lightgray", "darkgray","grey", "lightgrey", "darkgrey", "aqua", "fuchsia", "lime","maroon", "navy", "olive", "purple", "silver", "teal".
val blackColor : Int = "black".toColorInt()
graphics.Matrix

Previously above the post we the Matrix top-level function. Now it’s time to explore Matrix extension function. There is two extension function added to matrix class. Now you can multiply the matrix with another matrix and get the values of a matrix with extension functions.

// Multiplies this [Matrix] by another matrix and returns the result as a new matrix.
val newMatrix : Matrix = matrix.times(otherMatrix)

// Returns the 9 values of this Matrix as a new array of floats.
val floatArray : FloatArray = matrix.values()
graphics.PorterDuff.Mode

There is also some operation available when working with the PorterDuff.Mode class.

// Creates a new PorterDuffXfermode that uses this PorterDuff.Mode as the alpha compositing or blending mode.
val porterDuffXMode: PorterDuffXfermode = porterDuffMode.toXfermode()

// Creates a new PorterDuffColorFilter that uses this PorterDuff.Mode as the alpha compositing or blending mode, and the specified color.
val porterDuffColorFilter: PorterDuffColorFilter = porterDuffMode.toColorFilter(someIntColor)
graphics.Shader

You can also perform a transform operation on a Shader with the help extension function.

// Wrap the specified block, under the hood in calls Shader.getLocalMatrix and Shader.setLocalMatrix.
shader.transform{
    // blockExecuted()
}
graphics.Drawable

There are some handy extension functions available to convert the Bitmap into Drawable and Icon.

// Create a BitmapDrawable from this Bitmap.
val bitmapDrawable: BitmapDrawable = bitmap.toDrawable(resource: resources)

// Create an Icon from this Bitmap. This function internally called Icon.createWithBitmap static method. This function requires API level 26 or higher.
val icon : Icon = bitmap.toIcon()

And in relation to that, when we need to convert Int to drawable or color to drawable has been made simpler too.

// Create a ColorDrawable from this color value. 
val colorDrawable : ColorDrawable = someInt.toDrawable()

// Create a ColorDrawable from this Color via Color.toArgb. This function requires API level 26 or higher.
val colorDrawable : ColorDrawable = color.toDrawable()

The Drawable class also have given some extension function to convert a drawable into the bitmap and a function to update the drawable bounds.

// Return a Bitmap representation of this Drawable. If this instance is a BitmapDrawable and the width, height, and config match, the underlying Bitmap instance will be returned directly. If any of those three properties differ then a new Bitmap is created. For all other Drawable types, a new Bitmap is created. The width, height, and config are the default parameters, intrinsicWidth of drawable, intrinsicHeight of drawable and Config.ARGB_8888 are the default values respectively.
val bitmap: Bitmap =  drawable.toBitmap(width: someInt, height: someInt, config: bitmapConfig)

// Updates this drawable's bounds. This version of the method allows using named parameters to just set one or more axes. The left, top, right and bottom are the default values if not given then default bounds.left, bounds.top, bounds.right and bounds.bottom values respectively.
drawable.updateBounds(left : someInt , top : someInt, right : someInt, bottom: someInt)

When it comes to Uri to convert into Bitmap then the ktx have an extension function for this.

// Create an Icon from this Uri. This function internally called Icon.createWithContentUri static method. The method requires API level 26 or higher.
val icon : Icon = uri.toIcon()

The ByteArray class also given an extension to convert a byte array into Icon.

// Create an Icon from this ByteArray. The function internally called Icon.createWithData static method. This method required API level 26 or higher.
Core Content Package:

There is a bunch of extension function added to the content package. The content package also offers a top-level function. Let’s take a look at what is there currently offer.

Content Package Top-level Functions:

Now it is easy to create ContentValues instance using a contentValuesOf top-level function.

val contentValues : ContentValues = contentValuesOf(pairs : Pair<String,Any?>)  // Returns a new ContentValues with the given key and value as a elements.
Content Package Extension Functions:

Performing write operation to SharedPreference with extension function looks more concise.  Now we do not call apply or commit method to persist the changes.

sharedPreference.edit{
    putInt(key,value)
}

Styled attributes can also be worked with below extensions functions.

// Executes block on a receiver. The block holds the attribute values in set that are listed in attrs. In addition, if the given AttributeSet specifies a style class (through the `style` attribute), that style will be applied on top of the base attributes it defines.
context.withStyledAttributes(set : attributeSet,attrs : intArray, @AttrRes defStyleAttr : Int = 0,@StyleRes defStyleRes: Int = 0){
   // execute block
}

// Executes block on a receiver. The block holds the values defined by the style resource [resourceId] which are listed in attrs.
context.withStyledAttributes(set : attributeSet,attrs : intArray){
    // execute block
}

In every application somehow you need to retrieve the system service like NotificationManager, AlarmManager, etc. Getting the system service with the extension function.

// Return the handle to a system-level service by class.
val notificationManager : NotificationManager = getSystemService<NotificationManager>()
Core View Package:

There is some handy extension function added to core.view package. The ktx added extension function in View, Menu, ViewGroup and MarginLayoutParams class. Let’s see them one by one.

view.View

Updating the padding for a View is now easier. The ktx added bunch of function for updating the padding of a view.

// Updates this view's relative padding. This method using named parameters to just set one or more axes. All of the padding parameters are the default if you do not set the default parameters the calling view parameter will be used.
view.updatePaddingRelative(start : someInt, top : someInt, end : someInt, bottom : someInt)

// Updates the view padding. This method using named parameters to just set one or more axes. All of the padding parameters are the default if you do not set the default parameters the calling view parameter will be used.
view.updatePadding(left : someInt, top : someInt, right : someInt, bottom : someInt)

// Sets the view's padding. This method sets all axes to the provided size.
view.setPadding(size : someInt)

Now we have some extension functions for post animation with delayed in the view class.

// Re-orders the view parameters, allowing the action to be placed outside the parentheses.
view.postOnAnimationDelayed(delayInMillis : someLong){
   // handleCallback
}

// Re-orders the view parameters, allowing the action to be placed outside the parentheses.
view.postDelayed(delayInMillis : someLong){
   // handleCallback
}

Similar extension functions available for setting the view callbacks.

// Performs the given action when this view is next laid out. The function itself called addOnLayoutChangeListener to the view and remove the listener when calling the callback.
view.doOnNextLayout{
    val view = it
    // handleCallback
}

// Performs the given action when this view is laid out. If the view has been laid out and it has not requested a layout, the action will be performed straight away otherwise, the action will be performed after the view is next laid out.
view.doOnLayout{
   val view = it
   // handleCallback
}

// Performs the given action when the view tree is about to be drawn.
view.doOnPreDraw{
    val view = it
    // handleCallback
}

Converting view into the bitmap with a single line of code.

// Return a Bitmap representation of this View. The resulting bitmap will be the same width and height as this view's current layout dimensions. This does not take into account any transformations such as scale or translation. The default value for config is Bitmap.Config.ARGB_8888.
val bitmap = view.toBitmap(config : Bitmap.Config.ARGB_8888)

Updating the LayoutParams for a view is now a lot cleaner and easier to do.

// Executes block with the View's layoutParams and reassigns the layoutParams with the updated version.
view.updateLayoutParams{
   // executeBlock
}
view.ViewGroup.MarginLayoutParams

We can now update the margins for the layout params like we update the padding of the view previously.

// Sets the margins in the ViewGroup's MarginLayoutParams. This method sets all axes to the provided size.
marginLayoutParams.setMargins(size = 50)

// Updates the margins in the ViewGroup.MarginLayoutParams. This version of the method allows using named parameters to just set one or more axes. The left, top, right, bottom are the default params if you did not set params then the calling view params will be used.
view.updateMargins(left = someInt, top = someInt, right = someInt, bottom = someInt)

// Updates the relative margins in the ViewGroup's MarginLayoutParams. This version of the method allows using named parameters to just set one or more axes. The start, top, end, bottom are the default params if you did not set params then the calling view params will be used. The function requires API level 17.
view.updateMarginsRelative(start = someInt, top = someInt, end = someInt, bottom = someInt)


view.ViewGroup

Working with ViewGroup become much cleaner with extension functions. You can now loop through to children of a view group with forEach.

// Performs the given action on each view in this view group.
viewGroup.forEach{
    val view = it
    // performActionOnEachView(view)
}

// Performs the given action on each view in this view group, providing with sequential index.
viewGroup.forEachIndexed{ view, index -> 
    // performActionOnEachViewWithIndex(view,index)
}

You can also get Iterator from the ViewGroup with the extension function.

// Returns a MutableIterator over the views in this view group.
val viewGroupIterator : MutableIterator<View> = view.iterator()

There is also some utility extension function added to the view group class. You can now add, remove, get the view and check if the empty or not.

// Returns the view at index. IndexOutOfBoundsException throw, if the index is less than 0 or greater than or equal to the count
val view = viewGroup[0]

// Returns true if view is found in this view group.
val booleanValue : Boolean = viewGroup.contains(view)

// Removes view from this view group.
viewGroup.minusAssign(view)

// Returns true if this view group contains no views.
val booleanValue : Boolean = viewGroup.isEmpty()

// Returns true if this view group contains one or more views.
val booleanValue : Boolean = viewGroup.isNotEmpty()

// Adds a view to this view group.
viewGroup.plusAssign(view)
view.Menu

Similar to extension function for ViewGroup, we can now loop through to children of a Menu, in a similar manner with following functions.

// Performs the given action on each item in this menu, providing its sequential index.
menu.forEachIndexed{ index,menuItem -> 
    // performActionOnEachMenuItemWithIndex(index, menuItem)            
}

// 
menu.forEach{
    val menuItem = it
    // performActionOnEachMenuItem(menuItem)
}

Get Iterator from the Menu with the extension function.

// Returns a MutableIterator over the items in this menu.
val menuItemIterator : MutableIterator<MenuItem> = menu.iterator()

You can also get the Sequence of menu items from the menu extension. It is a final variable in menu class gives you a sequence over the menu items.

// Returns a Sequence over the items in this menu.
val menuItemsSequence : Sequence<MenuItem> = menu.children

Also, some utility extension function added to the menu class. You can now add, remove, get the menu item and check if has a menu item or not.

// Returns the menu item at index. The IndexOutOfBoundsException is thrown, if an index is less than 0 or greater than or equal to the count
val menuItem : MenuItem = menu[index]

// Returns true if the menu item is found in this menu.
val booleanValue : Boolean = menu.contains(menuItem)

// Removes menu item from this menu.
menu.minusAssign(menuItem)

// Returns true if this menu contains no menu items.
val booleanValue : Boolean = menu.isEmpty()

// Returns true if this menu contains one or more menu items.
val booleanValue : Boolean = menu.isNotEmpty()
Core Util Package:

The core ktx added a util package, there is a collection of an extension function related to AtomicFiles, Int, Float, Array, and SparseArray and many more classes.

Convert data into Half classes with other data types is now become more simple.

// Returns a half instance with the given float.
val half = someFloat.toHalf()

// Returns a half instance with the given double.
val half = someDouble.toHalf()

// Returns a half instance with the given short.
val half = someShort.toHalf() 

// Returns a half instance with the given string.
val half = someString.toHalf()
util.AtomicFiles

For atomic files, there is some function added for reading and writing to files. Let’s take a look at what are the extension’s functions available for writing to the file.

// Perform the write operations inside the block on this file. If the block throws an exception the write will be failed. Otherwise, the write will be applied atomically to the file. The function requires API level 17.
atomicFile.tryWrite {
   val outputStream: FileOutputStream = it
   // startWritingData(outputStream)
}

// Sets the content of this file as an array of bytes. The function requires the API level 17. This function internally called the tryWrite function.
atomicFile.writeBytes(array = bytesArray)

// Sets the content of this file as text encoded using UTF-8 or specified charset. If this file exists, it becomes overwritten. This function internally called the writeBytes function. The charset is default parameter, if not set it used Charsets.UTF_8. Requires the API level 17.
atomicFile.writeText(text = someText, charset = Charsets.UTF_8)


Similar to writing functions we have some functions for reading also.

// Gets the entire content of this file as a byte array. This method is not recommended on huge files. It has an internal limitation of 2 GB file size. Required API level 17.
val fileBytes : ByteArray = atomicFile.readBytes()

// Gets the entire content of this file as a String using UTF-8 or specified [charset]. This method is not recommended on huge files. It has an internal limitation of 2 GB file size. This function also requires API level 17.
val fileText : String = atomicFile.readText(charset = Charsets.UTF_8)
util.SparseArray

For the LongSparseArray, SparseArray, SparseBooleanArray, SparseIntArray and SparseLongArray have collection of extension functions available.

// Returns the number of key/value pairs in the collection.
val arraySize : Int = sparseArray.size

// Returns true if the collection contains the key.
val booleanValue : Boolean = sparseArray.contains(key : key)

// Allows the use of the index operator for storing values in the collection.
sparseArray.set(key : key value : someValue)

// Creates a new collection by adding or replacing entries from other.
val newSparseArray : SparseArray<T> = sparseArray.plus(other : otherSparseArray)

// Returns true if the collection contains value.
val booleanValue : Boolean = sparseArray.containsValue(value : someValue)

// Return the value corresponding to a key, or defaultValue when not present. 
val someValue : T = sparseArray.getOrDefault(key : key, defaultValue : someDefaultValue)

// Return the value corresponding to a key, or from defaultValue when not present. In this function for defaultValue, we need to pass a function with a return type of T.
val someValue : T = sparseArray.getOrElse(key : key) {
    [email protected] t
}

// Return true when the collection contains no elements.
val booleanValue : Boolean = sparseArray.isEmpty()

// Return true when the collection contains elements.
val booleanValue : Boolean = sparseArray.isNotEmpty()

// Removes the entry for key only if it is mapped to value.
val booleanValue : Boolean = sparseArray.remove(key : key , value : someValue)

// Update this collection by adding or replacing entries from other.
sparseArray.putAll(other : otherSparseArray)

// Performs the given action for each key/value entry.
sparseArray.forEach { key, value -> 
     // performOperationOnEachKeyAndValue(key,value)            
}

// Return an iterator over the collection's keys.
val keyIterator = sparseArray.keyIterator()

// Return an iterator over the collection's values.
val valueIterator : Iterator<T> = sparseArray.valueIterator()
util.Range

Working with Range class have become little easier. The Range class requires API level 21 or higher.

// Return the smallest range that includes this and value.
val newRange : Range<T> = range.plus(value : someValue)

// Return the smallest range that includes this and other.
val newRange : Range<T> = range.plus(other : otherRange)

// Return the intersection of this range and other. Throw IllegalArgumentException if this is disjoint from other.
val newRange : Range<T> = range.and(other : otherRange)

// Returns this Range as a ClosedRange. 
val closedRange : ClosedRange<T> = range.toClosedRange()

// Return Range from ClosedRange.
val newRange : Range<T> = closedRange.toRange()

util.Pair

We can also perform a bunch of operation on Pair class instance with provided extension functions.

// Returns the first component of the pair. 
val first = pair.component1()

// Returns the second component of the pair.
val second = pair.component2()

// Destructing declarations when working with Pair.
val (first, second) = pair

// Return the pair as Kotlin.Pair class.
val kotlinPair = pair.toKotlinPair()
util.Size & util.SizeF

The Size and SizeF class also given a bunch of extension function to work with.

// Returns width, the first component of this size.
val width = size.component1()

// Returns height, the first component of this size.
val height = size.component2()

// Allows to use destructuring declarations when working with Size classes, for example:
val (width , height) = size
Core Widget Package:

In every application somewhere down the road, you need to show the Toast.  The core ktx add an extension function to show a toast.

// Creates and shows a Toast with the given text. The duration parameter is the default, if not set then Toast.LENGHT_SHORT will be used.
context.toast("Show some toast",length = Toast.LENGTH_SHORT)

// Creates and shows a Toast with text from a resource. The duration parameter is the default, if not set then Toast.LENGHT_SHORT will be used.
context.toast(R.string.someResource,length = Toast.LENGTH_SHORT)
Core Text Package:

In most of the Android app, we’re going use Text somewhere in the app. The core ktx added some extension functions and one top-level function. Within the text package, there is a collection of extension function let’s see them one by one.

Core Text Top-level Functions:

Working with the spans API. The core ktx added a top-level extension to the main class. Let’s say, you want some text to be bold, italic and underline at the same time programmatically.

val string: SpannedString = buildSpannedString {
    append("Hello There")
    bold {
        append("bold")
        italic {
            append("bold and italic")
            underline {
                append("then some text with underline")
            }
        }
    }
}
textView.text = string

You see using directly top-level function converting the text into bold, italic and underline is a massive reduction of code.

Core Text Extension Functions:
Kotlin.CharSequence

In the CharSequence class ktx added some cool extension functions to work with. Say if you want to check if the char sequence contains the only digit.

// Returns whether the given CharSequence contains only digits.
val booleanValue : Boolean = charSequence.isDigitOnly()

There is also an extension function for getting the trimmed length of a char sequence.

// Returns the length that the specified CharSequence would have if spaces and ASCII control characters were trimmed from the start and end, as by String.trim.
val length : Int = charSequence.trimmedLength()

If you need to convert char sequence to Spanned or Spannable instance, then the core ktx have extension function for this.

// Returns a Spannable from a CharSequence.
val spannable : Spannable = charSequence.toSpannable()

// Return a Spanned from CharSequence.
val spanned : Spanned = charSequence.toSpanned()
Kotlin.String

With String, we have the ability to convert a string into HTML encoding with the extension function.

// Html encode the String
val htmlEncodeString : String = someString.htmlEncode()

There is also an extension function for parsing the string as Html.

// Returns a Spanned from parsing this string as HTML. 
val spanned : Spanned = someString.parseAsHtml()
text.Spanned

If we have a Spanned instance which we wish to convert into HTML string for then we can do so with a toHtml extension function.

// Returns a string of HTML from the spans in this Spanned. The options is default parameter.
val htmlString : String = spanned.toHtml(option : Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)

If you need to get all the spans inside the Spanned instance then we have a getSpans extension function for this.

// Get all the spans inside the spanned. The start and end are the default parameters, if you did not set then 0 value will be used for start and the end value will be spanned length.
val spanArray : Arra<T> = spanned.getSpans(start = 0, end = spanned.length)
text.Spannable

The core ktx have some utility function when working with Spannable. Like the previous classes, the core ktx added some basic functions for Spannable adding the new spans, remove spans, setting spans at the specific position and clearing the spans.

// Add span to the entire text. The span is the type of 'Any' in Kotlin and 'Object' in Java.
spannable.plusAssign(span : someSpan)

// Remove span from the entire text. The span is the type of 'Any' in Kotlin and 'Object' in Java.
spannable.minusAssign(span : someSpan)

// Clear all spans from the entire text.
spannable.clearSpans()

// Add span to the range of the text. The span is the type of 'Any' in Kotlin and 'Object' in Java. The range is the type of 'IntRange' in kotlin. The range end value is exclusive.
spannable.set(range : intRange , span : someSpan)

// Add span to the range start and end of the text. 
spannable.set(start : intValue, end : intValue, span : someSpan)

// Example of setting Span
val spannable = "Hello, World".toSpannable()
spannable.set(0 , 5 , Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
text.SpannableStringBuilder

At the beginning of core text package, we see one top-level function of creating spannable builder. Now there are a collection of extension functions available for SpannableStringBuilder class. To begin with, if you want to add some bold text to builder then we have an extension function for this.

// Wrap appended text in block in a bold StyleSpan.
builderSpan.bold { 
   append("Make this text bold")
}

Like the bold extension function, we have some other cool function for making the text to underline and italic.

// Wrap appended text in block in an italic StyleSpan. 
builderSpan.italic {
    append("Make this text italic")
}

// Wrap appended text in block in an UnderlineSpan.
builderSpan.underline {
    append("Make this text underline")
}

There are also build functions for setting the background color or wrap the text that you’re appending in spans.

// Wrap appended text in a block in spans. The spans will only have the correct position if the block only appends or replaces text. Inserting, deleting, or clearing the text will cause the span to be placed at an incorrect position. The span parameter is the type of 'Any' in Kotlin and 'Object' in Java.
builderSpan.inSpan(span : spanItem) {
    append("Add this text to span builder")
}

// Wrap appended text in a block in the ForegroundColorSpan.
builderSpan.color(color : R.color.someColor) {
    append("Change the foreground color of this text only")
}

// Wrap appended text in a block in the BackgroundColorSpan.
builderSpan.backgroundColor(color : R.color.someColor) {
   append("Change the background color of this text only")
}

Core Net Package:

Within the core.net package of ktx we a bunch of extension function to convert String into Uri, File into Uri and Uri to File.

// Creates a Uri from the given encoded URI string. Internally this function called the Uri.parse static method.
val uri : Uri = someString.toUri()

// Creates a Uri from the given file. Internally this function called the Uri.fromFile static method.
val uri : Uri = someFile.toUri()

// Creates a File from the given Uri. It internally creates a new File with Uri path.
val file : File = uri.toFile()
Core OS Package:

There is a bunch of extension and top-level functions provided for the OS package.

Core OS Top-Level Functions:

Creating a new instance of Bundle with top-level function looks nicer.

// Returns a new Bundle with the given key/value pairs as elements. The method will throw an IllegalArgumentException When a value is not a supported type of Bundle.
val bundle : Bundle = bundleOf(pairs : Pair(firstValue,secondValue))

// Returns a new PersistableBundle with the given key/value pairs as elements. This method will throw an IllegalArgumentException When a value is not a supported type of PersistableBundle. This function requires API level 21 or higher.
val persistentBundle : PersistentBundle = persistentBundleOf(pairs : Pair(firstValue,secondValue))

Writing trace messages can also be done with the top-level function.

// Wrap the specified block in calls to Trace.beginSection with the supplied sectionName and Trace.endSection. 
tracr(string : someSectionName) {
   // handleTraceStart
}
Core OS Extension Functions:

The core ktx also include some extension functions when working with Handler class.

val runnable : Runnable = handler.postDelayed(delayInMills: someLongValue) {
    // doSomething 
}

val runnable : Runnable = handler.postAtTime(uptimeMillis : someLongValue) {
    // doSomething
}

Wrap

That’s it guys here we’re going to complete our Android ktx tutorial series. You guys have seen ktx offer us awesome extension function and also some top-level functions to use in our Android Applications. Like I said the library is in preview mode,  and I’m excited to see what other extension functions will be added in the library.

I hope you guys have learned from this post. If you any queries please do comment below.

Thank for being here and keep reading.

Previous Part

Author

I’m a mobile product devsigner (i.e. I consider myself as both a developer and a designer) and user experience/interface engineer. I’m an expert on the Android platform and have been recognized as it by the community.

Write A Comment