Author

Ahsen Saeed

Browsing

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

This is the second part of android ktx tutorial. I hope you guy has seen my previous blog in which I briefly explain about fragment ktx, palette ktx, SQLite ktx and collection ktx extension functions. So in this blog, I’m gonna explain how we can use the extension function exists in ViewModel ktx and work-runtime ktx.

In this blog, I’m not gonna tell you how we can set up build. gradle file to use the android ktx. To see you have to check out my previous blog. So, let’s see what are the extension function that ViewModel ktx and work-runtime ktx have.

5. Lifecycle ViewModel-ktx:

There’s only two extension function that has been added to lifecycle view-model ktx. If we need to convert LiveData into Publisher then we have an extension function for this.

val myViewModel = MyViewModel()
val liveData : LiveData<T> =myViewModel.observeModel()
liveData.toPublisher(lifecycle : LifecycleOwner).subscribe(object : Subscriber<T>{
      override fun onComplete() {
      }

      override fun onNext(t: T?) {
      }

      override fun onError(t: Throwable?) {
      }

      override fun onSubscribe(s: Subscription?) {
      }
})

The toPublisher is the extension function which adapts the given LiveData streams into a reactive stream Publisher.

Let’s see another example to convert the LiveData into Publisher. Suppose we have a ViewModel which returns an array of String when we start listening to the stream. For this, we have to create a ViewModel which returns an array of String with LiveData.

ViewModel
class MyModel : ViewModel(), StringRepository {

    override fun getStrings(): LiveData<List<String>> {
        val stringsLiveData = MutableLiveData<List<String>>()
        stringsLiveData.postValue(Arrays.asList("Data", "return", "from", "view", "model"))
        return stringsLiveData
    }
}
StringRepository
interface StringRepository {

    fun getStrings(): LiveData<List<String>>
}

Now let’s see how we can listen to the array of String LiveData stream and convert into reactive stream Publisher.

MainActivity
val model = MyModel()
        model.getStrings().toPublisher(lifecycle = this).subscribe(object : Subscriber<List<String>> {
            override fun onComplete() {
            }

            override fun onSubscribe(s: Subscription?) {
            }

            override fun onNext(t: List<String>?) {
                  // Handle the data or show in the UI
            }

            override fun onError(t: Throwable?) {
            }

        })

Note: Only the UI observe the LiveData object. UI means your Activity or Fragment can only start listening to the data.

There’s another cool extension function in lifecycle ktx which converts the reactive stream Publisher to LiveData stream. Let’s say our StringRepository returns a Publisher instead of LiveData stream.

Now the StringRepository will look like this.

StringRepository
interface StringRepository {

    fun getStrings(): Publisher<List<String>>
}

You see our StringRepository class change it means ViewModel also have to update.

ViewModel
class MyModel : ViewModel(), MovieRepository {

    override fun getStrings(): Publisher<List<String>> {
        return Publisher {
            Arrays.asList("Data", "return", "from", "view", "model")
        }
    }
}

Now let’s see how we can create an observable LiveData stream from a reactive stream Publisher. This time MainActivity will look like this.

MainActivity
val model = MyModel()
        model.getStrings().toLiveData().observe(this, object : Observer<List<String>> {
            override fun onChanged(t: List<String>?) {
                // handle the data or show in the UI
            }
        })

The toLiveData is the extension function which gives us the feasibility to create an observable LiveData stream from reactive stream Publisher.

6. Work-Runtime-ktx:

Ktx also offers an extension function and a bunch of top-level function related to WorkManager. By the way, I wrote a pretty good article on how to work with WorkManager efficiently go and check it out. Let’s take a look at what there is currently an offer.

6.1 Work-Runtime-ktx Top-level Functions:

We can now create OneTimeWorkRequest.Builder directly with top-level function. The OneTimeWorkRequestBuilder is the top-level function. Let’s see an example of how we can create work request builder with top-level function.

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
                .build()

The PeriodicWorkRequest.Builder class also have a bunch of top-level to create work request builder. The PeriodicWorkRequestBuilder is the top-level function.

val workRequest = PeriodicWorkRequestBuilder<MyWorker>(10, TimeUnit.SECONDS)
                .build()

val workRequest = PeriodicWorkRequestBuilder<MyWork>(
                    repeatInterval : 10
                   , timeUnit : TimeUnit.SECONDS
                   , flexTimeInterval : 10
                   , timeUnit : TimeUnit.MINUTES)
                   .build()

The first periodic work executes the request every after 10 seconds when we enqueue this work request. The second periodic work request runs periodically once within the flex period of every interval period.

6.2 Work-Runtime-ktx Extension Functions:

The OneTimeWorkRequest.Builder also gives us an extension function to add an InputMerger when creating work request builder. InputMerger takes one or more Data inputs to a Worker and converts them to a single merged Data to be used as input.

Let’s see an example, how we can pass InputMerger when creating work request builder.

val workRequest = OneTimeWorkRequestBuilder<MyWork>()
                .setInputMerger(ArrayCreatingInputMerger::class.java)
                .setInputData(createInputData("Value1"))
                .build()


private fun createInputData(msg: String): Data {
    return Data.Builder().putString("Key", msg).build()
}

ArrayCreatingInputMerger creates an array in which all the values of this key are placed as an array. You can read about ArrayCreatingInputMerger here.

The input data that we passed when creating a work request builder will receive in Worker class. When setting input merger as ArrayCreatingInputMerger the result will always an array, even if there were no key matches.

Another cool extension functions in kotlin.collection.The Map which gives the ability to convert Map to the Data object.  You can create any Map data to work Data object with the help of extension function. Let’s see an example, how we can convert Map to the Data object.

private fun createInputData(msg: String): Data {
    val map = HashMap<String, String>()
    map["Key"] = msg
    return map.toWorkData()
}

You see in this function we’re using the toWorkData extension function to convert a map into the Data object.

That’s it guys, I’m going to end this blog here for more further reading about android ktx see the part 3. In the next part, we’re going to see the main core ktx extension function. Obviously, the core ktx have so many extension functions and top-level functions to explore.

I hope you guy’s have learned something from this post. If you’ve any queries please do comment below.

Thank you for being here and keep reading.

 

If you’re developing an Android application with Kotlin, then I have a good news for you. Recently Google released an Android ktx library, which is a set of Kotlin Extension function. Android ktx is a part of Jetpack family. The purpose of Android ktx is to write less code and more concise. Android ktx brings us lots of extension functions, named parameters, lambdas, and parameter with the default value. It does not add any new features to existing Android Api’s.

I read all the documentation on android-ktx and thought to write some notes on it. It’s not just reading on Android developer documentation. We’re going to see the extension functions with example and explore most of them.

Note: Android ktx is currently in preview mode but it is open for the developer to give it a try and give feedback to contributors. It may be offering more extension functions when it is released.

To get started with Android ktx add the google repository in build,gradle file.

repositories { 
    google() 
}

Android ktx dependency in split into the modular approach. By modular means, you don’t have to add the core dependency, if you do not want to use all of the extension’s functions. Here is the list of dependency of Android ktx.

implementation 'androidx.core:core-ktx:$currentVersion'
implementation 'androidx.fragment:fragment-ktx:$currentVersion'
implementation 'androidx.palette:palette-ktx:$currentVersion'
implementation 'androidx.sqlite:sqlite-ktx:$currentVersion'
implementation 'androidx.collection:collection-ktx:$currentVersion'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:$currentVersion'
implementation 'androidx.lifecycle:lifecycle-reactivestreams-ktx:$currentVersion'
implementation 'android.arch.work:work-runtime-ktx:$currentVersion'

After syncing the project you’re good to go to use Android ktx. Now let’s start with the first modular dependency.

1. Fragment-ktx:

Fragment ktx dependency right now supports only one extension function. The only extension function is about triggering the fragment transaction. Without ktx performing a fragment transaction, the transaction requires starting a transaction triggering an action like replacing or adding a fragment and then the signing on one of the four commit methods. Now let’s see how we can remove the boilerplate code with fragment ktx extension.

supportFragmentManager.transaction(now = false, allowStateLoss = false) {
      replace(R.id.frag_container, myFrament,FRAGMENT_TAG)
}

