android kotlin - DataBinding onClick example

MainActivity.kt

package com.cfsuman.jetpack

import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import com.cfsuman.jetpack.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Inflate view and obtain an instance of the binding class
        // ActivityMainBinding came from activity_main.xml file name with Binding suffix
        // Make your data type as this format
        val binding:ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main) as ActivityMainBinding

        // Define the student to data bind in xml layout
        binding.studentClass = Student("Hasiba Yeasmin",20)

        // Define the listener for binding
        binding.listener = ClickHandler()
    }
}

// Make a student data class
// Class name should start with capital letter otherwise may cause an error
data class Student(val name:String, val age:Int)


// Class to handle view click
class ClickHandler {
    fun onTextViewClick(view: View, student: Student) {
        val tv = view as TextView
        tv.setBackgroundColor(Color.YELLOW)
        tv.setTextColor(Color.RED)
        tv.text = student.name.toUpperCase() + "\n" + student.age
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
                name="studentClass"
                type="com.cfsuman.jetpack.Student"/>
        <variable
                name="listener"
                type="com.cfsuman.jetpack.ClickHandler"/>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/rootLayout"
            tools:context=".MainActivity"
            android:background="#fdfdfc">
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView"
                android:text="@{studentClass.name+ '\n' + studentClass.age}"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"
                tools:text="Sample Name"
                android:layout_marginTop="8dp"
                android:padding="16dp"
                app:layout_constraintTop_toTopOf="parent"
                android:textSize="40sp"
                android:textAlignment="center"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                android:background="#F8D1D1"
                android:onClick="@{view-> listener.onTextViewClick(view,studentClass)}"
        />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
gradle settings

// Enable data binding
android {
    dataBinding {
        enabled = true
    }
}
gradle.properties

# To enable the new data binding compiler
android.databinding.enableV2=true

android kotlin - DataBinding visibility example

MainActivity.kt

package com.cfsuman.jetpack

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.cfsuman.jetpack.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Inflate view and obtain an instance of the binding class
        // ActivityMainBinding came from activity_main.xml file name with Binding suffix
        // Make your data type as this format
        val binding:ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main) as ActivityMainBinding

        // Define the student to data bind in xml layout
        // Test the example by alternating the true false values
        //binding.studentClass = Student("Hasiba Yeasmin",true)
        binding.studentClass = Student("Hasiba Yeasmin",false)
    }
}

// Make a student data class
// Class name should start with capital letter otherwise may cause an error
data class Student(val name:String, val isVisible:Boolean)
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
                name="studentClass"
                type="com.cfsuman.jetpack.Student"/>
        <import type="android.view.View"/>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/rootLayout"
            tools:context=".MainActivity"
            android:background="#fdfdfc">
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView"
                android:text="@{studentClass.name}"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"
                tools:text="Sample Name"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"
                android:textSize="50sp"
                android:textColor="#B30404"
                android:textAlignment="center"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                android:visibility="@{studentClass.isVisible? View.VISIBLE : View.GONE}"
                android:background="#F8D1D1"
        />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
gradle settings

// Enable data binding
android {
    dataBinding {
        enabled = true
    }
}
gradle.properties

# To enable the new data binding compiler
android.databinding.enableV2=true

android kotlin - DataBinding example

MainActivity.kt

package com.cfsuman.jetpack

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.cfsuman.jetpack.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Inflate view and obtain an instance of the binding class
        // ActivityMainBinding came from activity_main.xml file name with Binding suffix
        // Make your data type as this format
        val binding:ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main) as ActivityMainBinding

        // Define the student to data bind in xml layout
        binding.studentClass = Student("Jenny Jones",25)
    }
}

// Make a student data class
// Class name should start with capital letter otherwise may cause an error
data class Student(val name:String, val age:Int)
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
                name="studentClass"
                type="com.cfsuman.jetpack.Student"/>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/rootLayout"
            tools:context=".MainActivity"
            android:background="#fdfdfc">
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView"
                android:text="@{studentClass.name +'\n'+ studentClass.age}"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"
                tools:text="Sample Name"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"
                android:textSize="50sp"
                android:textColor="#03A9F4"
                android:textAlignment="center"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
gradle settings

android {
    dataBinding {
        enabled = true
    }
}
gradle.properties

# To enable the new data binding compiler
android.databinding.enableV2=true

android kotlin - ViewModel LiveData DataBinding example

MainActivity.kt

