Android Kotlin: Horizontal ProgressBar programmatically

Android Kotlin: Programmatically Creating a Horizontal Progress Bar

This code demonstrates how to create and manage a horizontal progress bar programmatically in an Android app written with Kotlin. It achieves this by inflating a basic layout containing a Button and a TextView, then dynamically adding and configuring a progress bar within the Activity code.

The code utilizes ConstraintLayout to manage the positioning of the progress bar relative to the existing UI elements. It also simulates a download process by updating the progress bar and TextView on a separate thread.

Breakdown and Summary

The code is divided into two main parts: the activity_main.xml layout file and the MainActivity.kt class.

  • activity_main.xml: This file defines a simple layout with a Button and a TextView.

  • MainActivity.kt: This class handles the core functionality of the app. Here's a breakdown of the key steps:

    1. Variable Initialization: It initializes variables for progress status, a handler for UI updates, and references to UI elements obtained from the layout.
    2. Progress Bar Creation: A horizontal progress bar is programmatically created, assigned a unique ID, sized with MATCH_PARENT width and WRAP_CONTENT height, and added to the ConstraintLayout.
    3. Constraint Layout Configuration: A ConstraintSet object is used to define the positioning of the progress bar relative to the TextView. It's placed below the TextView with a margin and centered horizontally within the layout.
    4. Button Click Listener: The Button click listener is set. Clicking the button initiates the download simulation:
      • Disables the button to prevent multiple clicks.
      • Resets progress and progress status.
      • Generates a random number of files to download (simulating download size).
      • Sets the maximum value of the progress bar based on the generated file count.
      • Starts a separate thread to simulate the download process:
        • The thread iteratively increments the progress status.
        • The UI thread's handler is used to update the progress bar value, TextView text (showing downloaded files and percentage), and finally re-enables the button when the download is complete.

This code demonstrates a practical approach to creating and managing a progress bar for download simulations or any other operations requiring visual progress indication within an Android app.


MainActivity.kt

package com.cfsuman.kotlintutorials

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams
import androidx.constraintlayout.widget.ConstraintSet
import kotlin.random.Random


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

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

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


        // Create a horizontal progress bar programmatically
        val progressBar = ProgressBar(
            this,
            null,
            android.R.attr.progressBarStyleHorizontal
        )

        // Generate a view id for the progress bar
        progressBar.id = View.generateViewId()

        // Progress bar width and height
        val params = LayoutParams(
            LayoutParams.MATCH_PARENT,
            LayoutParams.WRAP_CONTENT
        )
        progressBar.layoutParams = params

        // Add the progress bar to constraint layout
        rootLayout.addView(progressBar)

        // Initialize a new constraint set
        val constraintSet = ConstraintSet()
        constraintSet.clone(rootLayout)

        // Put the progress bar bottom of text view
        constraintSet.connect(
            // Connect progress bar top to text view's bottom
            progressBar.id,
            ConstraintSet.TOP,
            R.id.textView,
            ConstraintSet.BOTTOM,
            24.toDp(this) // Margin
        )

        // Start constraint with margin
        constraintSet.connect(
            progressBar.id,
            ConstraintSet.START,
            R.id.rootLayout,
            ConstraintSet.START,
            16.toDp(this) // Margin
        )

        // End constraint with margin
        constraintSet.connect(
            progressBar.id,
            ConstraintSet.END,
            R.id.rootLayout,
            ConstraintSet.END,
            16.toDp(this)
        )

        // Finally, apply the constraint to constraint layout
        constraintSet.applyTo(rootLayout)


        // Set the button click listener
        button.setOnClickListener {
            button.isEnabled = false

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

            // Generate random number of files to download
            val filesToDownload= Random.nextInt(10,200)

            // Set up max value for progress bar
            progressBar.max = filesToDownload

            Thread {
                while (progressStatus < filesToDownload) {
                    // Update progress status
                    progressStatus += 1

                    // Sleep the thread for 100 milliseconds
                    Thread.sleep(100)

                    // Update the progress bar
                    handler.post {
                        progressBar.progress = progressStatus

                        // Calculate the percentage
                        var percentage = ((progressStatus.toDouble()
                                / filesToDownload) * 100).toInt()

                        // Update the text view
                        textView.text = "Downloaded $progressStatus of " +
                                "$filesToDownload files ($percentage%)"

                        if (progressStatus == filesToDownload) {
                            button.isEnabled = true
                        }
                    }
                }
            }.start()
        }
    }
}



// Extension method to convert values to dp
fun Int.toDp(context: Context):Int = TypedValue.applyDimension(
    TypedValue.COMPLEX_UNIT_DIP,
    this.toFloat(),
    context.resources.displayMetrics
).toInt()
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">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Task"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

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

</androidx.constraintlayout.widget.ConstraintLayout>