Fragment ktx as the transaction extension function to the FragmentManager class removing all the begin transaction boilerplate code. As well as providing the four value for sync and stateloss that you can easily override.

nowallowStateLossMethod
falsefalsecommit()
falsetruecommitAllowingStateLoss()
truefalsecommitNow()
truetruecommitNowAllowingStateLoss()

2. Palette-ktx:

Like I said the library is currently in the preview that’s why we only have two extension function for palette ktx. Now let’s take a look at what extension function we have…?

Now you can easily create Palette. Builder with the bitmap.

val paletteBuilder : Palette.Builder = bitmap.buildPalette()

You can easily get Palette. Swatch from Palette class.

val paletteSwatch = pallete[Target.DARK_VIBRANT]

Returns the selected swatch for the given Target from the palette, or if null one could not be found. With palette swatch, you can retrieve RGB color by calling the getRgb method.

3. SQLite-ktx:

SQLite ktx dependency right now supports only one extension function. When implementing database transaction you need a lot of boilerplate code, with the SQLite ktx, you can focus on what matters the operation that you want to make in the database. Let’s see an example of handling transaction with sqlLite ktx.

sqLitedatabase.transaction(exclusive = true){
    // Insert data, delete data, update data
}

If you setting exclusive to true it called beginTransaction and when setting false it called beginTransactionNonExclusive.

4. Collection-ktx:

If you use the Kotlin top-level function of creating collection then you absolutely love this collection ktx dependency. It supports many extension functions and it also supports top-level function of creating the collection. Let’s see them one by one.

4.1 Collection-ktx Top-level Functions:

With the .collection package of ktx, you can easily create an arrayMapOf with top-level function to create ArrayMap.

val arrayMap = arrayMapOf<String,String>()   // returns an empty new ArrayMap

You can also give a list of Pair where the first component is the key and second component is the value.

map = arrayMapOf(pairs = *arrayOf(
          Pair("Jon", "Doe"),
          Pair("Hilary", "Clinton"))
)    // returns a array map with specified content.

The ArraySet also gives some top-level function to ease the process when working with them.

val arraySet = arraySetOf<Int>()   // returns an empty new ArraySet.

You can also give a vararg to create ArraySet.

val arraySet = arraySetOf(1,2,3)  // return an array set with specified content.
4.2 Collection-ktx Extension Functions:

When working with the LongSparseArray and SparseArrayCompat we can make use of the below extension function. Both of the classes pretty much offers the same functionalities.

val longSparseArray = LongSparseArray<String>()   // create a LongSparseArray of String

//   returns true if the collection contain the key
longSparseArray.contains(key: Long)    

// You can use index operator to store value a specified index
longSparseArray.set(key : Long,value : String)   // value is String, because our sparse array is String type

// create new collcetion by adding or replacing entries from other
longSparseArray.plus(other : LongSparseArray<String>)

// returns true if the collection contains the key
longSparseArray.containsKey(key : Long)

// return true if the collection contains the value
longSparseArray.containsValue(value : String)

// return the value corresponding to the key, or default when no present.
longSparseArray.getOrDefault(key : Long, defaultValue : String)  // return defaultValue if no value found according to the key

// return true when the collection contains one or more than element in the array.
longSparseArray.isNotEmpty()

// removes the entry from the array only if it is mapped to value in the specified index.
longSparseArray.remove(key : Long, value: String)

// updates this collection by adding replacing entries from other.
longSparseArray.putAll(other: LongSparseArray<String>)

// performs foreach on every key/value entry.
longSparseArray.forEach(action : (key : Long, value : String) -> Unit)

// Example of foreach function
longSparseArray.forEach{ key,value -> 
     // Use the key and value to perform the operation
}

// return Iterator over the collection key.
val iterator : LongIterator = longSparseArray.keyIterator()

// return Iterator over the collection values.
val iterator : Iterator<String> = longSparseArray.valueIterator()

That’s it guy’s, I’m going to end this blog here for more further reading about Android ktx see the part 2. In the next part, I’m going to explain about lifecycle ktx, reactive streams ktx and runtime ktx. Finally, in the third part, we’re gonna explore the core ktx.

I hope you guy’s have learned something from this post. If you’ve any queries about this post please do comment below.

Thank you for being here and keep reading.

Next Part

As a mobile developer, I started my journey as an Android Developer and I really liked it. After that, I learned  IOS and get frustrated because I need to do the same thing twice to maintain the same features. So, I kind of like hating myself and always find ways to share more code among those apps.

Then I heard news about this new SDK by Google called Flutter. I said to myself, this is awesome Flutter is good, instead of not like Android and IOS. But it is an elegant and efficient framework that will let us a truly single codebase for IOS and Android.

So enough of this intro, let’s make our hand dirty and write some Flutter code.

App Intro

In this blog, we’re gonna make a very simple movie fetching app. We’re gonna hit an URL and download the JSON data and show in a GridView. To fetch movies we’re gonna use TheMovieDb API. 

App Components
  1. FutureBuilder: Widget that builds itself based on the latest AsyncSnapshots. It serves as a bridge between Futures and the widget’s UI.
  2. Stateless Widget: A stateless widget has no internal state to manage. e.g Text, IconButton, Icon are examples of a stateless widget.
  3. Stateful Widget: A stateful widget is dynamic. The user can interact with a stateful widget or it changes over the time.
  4. GridView Builder: To show a list of movies in grid view. Create a scrollable 2D array of widgets.
  5. Futures: Flutter uses future objects to represent Asynchronous operation. If any code block takes a long time and if, we did not run the code block as an asynchronous the app freeze. Asynchronous operations let your program run without getting blocked.
  6. Json Mapping: Json mapping helps you to a parse json response came from web service.
  7. Paging: We’re gonna make a network request every time for new movie pages when a user reaches at the end of GridView
Flutter App Setup

Let’s start with the main function where the Flutter app run. Below is the run function of the Flutter app.

void main() {  
  runApp(new MaterialApp(
      title: "Movie Seacher",
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(),
      home: new MoviePage(),
  ));
}

Now before to show how we create a MoviePage widget, I wanna show you the movie API response. Now let’s make the Movie model so that we can parse the movie’s response.

Movie Model
class Movie {
  Movie(
      {this.title,
      this.posterPath,
      this.id,
      this.overview,
      this.voteAverage,
      this.favored});

  final String title, posterPath, id, overview;
  final String voteAverage;
  bool favored;

  factory Movie.fromJson(Map value) {
    return Movie(
        title: value['title'],
        posterPath: value['poster_path'],
        id: value['id'].toString(),
        overview: value['overview'],
        voteAverage: value['vote_average'].toString(),
        favored: false);
  }
}

In movie API response, we have movie title, posterPath, overview, and vote average and many other fields. All of the remaining fields we can skip if you want to read you can add them as a parameter. The Movie.fromJson is the method which is called every time when we need to parse a single movie object.

So, we parse the single Movie object but in movie API response we have page_results, page, total_results, and results which is a list of movies. Now we need another class which is MovieList. 

MovieList Model
class MovieList {
  MovieList({
    this.page,
    this.totalResults,
    this.totalPages,
    this.movies,
  });

  final int page;
  final int totalResults;
  final int totalPages;
  final List<Movie> movies;

  MovieList.fromMap(Map<String, dynamic> value)
      : page = value['page'],
        totalResults = value['total_results'],
        totalPages = value['total_pages'],
        movies = new List<Movie>.from(
            value['results'].map((movie) => Movie.fromJson(movie)));
}

You see in this class we’re parsing the complete movie API response and converting the results into List of movies.

Now we all love abstractions right so we’re gonna create a MovieRespository abstract class. So, that user not gonna interact with our backend API.

MovieRepository
abstract class MovieRepository {
  Future<ListMovies> fetchMovies(int pageNumber);
}

To fetch movies from API we need to pass page number to read movies. We’re returning Future because of our FutureBuilder needs the future object.

Now it’s time to see how we can make an async network request and convert it into a Future<ListMovie>. Below is the class in which we’re fetching movies and parsing them into a Future<ListMovie>.