package com.cfsuman.jetpack

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import kotlinx.android.synthetic.main.activity_main.*
import androidx.lifecycle.ViewModelProviders
import com.cfsuman.jetpack.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var mModel: RandomUUIDViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Obtain the view model component
        mModel = ViewModelProviders.of(this).get(RandomUUIDViewModel::class.java)

        // Inflate view and obtain an instance of the binding class
        // ActivityMainBinding came from activity_main.xml with Binding suffix
        // Make your data type as this format
        val binding:ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main) as ActivityMainBinding

        // Set the view model
        binding.randomUUIDViewModel = mModel

        // Set the life cycle owner
        binding.setLifecycleOwner(this)

        // Set the initial uuid
        mModel.setRandomUUID()

        // Button click listener
        button.setOnClickListener{
            // Set the data
            mModel.setRandomUUID()
        }
    }
}
RandomUUIDViewModel.kt

package com.cfsuman.jetpack

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.*


class RandomUUIDViewModel: ViewModel(){

    private val randomUUID = MutableLiveData<String>()

    fun setRandomUUID(){
        randomUUID.value = UUID.randomUUID().toString()
    }

    fun getRandomUUID(): LiveData<String>{
        return randomUUID
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable 
                name="randomUUIDViewModel" 
                type="com.cfsuman.jetpack.RandomUUIDViewModel"/>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/rootLayout"
            tools:context=".MainActivity"
            android:background="#fdfdfc">
        <Button
                android:text="Generate Random UUID"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/button"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintHorizontal_bias="0.454"
                android:layout_marginTop="32dp"
                app:layout_constraintTop_toTopOf="parent"/>
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:id="@+id/textView"
                android:text="@{randomUUIDViewModel.randomUUID}"
                android:layout_marginTop="32dp"
                app:layout_constraintTop_toBottomOf="@+id/button"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
gradle dependencies

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
gradle settings

android {
    dataBinding {
        enabled = true
    }
}
gradle.properties

# To enable the new data binding compiler
android.databinding.enableV2=true

Android Kotlin: ViewModel LiveData example

ViewModel and LiveData in Android Kotlin: A Practical Example

In this article, we will explore a simple yet practical example of using ViewModel and LiveData in an Android Kotlin project. These two components, part of the Android Architecture Components, help developers manage UI-related data in a lifecycle-conscious way. The example consists of a MainActivity, a RandomNumberViewModel, and a simple XML layout with a button and a TextView.

The goal of the example is to demonstrate how LiveData and ViewModel can be used together to generate and observe random numbers. Every time a user clicks the button, a new random number is generated and displayed in the TextView. The use of ViewModel ensures that the random number persists across configuration changes, such as device rotation, while LiveData makes sure the UI updates automatically whenever the data changes.

MainActivity: Setting up ViewModel and Observing LiveData

In the MainActivity, the onCreate function is responsible for setting up the ViewModel and linking it to the UI components. First, we use the ViewModelProviders.of(this).get() method to instantiate the RandomNumberViewModel. This ViewModel instance is tied to the lifecycle of the activity, meaning it survives configuration changes like screen rotations.

Next, an Observer is created for the LiveData object. The Observer listens for changes in the currentRandomNumber property of the ViewModel. When a new number is generated, this observer updates the TextView with the latest value. The observe() method is used to bind the LiveData object to the activity's lifecycle, ensuring the UI only updates when the activity is in an appropriate state (e.g., started or resumed).

The Button in the UI is wired to generate a new random number every time it is clicked. This is achieved by updating the currentRandomNumber property of the ViewModel, which triggers the Observer to update the displayed number.

RandomNumberViewModel: Managing Data

The RandomNumberViewModel class extends ViewModel, providing a place to store and manage the random number. The currentRandomNumber property is a MutableLiveData object, which can hold integer values and notify observers when the data changes.

The by lazy keyword is used to initialize the currentRandomNumber only when it is accessed for the first time. This ensures that the LiveData object is not created unnecessarily, improving performance. The ViewModel ensures that the random number persists even when the MainActivity is destroyed and recreated during a configuration change.

Layout Design: Button and TextView

The layout file activity_main.xml defines a simple UI with a Button and a TextView. The button, labeled "Generate New Random Number", triggers the generation of a new random number when clicked. The TextView displays the current number generated by the ViewModel.

The layout uses a ConstraintLayout to manage the positioning of the Button and TextView. The button is placed at the top, and the TextView is positioned directly below it, centered horizontally. This minimal UI design is perfect for demonstrating the interaction between ViewModel, LiveData, and the activity lifecycle.

Dependencies

To use ViewModel and LiveData, the project needs the appropriate dependencies. In this example, the following dependency is added to the build.gradle file:

gradle

def lifecycle_version = "2.0.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

This ensures that the ViewModel and LiveData classes are available for use in the project.

Conclusion

This example demonstrates the power of ViewModel and LiveData in building responsive and lifecycle-aware Android applications. By separating UI data management from the activity lifecycle, these components allow for more maintainable and robust code. The use of LiveData ensures that the UI is always in sync with the data, while ViewModel provides a way to store data that survives configuration changes.

This architecture is ideal for scenarios where UI data needs to be persisted across different states of an activity. By adopting these components in your applications, you can greatly improve performance, simplify your code, and provide a more seamless user experience.


MainActivity.kt

package com.cfsuman.jetpack

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import java.util.*


class MainActivity : AppCompatActivity() {

    private lateinit var mModel: RandomNumberViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Get the view model
        mModel = ViewModelProviders.of(this).get(RandomNumberViewModel::class.java)

        // Create the observer which updates the ui
        val randomNumberObserver = Observer<Int>{newNumber->
            // Update the ui with current data
            textView.text = "Current Number : $newNumber"
        }

        // Observe the live data, passing in this activity as the life cycle owner and the observer
        mModel.currentRandomNumber.observe(this,randomNumberObserver)

        // Button click listener
        button.setOnClickListener{
            // Change the data
            mModel.currentRandomNumber.value = Random().nextInt(50)
        }
    }
}
RandomNumberViewModel.kt

package com.cfsuman.jetpack

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class RandomNumberViewModel:ViewModel(){
    // Create a LiveData with a random number
    val currentRandomNumber:MutableLiveData<Int> by lazy {
        MutableLiveData<Int>()
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <Button
            android:text="Generate New Random Number"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.454"
            android:layout_marginTop="32dp" 
            app:layout_constraintTop_toTopOf="parent"/>
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView"
            android:text="Live Data"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/button" 
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent" 
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
gradle dependencies

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

Android Kotlin: WorkManager parameters example

Introduction

WorkManager is an Android architecture component that provides a powerful and flexible API for scheduling background tasks. It allows developers to enqueue tasks that can be guaranteed to execute, even if the app is killed or the device is rebooted. In this article, we will explore how to use WorkManager in a Kotlin Android app, with an example that demonstrates how to download an image from the web and display it in the app.

This example demonstrates the use of WorkManager's OneTimeWorkRequest to download images asynchronously, passing URL parameters to the worker class. The worker performs the download operation, saves the image to internal storage, and the app later retrieves and displays it.

MainActivity and User Interaction

In the MainActivity.kt file, the app's layout is set in the onCreate() method. Two buttons are used for interacting with the user: one for downloading an image (btnDownload) and another for displaying the downloaded image (btnShow). The user inputs the image URL into a text field (editText), and the Download Image button initiates the image download process.

Before starting the download, the code validates the URL using URLUtil.isValidUrl(). If the URL is valid, the image URL is packed into a Data object and passed as input data to the WorkManager. This input data allows the worker to access the URL inside its doWork() method.

The OneTimeWorkRequest is then created and enqueued by calling WorkManager.getInstance().enqueue(). This ensures that the task will be scheduled for execution. If the URL is invalid, the user receives a toast message informing them of the error.

WorkManager and Image Downloading

The actual downloading of the image happens in the DownloadImageWorker class, which extends the Worker class provided by the WorkManager API. The doWork() method is responsible for fetching the image from the URL passed to it in the input data. The URL is converted to a URL object using the helper function stringToURL().

A HttpURLConnection is used to establish the connection to the URL. The image is downloaded as a stream and decoded into a Bitmap object using BitmapFactory.decodeStream(). After successfully downloading the image, the Bitmap is saved to the device's internal storage using an extension function, saveToInternalStorage(). If the download or connection fails, appropriate error handling is in place to log the error and return a Result.failure().

Saving and Retrieving the Image

The Bitmap.saveToInternalStorage() function is an extension function that handles saving the downloaded image to the device's internal storage. It uses the ContextWrapper class to get the internal storage directory and saves the image as a PNG file with a unique name generated by UUID.randomUUID().

In addition to saving images, two more extension properties are defined: countSavedBitmap, which returns the number of saved images, and savedBitmapList, which provides a list of image filenames stored in the internal storage. These properties are used to retrieve and display the most recently saved image when the user clicks the Show Image button in the app.

Displaying the Image

When the user presses the Show Image button, the app checks if any images have been downloaded by evaluating countSavedBitmap. If images are found, the last saved image is retrieved and displayed in the ImageView by calling nameToUri() to convert the saved filename into a URI. The URI is then set as the source of the ImageView, and the image's filename is displayed in the TextView below the image.

If no images are available, the user is informed via a toast message that no images have been downloaded.

Summary

This Kotlin Android app demonstrates a practical use case for WorkManager by downloading images in the background, passing input parameters such as a URL, and handling the downloading task in a worker. The image is saved to internal storage, and the app retrieves and displays the image when requested by the user.

WorkManager provides a simple and reliable way to handle background tasks that need to continue even if the app is closed. Its ability to pass parameters, handle retries, and schedule tasks for guaranteed execution makes it a robust tool for background task management in Android development.


MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import android.graphics.BitmapFactory
import java.io.BufferedInputStream
import java.io.IOException
import java.net.HttpURLConnection
import android.graphics.Bitmap
import android.webkit.URLUtil
import androidx.work.*
import java.net.URL


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Download image
        btnDownload.setOnClickListener{
            // Test image url
            // https://www.freeimageslive.com/galleries/nature/animals/pics/guinea_pig.jpg
            // https://www.freeimageslive.com/galleries/nature/animals/pics/hens4949.jpg

            val text:String? = editText.text.toString()
            if (URLUtil.isValidUrl(text)){
                // Define the input data for work manager
                val data = Data.Builder()
                data.putString("url",editText.text.toString())

                // You can pass more data in parameters
                //data.putString("url2","https://www.freeimageslive.com/galleries/nature/animals/pics/hens4949.jpg")

                // Create an one time work request
                val downloadImageWork = OneTimeWorkRequest.Builder(DownloadImageWorker::class.java)
                    .setInputData(data.build())
                    .build()

                // Enqueue the work
                WorkManager.getInstance().enqueue((downloadImageWork))
            }else{
                toast("invalid url")
            }
        }

        // Display the downloaded image
        btnShow.setOnClickListener{
            if (countSavedBitmap>0){
                imageView.setImageURI(nameToUri(savedBitmapList[countSavedBitmap-1]))
                textView.text = savedBitmapList[savedBitmapList.count()-1]
            }else
            {
                toast("No downloaded image found")
            }
        }
    }
}


// Worker class to download image
class DownloadImageWorker(context:Context, params: WorkerParameters): Worker(context,params){
    override fun doWork(): Result {
        // Get the input data from parameters
        val urlString:String? = inputData.getString("url")

        // You can get the more url as the same way
        //val urlString:String? = inputData.getString("url2")

        // Do the work here
        val url:URL? = stringToURL(urlString)

        url?.let {
            // IMPORTANT - Put internet permission on manifest file
            var connection: HttpURLConnection? = null

            try {
                // Initialize a new http url connection
                connection = url.openConnection() as HttpURLConnection

                // Connect the http url connection
                connection?.connect()

                // Get the input stream from http url connection
                val inputStream = connection?.inputStream

                // Initialize a new BufferedInputStream from InputStream
                val bufferedInputStream = BufferedInputStream(inputStream)

                // Convert BufferedInputStream to Bitmap object

                // Return the downloaded bitmap
                val bmp:Bitmap? = BitmapFactory.decodeStream(bufferedInputStream)
                bmp?.saveToInternalStorage(applicationContext)

                Log.d("download","success")
                return Result.success()

            } catch (e: IOException) {
                e.printStackTrace()
                Log.d("download",e.toString())

            } finally {
                // Disconnect the http url connection
                connection?.disconnect()
            }

        }

        Log.d("download","failed")
        return Result.failure()
    }
}
xBitmap.kt

package com.cfsuman.jetpack

import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap
import android.net.Uri
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.net.MalformedURLException
import java.net.URL
import java.util.*


// Extension function to save bitmap in internal storage
fun Bitmap.saveToInternalStorage(context: Context): Uri {
    // Get the context wrapper instance
    val wrapper = ContextWrapper(context)

    // Initializing a new file
    // The bellow line return a directory in internal storage
    var file = wrapper.getDir("images", Context.MODE_PRIVATE)


    // Create a file to save the image
    file = File(file, "${UUID.randomUUID()}.png")

    try {
        // Get the file output stream
        val stream: OutputStream = FileOutputStream(file)

        // Compress bitmap
        this.compress(Bitmap.CompressFormat.PNG, 100, stream)

        // Flush the stream
        stream.flush()

        // Close stream
        stream.close()
    } catch (e: IOException){ // Catch the exception
        e.printStackTrace()
    }

    // Return the saved image uri
    return Uri.parse(file.absolutePath)
}


// Extension function to count internal storage images
val Context.countSavedBitmap:Int
    get(){
        val wrapper = ContextWrapper(this)
        val file = wrapper.getDir("images", Context.MODE_PRIVATE)
        val length = file.list().size
        return length
    }


// Extension function to get internal storage images list
val Context.savedBitmapList:MutableList<String>
    get(){
        val wrapper = ContextWrapper(this)
        val file = wrapper.getDir("images", Context.MODE_PRIVATE)
        val list = file.list().asList().toMutableList()
        return list
    }



fun Context.nameToUri(name:String):Uri{
    val wrapper = ContextWrapper(this)

    // Initializing a new file
    // The bellow line return a directory in internal storage
    var file = wrapper.getDir("images", Context.MODE_PRIVATE)


    // Create a file to save the image
    file = File(file, name)

    val uri = Uri.fromFile(file)

    return uri
}


// Custom method to convert string to url
fun stringToURL(urlString: String?): URL? {
    urlString?.let {
        try {
            return URL(urlString)
        } catch (e: MalformedURLException) {
            e.printStackTrace()
        }
    }

    return null
}


fun Context.toast(message:String)=
    Toast.makeText(this,message, Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <Button
            android:text="Download Image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnDownload"
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.454"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/textInputLayout"/>
    <Button
            android:text="Show Image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnShow"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/btnDownload"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"/>
    <ImageView
            android:layout_width="0dp"
            android:layout_height="0dp"
            tools:srcCompat="@tools:sample/avatars"
            android:id="@+id/imageView"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintHorizontal_bias="0.0"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/textView"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"/>
    <TextView
            android:text="Image URI"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/btnShow"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"/>
    <com.google.android.material.textfield.TextInputLayout
            android:layout_width="395dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            android:id="@+id/textInputLayout">
        <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/editText"
                android:hint="Image URL"
                android:text="https://www.freeimageslive.com/galleries/nature/animals/pics/donkey.jpg"
        />
    </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
gradle dependencies

// WorkManager
def work_version = "1.0.0-beta02"
implementation "android.arch.work:work-runtime:$work_version"

Android Kotlin: WorkManager example

Introduction

In this article, we explore how to use Android's WorkManager with Kotlin to perform background tasks such as downloading images. WorkManager is part of Android Jetpack and is designed for tasks that require guaranteed execution, whether or not the app is in the foreground. It is particularly useful for scenarios like periodic data syncing, large file downloads, and applying long-running operations that need to be preserved even after app termination.

We'll walk through an example that demonstrates how to download an image using WorkManager, store it in internal storage, and display it in an Android app. This guide covers the key components such as setting up the WorkManager request, managing background worker tasks, and handling image storage with Kotlin extensions.

Breakdown of MainActivity

The MainActivity is where the core functionality of the app begins. In the onCreate method, the layout is initialized, which includes two buttons: one for downloading an image (btnDownload) and one for displaying the downloaded image (btnShow). When the "Download Image" button is clicked, a one-time WorkManager request is created and enqueued. This request triggers the DownloadImageWorker class, which handles the task of downloading the image from a specified URL.

The second button, "Show Image," retrieves the most recently downloaded image from internal storage and displays it in an ImageView. This is done using Kotlin extension functions to count and list saved images in internal storage. If no images have been downloaded yet, a message is displayed using a custom toast function.

Worker Class for Downloading the Image

The DownloadImageWorker class extends Worker and is responsible for the background operation of downloading the image from the internet. The doWork() method performs the core task of opening a network connection, fetching the image, and converting it into a Bitmap object.

To ensure the operation works smoothly, the app needs internet permission in the manifest. Once the image is successfully downloaded, it is saved to internal storage using a custom Kotlin extension function (saveToInternalStorage). The result of the work is then logged and returned, with either Result.success() or Result.failure() depending on whether the image was downloaded successfully.

Bitmap Extension Functions

The xBitmap.kt file contains several useful Kotlin extension functions that enhance the handling of images within the app. One of the key functions is saveToInternalStorage, which takes a Bitmap object and saves it to the app's internal storage. It also returns a Uri pointing to the saved file, which is later used to display the image in the app. The image is stored in a directory named "images" within the app's private storage space.

Other extension functions include countSavedBitmap, which returns the number of images stored in internal storage, and savedBitmapList, which provides a list of all the saved images. The nameToUri function helps convert a file name into a Uri, which is necessary for displaying the image in the app's ImageView.

UI Design in XML

The user interface of the app is defined in the activity_main.xml file. The layout is a simple ConstraintLayout with two buttons (btnDownload and btnShow), an ImageView to display the downloaded image, and a TextView that shows the URI of the image. The buttons are set up with onClick listeners in MainActivity to trigger the image download and display actions, respectively.

Gradle Dependencies

To use WorkManager, the app includes a dependency in its build.gradle file. This dependency ensures that WorkManager is available in the app, allowing us to manage background tasks reliably. The specific version used in this example is 1.0.0-beta02, but developers should check for the latest stable version when implementing WorkManager in their own projects.

Summary

This Android Kotlin example showcases how to efficiently use WorkManager to perform background operations such as downloading images and saving them to internal storage. By utilizing Kotlin extension functions, the app simplifies the process of image handling, making it easier to store, retrieve, and display downloaded images. WorkManager provides a powerful way to manage long-running tasks that need guaranteed execution, even when the app is not running.

In conclusion, this example demonstrates the seamless integration of WorkManager for background tasks, Kotlin's extension functions for enhanced code readability, and an efficient UI layout for a user-friendly experience. This approach can be extended to various use cases where background processing and file handling are required in Android applications.


MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import android.graphics.BitmapFactory
import java.io.BufferedInputStream
import java.io.IOException
import java.net.HttpURLConnection
import android.graphics.Bitmap
import androidx.work.*


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Download image
        btnDownload.setOnClickListener{
            // Create an one time work request
            val downloadImageWork = OneTimeWorkRequest.Builder(DownloadImageWorker::class.java).build()
            // Enqueue the work
            WorkManager.getInstance().enqueue((downloadImageWork))
        }


        // Display the downloaded image
        btnShow.setOnClickListener{
            if (countSavedBitmap>0){
                imageView.setImageURI(nameToUri(savedBitmapList[countSavedBitmap-1]))
                textView.text = savedBitmapList[savedBitmapList.count()-1]
            }else
            {
                toast("No downloaded image found")
            }
        }
    }
}


