Android Kotlin: How to create a secondary progress bar

Introduction

This code demonstrates the creation of a secondary progress bar in an Android application written in Kotlin. A secondary progress bar provides a visual indication of a sub-task within a larger task. The example showcases how to update both the primary and secondary progress bars while performing a background operation using a separate thread.

Breakdown

The code consists of two main parts: the MainActivity.kt file containing the Activity code and the activity_main.xml file defining the layout.

MainActivity.kt:

  1. Variables: The class defines variables for the progress status (primary), secondary progress status, and a Handler object for updating the UI thread.
  2. onCreate: This method sets up the layout and retrieves references to the UI elements (button, text views, progress bar).
  3. Button Click Listener: Clicking the button triggers the following actions:
    • Disables the button to prevent multiple clicks.
    • Uses TransitionManager for a smooth animation when showing the progress bar.
    • Sets the progress and secondary progress to zero, essentially resetting them.
    • Starts a new thread to handle the progress updates.

Thread:

  • The thread loop iterates until the primary progress reaches 100.
    • Inside the loop, there's a nested loop that increases the secondary progress until it reaches 100.
    • The Handler.post method is used to update the UI thread safely from within the background thread.
    • The secondary progress and its corresponding text view are updated within the post block.
    • Once the secondary progress reaches 100, it's reset to zero, and the primary progress is incremented. The progress bar and text view for the primary progress are also updated.
    • The button is re-enabled when the primary progress reaches 100, signifying task completion.

activity_main.xml:

This file defines the layout for the activity:

  • A ConstraintLayout as the root layout.
  • A TextView displaying the primary task progress.
  • A horizontal ProgressBar with styling for the secondary progress.
  • Another TextView displaying the secondary task progress update.
  • A Button to initiate the task.

Summary

This example effectively demonstrates how to create and utilize a secondary progress bar in Android using Kotlin. It showcases background thread usage for progress updates and safe UI manipulation from within the thread. The code includes visual cues (animations) and informative text updates to enhance the user experience.


MainActivity.kt

package com.cfsuman.kotlintutorials

import android.app.Activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.transition.TransitionManager


class MainActivity : Activity() {
    var progressStatus = 0
    var secondaryProgressStatus = 0
    var handler = Handler(Looper.getMainLooper())

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

        // Get the widgets reference from XML layout
        val rootLayout = findViewById<ConstraintLayout>(R.id.rootLayout)
        val button = findViewById<Button>(R.id.button)
        val textView = findViewById<TextView>(R.id.textView)
        val tvSecondary = findViewById<TextView>(R.id.tvSecondary)
        val progressBar = findViewById<ProgressBar>(R.id.progressBar)


        // button click listener
        button.setOnClickListener {
            button.isEnabled = false
            TransitionManager.beginDelayedTransition(rootLayout)
            progressBar.visibility = View.VISIBLE

            // set up progress bar on initial stage
            progressBar.progress = 0
            progressStatus = 0

            Thread {
                while (progressStatus < 100) {

                    while (secondaryProgressStatus < 100) {
                        secondaryProgressStatus += 1
                        Thread.sleep(5)

                        handler.post {
                            progressBar.secondaryProgress =
                                secondaryProgressStatus

                            if (secondaryProgressStatus == 100) {
                                tvSecondary.text = "Secondary task finished."
                                secondaryProgressStatus = 0

                                progressStatus += 1
                                progressBar.progress = progressStatus
                                textView.text = "Task finished" +
                                        " $progressStatus of 100"

                                if (progressStatus == 100) {
                                    button.isEnabled = true
                                }
                            } else {
                                tvSecondary.text = "Secondary task " +
                                        "$secondaryProgressStatus of 100"
                            }
                        }
                    }
                }
            }.start()
        }
    }
}
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:id="@+id/rootLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="24dp"
    android:background="#DCDCDC">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:padding="12dp"
        android:fontFamily="sans-serif"
        android:textSize="20sp"
        android:textStyle="bold"
        tools:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:visibility="invisible"
        android:secondaryProgressTint="#0018A8"
        android:secondaryProgressTintMode="src_in"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/tvSecondary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:fontFamily="sans-serif"
        android:padding="12dp"
        android:textSize="18sp"
        android:textStyle="italic"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.491"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar"
        tools:text="Secondary Task" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Start Task"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvSecondary" />

</androidx.constraintlayout.widget.ConstraintLayout>