MovieProdRepository
import 'dart:async';
import 'package:http/http.dart' as http;
import 'movie_data.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart';

const MOVIE_API_KEY = "e5c7041343c************8b720e80c7";    // Replace with your own API key
const BASE_URL = "https://api.themoviedb.org/3/movie/";

class MovieProdRepository implements MovieRepository {
  @override
  Future<ListMovie> fetchMovies(int pageNumber) async {
    http.Response response = await http.get(BASE_URL +
        "popular?api_key=" +
        MOVIE_API_KEY +
        "&page=" +
        pageNumber.toString());
    return compute(parseMovies, response.body);
  }
}

ListMovie parseMovies(String responseBody) {
  final Map moviesMap = JsonCodec().decode(responseBody);
  print(moviesMap);
  ListMovie movies = ListMovie.fromMap(moviesMap);
  if (movies == null) {
    throw new Exception("An error occurred : [ Status Code = ]");
  }
  return movies;
}

In MovieProdRepository class we’re fetching movies and parsing them into ListMovie class. After parsing the response we’re gonna simply return the ListMovie as a Future. 

Now everything is done from the backend point of view. Let’s see how we can use the MovieRepository class, call the async function and show the movies into a GridView

Below is the MoviePage widget class. The MoviePage widget is a Stateless widget.

MoviePage
class _MoviePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<ListMovie>(
        future: movieRepository.fetchMovies(1),
        builder: (context, snapshots) {
          if (snapshots.hasError)
            return Text("Error Occurred");
          switch (snapshots.connectionState) {
            case ConnectionState.waiting:
              return Center(child: CircularProgressIndicator());
            case ConnectionState.done:
              return MovieTile(movies: snapshots.data);
            default:
          }
        });
  }
}

In this MoviePage widget, we’re executing our network requests to fetch movies. You see in FutureBuilder widget we’re passing the future object to FutureBuilder class. We’re showing CircularProgressIndicator widget unless the movie’s response request completes and then we call our MovieTile widget.

Below is the MovieTile widget class. The MovieTile widget is a stateful widget.

MovieTile
class MovieTile extends StatefulWidget {
  final ListMovie movies;

  MovieTile({Key key, this.movies}) : super(key: key);

  @override
  State<StatefulWidget> createState() => MovieTileState();
}

class MovieTileState extends State<MovieTile> {
  MovieLoadMoreStatus loadMoreStatus = MovieLoadMoreStatus.STABLE;
  final ScrollController scrollController = new ScrollController();
  static const String IMAGE_BASE_URL = "http://image.tmdb.org/t/p/w185";
  List<Movie> movies;
  int currentPageNumber;
  CancelableOperation movieOperation;

  @override
  void initState() {
    movies = widget.movies.movies;
    currentPageNumber = widget.movies.page;
    super.initState();
  }

  @override
  void dispose() {
    scrollController.dispose();
    if(movieOperation != null) movieOperation.cancel();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: onNotification,
      child: new GridView.builder(
        padding: EdgeInsets.only(
          top: 5.0,
        ),   // EdgeInsets.only
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 0.85,
        ),  // SliverGridDelegateWithFixedCrossAxisCount
        controller: scrollController,
        itemCount: movies.length,
        physics: const AlwaysScrollableScrollPhysics(),
        itemBuilder: (_, index) {
          return MovieListTile(movie: movies[index]);
        },
      ),  // GridView.builder
    );  // NotificationListener
  }

This is the main important widget where we are creating GridView, listening to the widget tree with NotificationListenerAdding ScrollController so that we can make a network request every time with new page number when a user reaches at the end of GridView.

You see in NotificationListener class we’re passing the onNotification function. The onNotification is basically accepting a function with bool return type and ScrollNotification as an input type.

One more thing noticeable here, you see I’m creating a CancelableOperation at the top MovieTileState class. This operation helps us to cancel the async request if the user navigates to a different screen. Later, I’m setting the CancelableOperation object to our movie page network request.

Below is the onNotification function.

bool onNotification(ScrollNotification notification) {
  if (notification is ScrollUpdateNotification) {
    if (scrollController.position.maxScrollExtent > scrollController.offset &&
        scrollController.position.maxScrollExtent - scrollController.offset <=
            50) {
      if (loadMoreStatus != null &&
          loadMoreStatus == MovieLoadMoreStatus.STABLE) {
        loadMoreStatus = MovieLoadMoreStatus.LOADING;
        movieOperation = CancelableOperation.fromFuture(injector
            .movieRepository
            .fetchMovies(currentPageNumber + 1)
            .then((moviesObject) {
          currentPageNumber = moviesObject.page;
          loadMoreStatus = MovieLoadMoreStatus.STABLE;
          setState(() => movies.addAll(moviesObject.movies));
        }));
      }
    }
  }
  return true;
}

In here we first check if the notification is ScrollNotification then we check if the user at the end of the GridView and last we make a network request to fetch new movies with a new page number. The LoadMoreStatus is the enum. We using LoadMoreStatus enum because we do not want to execute many network requests same if the one request is already executing.

MovieLoadMoreStatus
enum MovieLoadMoreStatus { LOADING, STABLE }

You guys must have noticed that we’re calling MovieListTile widget from MovieTile widget class. The MovieListTile widget is the single movie GridView tile.

MovieListTile
const String IMAGE_BASE_URL = "http://image.tmdb.org/t/p/w185";

class MovieListTile extends StatelessWidget {
  MovieListTile({this.movie});
  final Movie movie;

  @override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: new BorderRadius.all(
          new Radius.circular(15.0),
        ),  // BorderRadius.all
      ),  // RoundedRectangleBorder
      color: Colors.white,
      elevation: 5.0,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Image(
              image: NetworkImageWithRetry(IMAGE_BASE_URL + movie.posterPath,
                  scale: 0.85),  // NetworkImageWithRetry
              fit: BoxFit.fill),  // Image
          _MovieFavoredImage(movie: movie),
          Align(
            alignment: Alignment.bottomRight,
            child: Padding(
              padding: EdgeInsets.only(bottom: 5.0, right: 5.0),
              child: Text('Rating : ${movie.voteAverage}'),
            ),  // Padding
          )  // Align
        ],  //  <Widget>[]
      ),  // Stack 
    );   // Card
  }
}

This widget class is very simple in here we simply create a CardView with an image inside it. To fetch movies poster we need to make another async. To fetch movies we’re using an image loading library. This library automatically fetches the image and show in the image and if the image failed due to the internet failure it automatically retries again.

MovieFavoredImage
class _MovieFavoredImage extends StatefulWidget {
  final Movie movie;
  _MovieFavoredImage({@required this.movie});

  @override
  State<StatefulWidget> createState() => _MovieFavoredImageState();
}

class _MovieFavoredImageState extends State<_MovieFavoredImage> {
  Movie currentMovie;

  @override
  void initState() {
    currentMovie = widget.movie;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new Align(
        alignment: Alignment.topRight,
        child: new IconButton(
            icon: Icon(
              currentMovie.favored ? Icons.star : Icons.star_border,
            ),  // Icon 
            onPressed: onFavoredImagePressed),  // IconButton
      ),  // Align
    );  // Container
  }

  onFavoredImagePressed() {
    setState(() => currentMovie.favored = !currentMovie.favored);
  }
}

This widget is for when the user presses on the star icon and add the movie into Favorites. We need to make this widget Stateful because the widget changes its state when a user taps on the star icon from outline to filled star.

We can make MovieFavoredImage widget inside MovieListTile widget but when we update the state of the widget it updates the whole widget instead of just star icon state. That’s why we make another widget for just to update the star icon state, not the whole movie widget state.

If you want to see the code of the above example see it on GitHub. I wrote the complete example with Dependency Injection, Abstractions and with the Modular approach.

That’s it guys this is my demonstration about Future Builder with Pagination in Flutter. I hope you guy’s have learned something from this post. If you’ve any queries please do comment below.

Thank you for being here and keep reading…

Finally, Android O support library got this feature covered. Material design recommends using a dynamic type instead of smaller type sizes or truncating larger size text. A TextView that automatically resize the text, expand and fill its layout based on the text view characteristics and boundaries. These characteristics make it easier to optimize the text size with different screen size and dynamic content.