// Worker class to download image
class DownloadImageWorker(context:Context, params: WorkerParameters): Worker(context,params){
    override fun doWork(): Result {
        // Do the work here
        val url = stringToURL("https://www.freeimageslive.com/galleries/buildings/china/pics/nature_water3978.jpg")

        // IMPORTANT - Put internet permission on manifest file
        var connection: HttpURLConnection? = null

        try {
            // Initialize a new http url connection
            connection = url?.openConnection() as HttpURLConnection

            // Connect the http url connection
            connection.connect()

            // Get the input stream from http url connection
            val inputStream = connection.getInputStream()

            // Initialize a new BufferedInputStream from InputStream
            val bufferedInputStream = BufferedInputStream(inputStream)

            // Convert BufferedInputStream to Bitmap object

            // Return the downloaded bitmap
            val bmp:Bitmap? = BitmapFactory.decodeStream(bufferedInputStream)
            bmp?.saveToInternalStorage(applicationContext)

            Log.d("download","success")
            return Result.success()

        } catch (e: IOException) {
            e.printStackTrace()
            Log.d("download",e.toString())

        } finally {
            // Disconnect the http url connection
            connection?.disconnect()
        }
        Log.d("download","failed")
        return Result.failure()
    }
}
xBitmap.kt

package com.cfsuman.jetpack

import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap
import android.net.Uri
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.net.MalformedURLException
import java.net.URL
import java.util.*


// Extension function to save bitmap in internal storage
fun Bitmap.saveToInternalStorage(context: Context): Uri {
    // Get the context wrapper instance
    val wrapper = ContextWrapper(context)

    // Initializing a new file
    // The bellow line return a directory in internal storage
    var file = wrapper.getDir("images", Context.MODE_PRIVATE)


    // Create a file to save the image
    file = File(file, "${UUID.randomUUID()}.png")

    try {
        // Get the file output stream
        val stream: OutputStream = FileOutputStream(file)

        // Compress bitmap
        this.compress(Bitmap.CompressFormat.PNG, 100, stream)

        // Flush the stream
        stream.flush()

        // Close stream
        stream.close()
    } catch (e: IOException){ // Catch the exception
        e.printStackTrace()
    }

    // Return the saved image uri
    return Uri.parse(file.absolutePath)
}


// Extension function to count internal storage images
val Context.countSavedBitmap:Int
    get(){
        val wrapper = ContextWrapper(this)
        val file = wrapper.getDir("images", Context.MODE_PRIVATE)
        val length = file.list().size
        return length
    }


// Extension function to get internal storage images list
val Context.savedBitmapList:MutableList<String>
get(){
    val wrapper = ContextWrapper(this)
    val file = wrapper.getDir("images", Context.MODE_PRIVATE)
    val list = file.list().asList().toMutableList()
    return list
}