Auto-resizing can also help you avoid empty spaces in your layout or text that gets cut off mid-sentence because you tried to add too many words into a TextView.

Dependency

For to use Autosize TextView you need to add a dependency in your build.gradle file.

implementation 'com.android.support:appcompat-v7:27.1.0'
Types of setting Autosize TextView
  • Default
  • Granularity
  • Preset Sizes
1. Default

To define the default settings of Autosizing TextView with xml set “setautoSizeTextType” to uniform. Let’s see an example how we can use it in the xml file.

<TextView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    app:autoSizeTextType="uniform" />

Now this setting of TextView scale uniformly on horizontally and vertically ignoring the text size attributes. If you’re not settings the minTextSize, maxTextSize, and granularity properties then the default values will be used 12sp, 112sp, and 1px respectively.

Note: If you set Autosizing in an XML file, it is not recommended to use the value “wrap_content” for the layout_width or layout_height attributes of a TextView. It may produce unexpected results.

You can also set Autosizing of a TextView programmatically.

emojiCompatTextView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM)
2. Granularity

With Granularity, the TextView will scale uniformly in the range between minimum and maximum size in increments of step granularity. If you don’t set these properties the default values will be used.

To implement Autosizing using granularity you’ll need to add the following attributes in the xml file. Below is the example how to use granularity in xml.

<TextView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    app:autoSizeTextType="uniform"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeMaxTextSize="100sp"
    app:autoSizeStepGranularity="2sp" />

You can also set Autosizing using granularity programmatically.

emojiCompatTextView.setAutoSizeTextTypeUniformWithConfiguration(12, 100, 2, TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM)
3. Preset Sizes

To have more control over the final size. e.g your app needs to compile with specific text size design guidelines. You can provide an array of supported text sizes and it will use the largest one that fits. All you need to do for Preset Sizes create an array in your resources and then set “autoSizePresetSizes” in xml.

<TextView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    app:autoSizeTextType="uniform"
    app:autoSizePresetSizes="@array/autosize_text_sizes" />

An array of resources.

<array name="autosize_text_sizes">
    <item>12sp</item>
    <item>24sp</item>
    <item>36sp</item>
    <item>48sp</item>
    <item>60sp</item>
    <item>72sp</item>
</array>

You can also set Autosizing using Preset Sizes programmatically.

emojiCompatTextView.setAutoSizeTextTypeUniformWithPresetSizes(resources.getIntArray(R.array.auto_size_array),TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM)

Alright guy’s, that’s my explanation about Autosizing TextView with programmatically and with xml. If you guys have any query about this post please do comment below.

Thank you for being here and keep reading.

Emojis, Emojis, Emojis. If you’re working on a chat app or creating an app in which user has to communicate with other users, this blog is for you. New Emojis are constantly being added to the Unicode standard. Now, it’s important that all users will be able to see the new Emojis even if they are stuck on previous Android Versions.

If you still see an empty box☒ with the cross, it means that the app you’re using probably not using the EmojiCompatLibrary.

Luckily for an Android developer, Google has released a new library knows as EmojiCompat. With EmojiCompat support library your app user does not need to wait for Android OS update to get the latest emoji. The EmojiCompat support library compatible with devices running Android 4.4 (API level 19) or higher.

How does EmojiCompat work under the hood..?

Well, the library takes Unicode in the form of CharSequence that is representing an Emoji if the given code did not recognize on the device, then the library replaces Emoji character with the EmojiSpan. EmojiCompat performs some internal calls, once it completes it renders the Emoji Glyph

EmojiSpan

Base Span class for the emoji replacement. When an emoji is found and needs to be replaced in a CharSquence, an instance of this class is added to CharSequence. This is the process which ensures that the device will display the given Emoji. 

EmojiCompat Widgets

  1. EmojiCompatTextView
  2. EmojiCompatEditText
  3. EmojiCompatButton

Android App Setup

First, add the support library to the dependency section.

implementation 'com.android.support:support-emoji:27.1.1'

EmojiCompat builds on the downloadable fonts mechanism, to make sure that you always have the latest Emojis available. So, if you want the Emojis fonts to be downloadable when your app is installed from play store. Add the below metadata in your Manifest.xml file.

<meta-data
    android:name="fontProviderRequests"
    android:value="Noto Color Emoji Compat" />

Before being used the EmojiCompat library needs one-time Asynchronous setup. Google developer recommend this in your Launch Activity or CustomApplication class. The following shows how to setup EmojiCompat.

val fontRequest = android.support.v4.provider.FontRequest(
                "com.google.android.gms.fonts",
                "com.google.android.gms",
                "Noto Color Emoji Compat",
                R.array.com_google_android_gms_fonts_certs)
val config = FontRequestEmojiCompatConfig(this, fontRequest)
                .setReplaceAll(true)
                .setEmojiSpanIndicatorEnabled(true)
                .setEmojiSpanIndicatorColor(Color.MAGENTA)
                .registerInitCallback(this)
EmojiCompat.init(config)

When using the downloadable fonts configuration create your FontRequest and FontRequestEmojiCompatConfig object.

FontRequest

FontRequest class provide the font provider authority, the font provider package and a list of set hashes for the certificate. For more information about FontRequest see this link.

Configuration Options

  • setReplaceAll: When set to true replace all emojis with EmojiSpans. By default, EmojiCompat tries it’s best to understand if the system can render an emoji and does not replace those emojis.
  • setEmojiSpanIndicatorEnabled: Indicates whether EmojiCompat has replaces an emoji with EmojiSpan. This method is mainly used for debugging purpose.
  • setEmojiSpanIndicatorColor: Color used for indicating EmojiSpan. The default color is Green.
  •  registerCallback: If you want to get notified when your app is ready to start using EmojiCompat use EmojiCompat.InitCallback.

Tip: It’s a good approach that you initialize the EmojiCompat in the background thread. For this, you can use Observable, Kotlin Co-Routine or IntentService.

Using EmojiCompat Widgets

After the successful initialization of EmojiCompat, you can use the Emoji Widgets in the xml file or you can create Custom Widgets by extending Emoji Widgets. 

The following shows how we can add EmojiCompat widgets in the xml file.

<android.support.text.emoji.widget.EmojiTextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

<android.support.text.emoji.widget.EmojiEditText
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

<android.support.text.emoji.widget.EmojiButton
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Now let’s see how we can create EmojiTextView with a custom view. EmojiCompat provides the EmojiTextViewHelper class for custom widget emoji support. The following shows how we can create custom EmojiTextView.

class CustomEmojiTextView(context: Context) : AppCompatTextView(context) {

    init {
        getEmojiTextViewHelper().updateTransformationMethod();
    }

    override fun setFilters(filters: Array<out InputFilter>?) {
        super.setFilters(getEmojiTextViewHelper().getFilters(filters!!))
    }

    override fun setAllCaps(allCaps: Boolean) {
        super.setAllCaps(allCaps)
        getEmojiTextViewHelper().setAllCaps(allCaps)
    }

    private fun getEmojiTextViewHelper() = EmojiTextViewHelper(this)

    // Remaining stuff......
}

Like EmojiTextViewHelper EmojiCompat provides EmojiEditTextHelper for creating custom EditText with emoji support. The following shows how we can create custom EmojiEditText.

class CustomEmojiEditText(context: Context) : AppCompatEditText(context) {

    init {
        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener))
    }

    override fun setKeyListener(input: KeyListener?) {
        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input!!))
    }

    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection {
        val inoutConnection = super.onCreateInputConnection(outAttrs)
        return outAttrs?.let { getEmojiEditTextHelper().onCreateInputConnection(inoutConnection, it) }!!
    }

    private fun getEmojiEditTextHelper() = EmojiEditTextHelper(this)
}

EmojiCompat with AppCompat

If you’re using AppCompat classes, then you can use EmojiCompat widgets that extend from AppCompat widgets. For this, you’ve to add another dependency.

implementation 'com.android.support:support-emoji-appcompat:27.1.1'

Use EmojiCompat AppCompat widgets in xml.