fun Context.nameToUri(name:String):Uri{
    val wrapper = ContextWrapper(this)

    // Initializing a new file
    // The bellow line return a directory in internal storage
    var file = wrapper.getDir("images", Context.MODE_PRIVATE)


    // Create a file to save the image
    file = File(file, name)

    val uri = Uri.fromFile(file)

    return uri
}


// Custom method to convert string to url
fun stringToURL(urlString: String): URL? {
    try {
        return URL(urlString)
    } catch (e: MalformedURLException) {
        e.printStackTrace()
    }

    return null
}


fun Context.toast(message:String)=
    Toast.makeText(this,message, Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <Button
            android:text="Download Image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnDownload"
            android:layout_marginTop="8dp" 
            app:layout_constraintTop_toTopOf="parent" 
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent" 
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent" 
            app:layout_constraintHorizontal_bias="0.498"/>
    <Button
            android:text="Show Image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnShow"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/btnDownload"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"/>
    <ImageView
            android:layout_width="0dp"
            android:layout_height="0dp" 
            tools:srcCompat="@tools:sample/avatars"
            android:id="@+id/imageView"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" 
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintHorizontal_bias="0.0" 
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/textView" 
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"/>
    <TextView
            android:text="Image URI"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/btnShow" 
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
gradle dependencies

// WorkManager
def work_version = "1.0.0-beta02"
implementation "android.arch.work:work-runtime:$work_version"

android - MaterialCardView example

MainActivity.kt

package com.cfsuman.kotlintutorials

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#DCDCDC"
    android:id="@+id/rootLayout"
    android:padding="24dp">

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/card1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:cardCornerRadius="3dp"
        app:cardElevation="5dp"
        app:cardMaxElevation="8dp"
        app:contentPadding="40dp"
        app:cardPreventCornerOverlap="true"
        app:cardUseCompatPadding="false">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Material\nCard View"
            android:textSize="30sp"
            android:layout_gravity="center" />
    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/card2"
        app:strokeWidth="2dp"
        app:strokeColor="#ff3939"
        app:cardBackgroundColor="#cbd4c6"
        app:cardCornerRadius="3dp"
        app:contentPadding="40dp"
        app:cardPreventCornerOverlap="true"
        app:cardUseCompatPadding="false"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="48dp"
        app:layout_constraintTop_toBottomOf="@+id/card1"
        app:layout_constraintStart_toStartOf="parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Colored Material \nCard View"
            android:textSize="30sp"
            android:layout_gravity="center" />
    </com.google.android.material.card.MaterialCardView>

</androidx.constraintlayout.widget.ConstraintLayout>
build.gradle dependencies[add]

// Material components
implementation 'com.google.android.material:material:1.6.1'

android kotlin - ChipGroup multi selection example

MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.chip.Chip


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create an empty list
        val list = mutableListOf<String>()

        // Loop through the chips
        for (index in 0 until chipGroup.childCount) {
            val chip:Chip = chipGroup.getChildAt(index) as Chip

            // Set the chip checked change listener
            chip.setOnCheckedChangeListener{view, isChecked ->
                if (isChecked){
                    list.add(view.text.toString())
                }else{
                    list.remove(view.text.toString())
                }

                if (list.isNotEmpty()){
                    // SHow the selection
                    toast("Selected $list")
                }
            }
        }
    }
}

// Toast extension method
fun Context.toast(message:String)=
    Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.ChipGroup
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="32dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="8dp"
            android:background="#fafff7"
            app:singleLine="false"
            app:singleSelection="false">
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipRed"
                android:text="Red"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipGreen"
                android:text="Green"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipBlue"
                android:text="Blue"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipYellow"
                android:text="Yellow"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipPink"
                android:text="Pink"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipWhite"
                android:text="White"
                style="@style/Widget.MaterialComponents.Chip.Filter"
                app:checkedIconEnabled="true"
        />
    </com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'

android - Change chip text size, color and font

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.ChipGroup
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="32dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="8dp"
            app:singleLine="false"
            app:singleSelection="true">
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip1"
                android:text="Large Text"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:checkedIconEnabled="true"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip2"
                android:text="Medium Text"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:checkedIconEnabled="true"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip3"
                android:text="Default"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip4"
                android:text="Text Appearance"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:checkedIconEnabled="true"
                android:textAppearance="@style/chipTextAppearance"
                android:textColor="#ff0f0f"
        />
    </com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="chipTextAppearance" parent="TextAppearance.MaterialComponents.Chip">
        <item name="android:textSize">28sp</item>
        <item name="android:fontFamily">monospace</item>
        <item name="android:textStyle">italic|bold</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'