<android.support.text.emoji.widget.EmojiAppCompatTextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

<android.support.text.emoji.widget.EmojiAppCompatEditText
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

<android.support.text.emoji.widget.EmojiAppCompatButton
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Using EmojiCompat without widgets

If you’re not using EmojiCompat widgets in the xml file. You can still use EmojiCompat library to convert CharSequence into spanned instances with EmojiSpane. The EmojiCompat class provides a “process” method which returns a CharSequence. 

The following shows how we can use it.

val charSequence = EmojiCompat.get().process("Normal TextView \uD83D\uDE48\uD83D\uDE49\uD83D\uDE4A")
normalTextView.text = charSequence

Bundled Fonts

If you can not use downloadable fonts then you can bundle the emoji fonts in APK file. For this, you have to add a new dependency to a build. gradle file.

implementation 'com.android.support:support-emoji-bundled:27.1.1'

Now we simply need to init our EmojiCompat with BundledEmojiCompatConfig. Loading the size of fonts is multiple MegaBytes, so do run this on a background thread instead of the main thread.

val config = new BundledEmojiCompatConfig(context : this)
EmojiCompat.init(config);

Note: By adding bundled fonts your APK size will increase.

So, you guy’s see that how we can add EmojiCompat library with very much less code and also it has backward compatibility as well. I hope this blog gives you a good understanding of EmojiCompat. If you guys have any question please do comment below.

Thank you for being here and keep reading…

Hello, guys today we’re gonna look out a new Android Architecture Component library called Paging. In this blog, I try to explain the different components of the library and tell how they interact. Paging library makes it easier to load data gradually and gracefully in your app. The paging library supports both large bounded list and unbounded lists, such as continuously updating feeds. The paging library is based on the idea of sending lists to the UI with the live data of a list that is observed by RecyclerView.Adapter.

 Scenario

Ok, let’s say our client want’s to make a movie app. In this app he wants to show a list of movies and fetch new movies when the user reaches at the end of a list. So, what you think first in mind. Alright, that’s simple I can make it with RecyclerView.ScrollListener and fetch the new movies when the user reaches at the end of the list. But let me tell you a keynote here, with the ScrollListener approach you might request data actually you don’t need, wasting user battery and bandwidth. The most important is, you have to check every time when the user scrolls down and see whether it’s appropriate to fetch new movies or not.

Example

So, let’s see how we gonna tackle the previous problem with the paging library. In this app, we simply wanna show some movie content fetched with API and of course our app using paging library. For this example, we’re gonna use TheMovieDB API for fetching the movies.

Movie API Response

Before to start I wanna show you the response. Please see this link for API response.

Android App Setup

First, add the Paging dependency in the app level build.gradle file.

// Paging
implementation 'android.arch.paging:runtime:1.0.0-alpha4-1'    // Change it current dependency version
implementation 'android.arch.lifecycle:runtime:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'

Now you guy’s must have to see the response and you noticed that every time we need to request TheMovieDB. We need to add the page number in the query. So, for this type of paging requests, we can easily use PageKeyedDataSource. To use paged keyed data sourcewe need to extend it to our class.

MovieDataSource

The following shows how we can create a paged keyed data source for our MovieDataSource class.

class MovieDataSource(private val serviceApi: ServiceApi) : PageKeyedDataSource<Int, MovieResponse.Movie>() {

    val loadDataState = MutableLiveData<DataLoadState>()

    companion object {
        const val MOVIE_API_KEY = "e5c7041343c99********8b720e80c7"   // Change it with your API_kye
    }

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, MovieResponse.Movie>) {
        loadDataState.postValue(DataLoadState.LOADING)
        val movieCallback: Call<MovieResponse> = serviceApi.getPopular(MOVIE_API_KEY, 1)
        Completable.fromRunnable {
            val response = movieCallback.execute()
            if (response != null && response.isSuccessful && response.body() != null)
                callback.onResult(response.body()!!.movies, 1, 2)
            else
                loadDataState.postValue(DataLoadState.FAILED)
        }.subscribeOn(Schedulers.io())
                .subscribe({ loadDataState.postValue(DataLoadState.COMPLETED) },
                        { loadDataState.postValue(DataLoadState.FAILED) })
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, MovieResponse.Movie>) {
        val movieCallback = serviceApi.getPopular(MOVIE_API_KEY, params.key)
        Completable.fromRunnable {
            val response = movieCallback.execute()
            if (response != null && response.isSuccessful && response.body() != null)
                callback.onResult(response.body()!!.movies, params.key + 1)
        }.subscribeOn(Schedulers.io())
                .subscribe({
                    Log.e("Call Response", "Paged Call Complete")
                }, { it.printStackTrace() })
    }
}

You see we’re passing ServiceApi instance to our MovieDataSource. ServiceApi is the Retorift interface instance. This blog is about paging, I’m not gonna show you how to create Retrofit instance.

So, loadInitial is the overridden method which will be called only first time. In this method, we’re executing our network request to fetch movies. You see I’m passing 1 in the getPopular method. This is because loadInitial will only be called the first time and that’s why it will only fetch the first page of popular movies list. When the response came from a server then we need to setResult in on LoadInitialCallbackThe onResult method takes three parameters. First, is the actual result came from the server, second the page number that we’ve just executed. And the parameter is the next page number which is 2.

Now the second overridden method is loadAfter which will be called every time when a user scrolls down at the end of a list. In this method, we’re doing the same work as we do in loadInitial except for incrementing the page number every time request execute.

Now that we successfully create DataSource, it’s time to see how we can create DataSource.FactoryTo create the data source factory we need to extend it with DataSource.Factory class.

MovieDataFactory

The following shows how to create DataSource.Factory.

class MovieDataSourceFactory(private val serviceApi: ServiceApi) : DataSource.Factory<Int, MovieResponse.Movie> {

    private val mutableDataSource = MutableLiveData<MovieDataSource>()

    override fun create(): DataSource<Int, MovieResponse.Movie> {
        val dataSource = MovieDataSource(serviceApi)
        mutableDataSource.postValue(dataSource)
        return dataSource
    }
}

The paging library provides the LivePagedListBuilder class for getting a LiveData object of type PagedListTo create a live paged list builder pass in the data source factory object and the paging configuration.

MovieRepositoryImp

The following shows how to create LivePagedListBuilder.

class MovieRepositoryImp(private val movieDataSourceFactory: MovieDataSourceFactory) : MovieRepository {

    companion object {
        const val PAGE_SIZE = 15
    }

    @MainThread
    override fun getMovies(): LiveData<PagedList<MovieResponse.Movie>> {
        val config = PagedList.Config.Builder()
                .setInitialLoadSizeHint(PAGE_SIZE)
                .setPageSize(PAGE_SIZE)
                .build()
        return LivePagedListBuilder(movieDataSourceFactory, config)
                .setInitialLoadKey(1)
                .setBackgroundThreadExecutor(Executors.newFixedThreadPool(3))
                .build()
    }
}

That’s the basic way of creating LivePagedListBuilder.

MovieRepository

Below is the MovieRepository interface.

interface MovieRepository {

    fun getMovies(): LiveData<PagedList<MovieResponse.Movie>>

}

The paging library also provides PagedListAdapterwhich helps you present data from PagedList inside recycler viewThe paged list adapter notifies when the pages are loaded.

MovieAdapter

Now let’s see how can we create PagedListAdapter.

class MovieAdapter(context: Context) : PagedListAdapter<MovieResponse.Movie, MovieViewHolder>(object : DiffUtil.ItemCallback<MovieResponse.Movie>() {
    override fun areItemsTheSame(oldItem: MovieResponse.Movie, newItem: MovieResponse.Movie) = oldItem.title == newItem.title

    override fun areContentsTheSame(oldItem: MovieResponse.Movie, newItem: MovieResponse.Movie) = oldItem.title == newItem.title

}) {

    private val layoutInflater = LayoutInflater.from(context)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
       // return ViewHolder Object
    }

    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        val movie = getItem(position)
        // Show data in to ViewHolder
    }
}

You see PagedListAdapter is accepting DiffUtil.ItemCallbackThe callback computes fine grains update as new data received.

MainActivity