android - How to change chip selected color

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.ChipGroup
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="32dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="8dp"
            app:singleLine="true"
            app:singleSelection="true">
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip1"
                android:text="Android"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:chipBackgroundColor="@color/chip_bg_states"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip2"
                android:text="ColdFusion"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:chipBackgroundColor="@color/chip_bg_states"
                app:checkedIconEnabled="true"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chip3"
                android:text="PHP"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                app:chipBackgroundColor="@color/chip_bg_states"
                app:checkedIconEnabled="true"
        />
    </com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
res/color/chip_bg_states.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
        xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:color="#07b438"
            android:state_checked="true"/>
    <item android:color="#ff3798"/>
</selector>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'

android kotlin - Create chip programmatically

MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.transition.TransitionManager
import android.widget.Toast
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.chip.Chip
import kotlin.random.Random


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // set the button click listener
        button.setOnClickListener{
            // Initialize a new chip instance
            val chip = Chip(this)
            chip.text = "Random ${Random.nextInt(100)}"

            // Set the chip icon
            chip.chipIcon = ContextCompat.getDrawable(this,R.drawable.ic_action_android)
            //chip.setChipIconTintResource(R.color.abc_search_url_text)

            // Make the chip clickable
            chip.isClickable = true
            chip.isCheckable = false

            // Show the chip icon in chip
            chip.isCloseIconVisible = true

            // Set the chip click listener
            chip.setOnClickListener{
                toast("Clicked: ${chip.text}")
            }

            // Set chip close icon click listener
            chip.setOnCloseIconClickListener{
                // Smoothly remove chip from chip group
                TransitionManager.beginDelayedTransition(chipGroup)
                chipGroup.removeView(chip)
            }

            // Finally, add the chip to chip group
            chipGroup.addView(chip)
        }
     }
}

// Toast extension method
fun Context.toast(message:String)=
    Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.ChipGroup
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="32dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="8dp"
            app:singleLine="true"
            app:singleSelection="true"/>
    <Button
            android:text="Add New Chip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            app:layout_constraintTop_toBottomOf="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            android:layout_marginTop="32dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'

android kotlin - Chip group example

MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.chip.Chip


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Set chip group checked change listener
        chipGroup.setOnCheckedChangeListener{group,checkedId:Int ->
            // Get the checked chip instance from chip group
            val chip:Chip? = findViewById(checkedId)

            chip?.let {
                // Show the checked chip text on toast message
                toast("${it.text} checked")
            }
        }
    }
}

// Toast extension method
fun Context.toast(message:String)=
    Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.ChipGroup
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chipGroup"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="32dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="8dp"
            android:background="#fafff7"
            app:singleLine="true"
            app:singleSelection="true"
    >
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipRed"
                android:text="Red"
                style="@style/Widget.MaterialComponents.Chip.Choice"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipGreen"
                android:text="Green"
                style="@style/Widget.MaterialComponents.Chip.Choice"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipBlue"
                android:text="Blue"
                style="@style/Widget.MaterialComponents.Chip.Choice"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipYellow"
                android:text="Yellow"
                style="@style/Widget.MaterialComponents.Chip.Choice"
        />
        <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/chipPink"
                android:text="Pink"
                style="@style/Widget.MaterialComponents.Chip.Choice"
        />
    </com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'

android kotlin - Material chip example

MainActivity.kt

package com.cfsuman.jetpack

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Set chip click listener
        chip1.setOnClickListener{
            toast("chip clicked")
        }

        // Set chip checked change listener
        chip2.setOnCheckedChangeListener { view, isChecked ->
            // Handle the toggle.
            if (isChecked){
                toast("chip checked")
            }else{
                toast("chip unchecked")
            }
        }


        // Chip close icon click listener
        chip2.setOnCloseIconClickListener{
            toast("chip close icon clicked")
        }
    }
}

// Toast extension method
fun Context.toast(message:String)=
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootLayout"
        tools:context=".MainActivity"
        android:background="#fdfdfc">
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/chip1"
            android:text="Simple Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/chip2"
            android:text="Entry Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip1"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            style="@style/Widget.MaterialComponents.Chip.Entry"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:id="@+id/chip3"
            android:text="Icon Entry Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip2"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            style="@style/Widget.MaterialComponents.Chip.Entry"
            app:chipIcon="@drawable/ic_action_rowing"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/chip4"
            android:text="Filter Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip3"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            style="@style/Widget.MaterialComponents.Chip.Filter"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:id="@+id/chip5"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip4"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="Icon Close Filter Chip"
            style="@style/Widget.MaterialComponents.Chip.Filter"
            app:chipIcon="@drawable/ic_action_android"
            app:closeIconEnabled="true"
            app:chipIconEnabled="true"
            app:checkedIconEnabled="false"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            android:id="@+id/chip6"
            android:text="Choice Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip5"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            style="@style/Widget.MaterialComponents.Chip.Choice"
    />
    <com.google.android.material.chip.Chip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/chip7"
            android:text="Action Chip"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/chip6"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            style="@style/Widget.MaterialComponents.Chip.Action"
    />
</androidx.constraintlayout.widget.ConstraintLayout>
res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
gradle dependencies

// Material components theme
implementation 'com.google.android.material:material:1.0.0'