Now everything is done for creating paging stuff. Let’s see how can we use this in our activity class.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)        
        movieRecyclerView.layoutManager = GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false)
        movieRecyclerView.setHasFixedSize(true)
        val movieAdapter = MovieAdapter(this)
        movieRecyclerView.adapter = movieAdapter
        movieRepo.getMovies()        // First create MovieRepositoryImp first
                .observe(this, Observer<PagedList<MovieResponse.Movie>> {
                    movieAdapter.submitList(it)
                })
    }

You guys must be thinking why on earth, I did not create a movie repository instance. If you remember our movie repo needs MovieDataSourceFactory and the factory needs ServiceApi. You see all of these are depending on another object, that’s why I wrote the above application with DepedencyInjection. 

Note: I wrote a pretty good article on how the work with dependency injection and how we can use it in our Android app.

Now it’s a good approach that you stop listening to the new movies list in onStop or onDestroy. 

override fun onStop() {
       super.onStop()
       movieRepo.getMovies()
               .removeObservers(this)
   }

That’s it guys, this is my demonstration about how to work with the paging library. I’m sure there’s a lot more about this library and I encouraged you to read about it. If you wanna see the complete code of the above example see it on GitHub.

Thanks for being here and keep reading.

Hello guy’s, today we gonna look out how to work with QRCode. Recently, I’ve been working on my startup. My startup is based on Wallet App. So, I had to implement QRCode generator and reader on the client side. When creating the app I had faced a lot difficulty to find the right solution. So, I thought it might be good to write a blog about it.
So, let’s see how can we generate and read QRCode in Android app with the custom object. In this example, we’re going to use Zxing library for reading and generating QRCode. 

First, add the following dependency in your app level build.gradle file.

// Zxing barcode dependency
implementation 'me.dm7.barcodescanner:zxing:1.9.8'

Now add the following permission in Android Manifest file.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.hardware.camera" />
<uses-permission android:name="android.hardware.camera.autofocus" />

Now the setup part is done. Let’s see how can we read the QRCode.

Reading QRCode

Add the ZxingScannerView in your activity xml file.

<me.dm7.barcodescanner.zxing.ZXingScannerView
        android:id="@+id/qrCodeScanner"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

</me.dm7.barcodescanner.zxing.ZXingScannerView>

Now get the ZxingScannerView reference in the activity class.

val qrCodeScanner = findViewById<ZxingScannerView>(R.id.qrCodeScanner)
setScannerProperties()

setScannerProperites is my custom method. ZxingScannerView needs some basic properties before start scanning. The following shows how to set scanner properties.

/**
 * Set Qr code scanner basic properties.
 */

private fun setScannerProperties() {
    qrCodeScanner.setFormats(listOf(BarcodeFormat.QR_CODE))
    qrCodeScanner.setAutoFocus(true)
    qrCodeScanner.setLaserColor(R.color.colorAccent)
    qrCodeScanner.setMaskColor(R.color.colorAccent)
    if (Build.MANUFACTURER.equals(HUAWEI, ignoreCase = true))
        qrCodeScanner.setAspectTolerance(0.5f)
}

Now we need to start the scanner. It’s a good approach that we start scanner in onResume and stop the scanner in on onPause.

The following shows to start scanner.

override fun onResume() {
    super.onResume()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
                    MY_CAMERA_REQUEST_CODE)
            return
        }
    }
    qrCodeScanner.startCamera()
    qrCodeScanner.setResultHandler(this)
}

You see the setResultHandler is accepting the RequestHandler type interface and bypassing “this” reference our activity needs to implement this interface.

override fun handleResult(p0: Result?) {
    if (p0 != null) {
        Toast.makeText(this,p0.text,Toast.LENGTH_LONG).show()
    }
}

Now, this abstract function called every time whenever scanner successfully scanned. The scanned result is in Result type object.

The following shows how to stop scanner in onStop or in the onPause method.

/**
    * stop the qr code camera scanner when activity is in the onPause state.
    */
   override fun onPause() {
       super.onPause()
       qrCodeScanner.stopCamera()
   }
Generating QRCode

Generating a QR code is very easy. You guys must be remembered that I said, we’re going to generate QRCode with Custom Object. By, custom object means we can store user info like name, email,image_url etc in QRCode. So, in this example, we’re going to store user full name and age in QRCode. So, when we scan the QRCode, we get user info in the Result object.

Now, this is another activity for generating the QR code. Below is code for the xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    tools:context=".GenerateQrCodeActivity">

    <android.support.design.widget.TextInputLayout
        android:id="@+id/fullNameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="15dp"
        android:theme="@style/TextLabel">

        <android.support.v7.widget.AppCompatEditText
            android:id="@+id/fullNameEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:drawablePadding="10dp"
            android:hint="@string/full_name"
            android:inputType="textCapSentences"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            android:textColor="@color/whiteColor"
            android:textSize="15sp" />

    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:id="@+id/ageLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/fullNameLayout"
        android:layout_marginEnd="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:theme="@style/TextLabel">

        <android.support.v7.widget.AppCompatEditText
            android:id="@+id/ageEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:drawablePadding="10dp"
            android:hint="@string/user_age"
            android:inputType="number"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            android:textColor="@color/whiteColor"
            android:textSize="15sp" />

    </android.support.design.widget.TextInputLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/generateQrCodeButton"
        android:layout_below="@+id/ageLayout"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp">

        <ImageView
            android:id="@+id/qrCodeImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginEnd="25dp"
            android:layout_marginStart="25dp"
            android:contentDescription="@null" />

    </FrameLayout>

    <Button
        android:id="@+id/generateQrCodeButton"
        style="?android:buttonBarButtonStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@drawable/square_primary_button_drawable"
        android:text="@string/generate_qr_code"
        android:textColor="@color/whiteColor"
        android:textStyle="bold" />

</RelativeLayout>

Now in this xml, we have one EditText for the full name and one for user age. We have an ImageView where we show the QR code and one Button for generating QR code.

I’m gonna show you quickly how to generate QR code except for the prep work.

The following shows how to generate QR code.

generateQrCodeButton.setOnClickListener {
            if (checkEditText()) {
                val user = UserObject(fullName = fullNameEditText.text.toString(), age = Integer.parseInt(ageEditText.text.toString()))
                val serializeString = Gson().toJson(user)
                val bitmap = QRCodeHelper
                        .newInstance(this)
                        .setContent(encryptedString)
                        .setErrorCorrectionLevel(ErrorCorrectionLevel.Q)
                        .setMargin(2)
                        .getQRCOde()
                qrCodeImageView.setImageBitmap(bitmap)
            }
        }

You see in click listener we’re creating a User object for QR code. We can only create QR code with String. So, we have to serialize the User object into a string with GsonAfter that, we need to set QR code setting for generating the code. The QRCodeHelper is my custom class for the generating QR code.

Note: You see we’re serializing the user object when generating the code. So, we’ve to deserialize the string into User object when the scanner successfully scanned the QR code. One more important thing to say here please do not create a huge object for creating QR code. If you create a huge object your scanner needs more time to scan the code.

Below is the QRCodeHelper class.

public class QRCodeHelper {

    private static QRCodeHelper qrCodeHelper = null;
    private ErrorCorrectionLevel mErrorCorrectionLevel;
    private int mMargin;
    private String mContent;
    private int mWidth, mHeight;

    /**
     * private constructor of this class only access by stying in this class.
     */

    private QRCodeHelper(Context context) {
        mHeight = (int) (context.getResources().getDisplayMetrics().heightPixels / 2.4);
        mWidth = (int) (context.getResources().getDisplayMetrics().widthPixels / 1.3);
        Log.e("Dimension = %s", mHeight + "");
        Log.e("Dimension = %s", mWidth + "");
    }

    /**
     * This method is for singleton instance od this class.
     *
     * @return the QrCode instance.
     */

    public static QRCodeHelper newInstance(Context context) {
        if (qrCodeHelper == null) {
            qrCodeHelper = new QRCodeHelper(context);
        }
        return qrCodeHelper;
    }

    /**
     * This method is called generate function who generate the qrcode and return it.
     *
     * @return qrcode image with encrypted user in it.
     */

    public Bitmap getQRCOde() {
        return generate();
    }

    /**
     * Simply setting the correctionLevel to qrcode.
     *
     * @param level ErrorCorrectionLevel for Qrcode.
     * @return the instance of QrCode helper class for to use remaining function in class.
     */

    public QRCodeHelper setErrorCorrectionLevel(ErrorCorrectionLevel level) {
        mErrorCorrectionLevel = level;
        return this;
    }

    /**
     * Simply setting the encrypted to qrcode.
     *
     * @param content encrypted content for to store in qrcode.
     * @return the instance of QrCode helper class for to use remaining function in class.
     */

    public QRCodeHelper setContent(String content) {
        mContent = content;
        return this;
    }

    /**
     * Simply setting the width and height for qrcode.
     *
     * @param width  for qrcode it needs to greater than 1.
     * @param height for qrcode it needs to greater than 1.
     * @return the instance of QrCode helper class for to use remaining function in class.
     */

    public QRCodeHelper setWidthAndHeight(@IntRange(from = 1) int width, @IntRange(from = 1) int height) {
        mWidth = width;
        mHeight = height;
        return this;
    }

    /**
     * Simply setting the margin for qrcode.
     *
     * @param margin for qrcode spaces.
     * @return the instance of QrCode helper class for to use remaining function in class.
     */

    public QRCodeHelper setMargin(@IntRange(from = 0) int margin) {
        mMargin = margin;
        return this;
    }

    /**
     * Generate the qrcode with giving the properties.
     *
     * @return the qrcode image.
     */

    private Bitmap generate() {
        Map<EncodeHintType, Object> hintsMap = new HashMap<>();
        hintsMap.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hintsMap.put(EncodeHintType.ERROR_CORRECTION, mErrorCorrectionLevel);
        hintsMap.put(EncodeHintType.MARGIN, mMargin);
        try {
            BitMatrix bitMatrix = new QRCodeWriter().encode(mContent, BarcodeFormat.QR_CODE, mWidth, mHeight, hintsMap);
            int[] pixels = new int[mWidth * mHeight];
            for (int i = 0; i < mHeight; i++) {
                for (int j = 0; j < mWidth; j++) {
                    if (bitMatrix.get(j, i)) {
                        pixels[i * mWidth + j] = 0xFFFFFFFF;
                    } else {
                        pixels[i * mWidth + j] = 0x282946;
                    }
                }
            }
            return Bitmap.createBitmap(pixels, mWidth, mHeight, Bitmap.Config.ARGB_8888);
        } catch (WriterException e) {
            e.printStackTrace();
        }
        return null;
    }
}

That’s it guy’s, this is my demonstration about generating and reading QR code with the custom object. If you guy’s want to see the code of the example, that I’ve shown you in the above see it on GitHub.

I hope you guys learn how can we generate and read QR code. If you have any queries please do comment below.

Thank you for being here and keep reading…

Today we’re gonna look out a new Android Architecture Component library called Room. The room is basically a “Persistence library that provides an abstraction over SQLite“. The room is a new way to create the database in your Android apps. Although the Android framework provides built-in support for SQLite database, there are many drawbacks working with SQLite.

Drawbacks Of SQLite

Before the start, I want to clarify the drawbacks of SQLite database.

  1. You have to write a lot of boilerplate code.
  2. You have to implement object mapping for every query you write.
  3. Difficult to implement database migrations.
  4. Database operation on the main thread.

Components of Room

Now there are three main components for creating the database with the room.

  1. EntityEntity is an annotated class. This annotation is used to create the database table. After adding Entity annotation room will take care of creating the database table for you.
  2. Dao: Dao is also an annotated class. To access the data from the database you used Dao (Data Access Object). In Dao interface, you declare all the methods needed to work with the database.
  3. DatabaseDatabase is an annotated class. In database class, we define all of our Entity class and tell the version of the database.

Android App Coding

So, enough of this theory let’s build a real-life scenario app. We’re going to create a Book Library app. In this app, the user adds a book name and select the who is the author then insert the book along with author name into the database.

Alright first create a new project in Android Studio with empty activity. Now add the Room dependency into a build.gradle file.

// Room database dependencies
implementation 'android.arch.persistence.room:runtime:1.1.0'   // Use current library version
kapt 'android.arch.persistence.room:compiler:$versions.1.1.0'  // If language is kotlin remain same else use annotationProcessor instead of kapt

Note: If you’re using Kotlin language please add kapt annotation plugin at the top of a build.gradle file.

Now let’s create our first component which is EntityIn our example, we need two Entity classes one for Author of a book and one for Book itself. It means we have two tables in our database.

The following shows how to create our first Entity of Author class.

AuthorModel

@Entity(tableName = "authors")
data class AuthorModel(@PrimaryKey(autoGenerate = true)
                       @ColumnInfo(name = "author_id")
                       val authorId: Long = 0,
                       @ColumnInfo(name = "author_name")
                       var authorName: String)

For Entity, we add the annotation to our class and tell the name of a table. Now in every Entity class, one Primary Key is necessary. In order to tell which value is the primary key annotate with @PrimaryKey. The @ColumnName is for describing the name of table values. The @ColumnName is not necessary for every field if the not defined room will use the variable name when creating the table.

Below is another Entity for BookItem class.

BookItem

@Entity(tableName = "books"
        , indices = [(Index(value = ["book_id"], name = "idx_books_book_id"))],
        foreignKeys =
        [(ForeignKey(
                entity = AuthorModel::class
                , parentColumns = ["author_id"]
                , childColumns = ["book_id"]
                , onUpdate = ForeignKey.CASCADE
                , onDelete = ForeignKey.CASCADE))]
)
data class BookItem(@PrimaryKey @ColumnInfo(name = "author_id") val authorId: Long, @ColumnInfo(name = "book_id") val bookId: Long, val name: String)

Now, this Entity class is complicated than the previous one. In BookItem we use a Foreign Key relation with the Author class table. You see every book has an author that’s why we need the authorId to specify who is the author of the book.

We create database tables right, how do we access the data. For accessing the data here comes the Dao’s annotation. For creating a Dao you need to create an interface and annotate with Dao and declares all the methods needed to work with the database. There are four annotations when declaring the methods in Dao’s

  1. InsertInsert for inserting the data.
  2. Update:  For updating the data.
  3. Delete: For deleting the data.
  4. QueryThis annotation is for SQL statement and this checked at compile time. If the query has an error it will tell you at compile time instead of when executing the query.

Note: All of these queries is Synchronous meaning that they will be run on the same thread you’re triggering from. If that’s Main Thread your app will crash IllegalStateExceptionSo, the best approach is that you’ve to run these queries from Background Thread and get the result in Main Thread. For this, you can use RxJava and Kotlin Co-routines.

Now let’s see how we can create a basic Dao interface.

@Dao
interface BaseDao<in T> {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(t: T): Long

    @Delete
    fun delete(type : T)

    @Update
    fun update(type : T)

}

See every database table has some base methods like insert, delete, update etc. So, I create a generic Dao interface and we just need to extend this BaseDao interface.

Below is the AuthorDao interface.

AuthorDao

@Dao
interface AuthorDao : BaseDao<AuthorModel> {

    @Query(value = "SELECT * FROM authors")
    fun getAllAuthors(): List<AuthorModel>

    @Query(value = "SELECT * FROM authors WHERE author_id = :authorId")
    fun getAuthorWithId(authorId: Long): AuthorModel?

    @Query(value = "SELECT author_id FROM authors")
    fun getAllIds(): List<Long>
}

You see with Query annotation we’re providing a SQL query and Room will be able to provide the data according to SQL query.

Below is the BookDao interface.

BookDao

@Dao
interface BookDao : BaseDao<BookItem> {

    @Query(value = "SELECT * FROM books")
    fun getAllBooks(): List<BookItem>

    @Query(value = "SELECT * FROM books WHERE author_id = :authorId")
    fun getBooksWithAuthorId(authorId: Long): List<BookItem>
}

The class, that’s put the Entities and Dao’s together is the RoomDatabaseIn the database class, we define all the Entities and the Version of the database.

Below show’s how to create RoomDatabase class.

BookLibraryDatabase

@Database(entities = [AuthorModel::class, BookItem::class], version = 1)
abstract class BookLibraryDatabase : RoomDatabase() {

    abstract fun authorDao(): AuthorDao

    abstract fun bookDao(): BookDao
}

Everything is done for creating a RoomDatabaseIt’s time to see how can we create RoomLibraryDatabase instance and get the Dao’s object.

Below shows how to create RoomLibraryDatabase instance.

val database = Room.databaseBuilder(context, BookLibraryDatabase::class.java, DATABASE_NAME).build()

So, we have a database object. Now It’s time to use all of the database utility methods that we write in Dao class.

// For inserting the author
Observable.just(database.authorDao().insert(AuthorModel(authorName = "Ahsen Saeed")))
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe({// Author inserted},{it.printStacktrace()})

// Author with Id
Observable.just(database.authorDao().getAuthorWithId(id))
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe({System.out.println(it)},{it.printStacktrace()})

// Get all authors
Observable.just(database.authorDao().getAllAuthors())
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe({ // Here you all authors},{it.printStacktrace()})

// Get books of authors with id
Observable.just(database.bookDao().getBooksWithAuthorId(id))
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe({ // Here you have all books with author id},{it.printStacktrace()})

// Delete the book
Observable.just(database.bookDao().delete(book))
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe({ // Book deleted successfully},{it.printStacktrace()})

// Similarly you can perform all the utility the functions

You guys must be remembered that we need to call these methods from Background Thread and get the result on the Main Thread.

So that’s it, I’m going to end this blog here. I hope you guys, have learned something from this post. If you’ve any queries regarding Room persistence, please do comment below.

I have also written some articles on Room Persistence with RxJava2 and Room Persistence with LiveData.

Thank you for being here and keep reading…

Hi, guys today I’m gonna talked to you about a new library Google released is WorkManager. WorkManager is a part of AndroidJetpack and an Architecture Component for a background work. WorkManager chooses an appropriate way to schedule a background task depending on the device API level and including dependencies. It might use JobScheduler, Firebase JobDispatcher, or AlarmManager

Features Of WorkManager

  1. Guaranteed, constraint-aware execution: Let’s say If I wanna upload a photo. I only wanna do it when the device has the network, that’s the constraint.
  2. Respectful of system background restrictions: Let’s say If your app in a doze mode it won’t wake up your phone just to do those work.
  3. Backwork compatible with or without Google play services
  4. Queryable: If you have a queue of work, you can actually check what’s the state. Is it running right now, has it succeeds or fails.
  5. Chainable: It is also chainable like you have work A depending on work B and C which in turn out a dependent on work D.
  6. Opportunistic: This means WorkManager tried to execute the work in your process as soon as the constraint match- without actually needing JobScheduler.

WorkManager Basics (Core Classes)

There are few WorkManager classes you need to know about.

  1. Worker: This is the class that does the background work. This is where you write most of your business logic.
  2. WorkRequest: This class actually schedules your work request and make it run.

Types of WorkRequest

  1. OneTimeWorkRequest: Things which can only be done once.
  2. PeriodicWorkRequest: For recurring work.

Android App Setup

So, enough of this theory let’s see how we can integrate WorkManager in Android app.

First, open your app level build.gradle file and add a dependency.

implementation 'android.arch.work:work-runtime:1.0.0-alpha01'   // Please use current version of library

Now the setup part is done. Let’s see how we can make a simple Worker.

class UploadPhotoWorker : Worker() {

    override fun doWork(): WorkerResult {
        return try {
            // uploadPhoto()     // Replace this function with logn running task.
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

The doWork method actually runs on a background thread. WorkManager automatically runs this function on a background thread you don’t need to do run. You see doWork method have return type WorkerResult. So, we need to return Success if everything goes well and Failure if any error occurred.

Now let’s see how we can call this UploadPhotoWorker class.

val workManager = WorkManager.getInstance()
val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
            .build()
workManager.enqueue(uploadPhotoRequest)

Soon after this request enqueued it will start uploading pictures. Now, what if you lose connectivity in the middle of this or even before this what if you do not have connectivity. You actually wanna constraints in this case.

The following shows how to add constraints in WorkRequest.

val workManager = WorkManager.getInstance()
        val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

        workManager.enqueue(uploadPhotoRequest)

Now let’s say I want to observe the request. I wanna show a progress bar while this work is executing and I wanna hide the spinner when it’s done.

The following shows how to observe a WorkRequest.

val status = workManager.getStatusById(uploadPhotoRequest.id)

        status.observe(LifeCycleOwner, Observer {
            if (it != null && it.state.isFinished) {
                progressBar.setVisbility(GONE)
            }
        })

You see every WorkRequest has a unique UUID. With this id, we can observe that request. In here it means WorkStatus of the requestIf you want to learn more about WorkStatus see this link.

So, let’s move a step up in concept here and talk about Chaining Work. Let’s say my problem is I want to upload multiple photos to the server. Before this, I want to compress these photos then upload to the server. So, how would I do that, here comes the Chaining work concept.

The following shows how to Chain work.

class CompressPhotoWorker : Worker() {
    override fun doWork(): WorkerResult {
        return try {
            // compressPhotos()
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

class UploadPhotoWorker : Worker(){
    override fun doWork(): WorkerResult {
        return try {
            // uploadPhoto()
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

val compressPhotoRequest = OneTimeWorkRequest.Builder(CompressPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

workManager.beginWith(compressPhotoRequest).then(uploadPhotoRequest).enqueue()

In here we’re telling to our workManager that first execute compressPhotoRequest then execute uploadPhotoRequest. The beginWith ensures that compressPhotoRequest execute first then execute uploadPhotoRequest.

Now let’s say I want to upload multiple photos in parallel. The following shows how to execute requests in parallel.

val oneUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()
val twoUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()
val threeUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()

workManager.enqueue(oneUploadPhotoRequest, twoUploadPhotoRequest, threeUploadPhotoRequest)

These all work requests will be executed in parallel.

Now let’s see how to cancel the work request. The following shows how to cancel work request.

workManager.cancelWorkById(uploadPhotoRequest.id)

Note: Cancellation is the best effort. The work is an asynchronous thing, they may be happening in the background before you had a chance to cancel it. It may be completed.

Now I have a problem here, I want to update my profile picture to the server and my network state is not good. The first picture is not uploaded yet, I decided that I want to upload another profile picture. As a user, I want my updated profile photo on my account. How would I solve this, here comes UniqueWork in the picture?

The following shows how to use unique work.

fun uploadProfilePhoto(bitmap: Bitmap) {
    val dataInput = Data.Builder()
    dataInput.putString("image", encodeToString(bitmap))    // encoding function convert bitmap to String
    val data = dataInput.build()
    val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoRequest::class.java)
            .setInputData(data)
            .build()
    mWorkManager.getInstance().beginUniqueWork("update_photo", ExistingWorkPolicy.REPLACE, uploadPhotoRequest)
            .enqueue()
}

In this, we’re using beginUniqueWork method. In this method, the first parameter is for uniqueWorkName. The second parameter is the ExistingWorkPolicy.REPLACE. Replace basically cancel and deletes any existing in-flight operation of this name. So, the last one always does win. In our case, the last profile picture will be uploaded and previous will be canceled. The third parameter is WorkRequest.

How WorkManager Works Under The Hood

Alright so, we’ve talked a lot about code. Let’s talked about how it all works under the hood. I have made a diagram and I want to explain WorkManager with this.

android workmanager

When To Use WorkManager

  1. It’s ok to use WorkManager to upload data to the server.
  2. It’s ok to parse data and store it in the database.
  3. Not ok to extract palette color and update ImageView.
  4. Not ok to parse data and update the contents of a view.
  5. It’s not good to process the payment transaction.

That’s it guys, I’m going to end this blog here. I’m sure there’s a lot more about WorkManager but it’s good if you guys explore it. The complete example of WorkManager is on GitHub.

Download Complete Code

I hope this blog gives you a good understanding of WorkManager. If you’ve any queries please do comment below.

Thank you for being here and keep reading…