Update ProgressBar in Kotlin XML: A Complete Guide

Progress bars are essential UI components in Android applications, providing users with visual feedback about the status of an ongoing operation. In Kotlin-based Android XML development, updating a progress bar programmatically is a common requirement. This post will delve into how to effectively update a progress bar programmatically using Kotlin within the context of XML layouts.

Why Use Progress Bars?

Progress bars enhance user experience by:

  • Indicating that the application is actively working.
  • Reducing user uncertainty and perceived waiting time.
  • Providing a visual representation of task completion status.

Types of Progress Bars

Android offers two primary types of progress bars:

  • Determinate Progress Bar: Shows the actual progress of a task (e.g., file download).
  • Indeterminate Progress Bar: Indicates that a task is in progress without specifying how much has been completed (e.g., loading screen).

Setting Up Your Project

Before diving into the code, make sure your Android project is set up with Kotlin support. If you’re starting from scratch, create a new Android project and ensure Kotlin is configured.

Step 1: Add the Progress Bar to Your XML Layout

First, you need to add a ProgressBar to your XML layout file (e.g., activity_main.xml). Here’s how you can add both types:

Determinate Progress Bar:


<ProgressBar
    android:id="@+id/determinateProgressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="100"
    android:progress="0" />

Indeterminate Progress Bar:


<ProgressBar
    android:id="@+id/indeterminateProgressBar"
    style="?android:attr/progressBarStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:indeterminate="true" />

In these XML snippets:

  • android:id sets a unique ID for the progress bar.
  • style defines the visual style of the progress bar.
  • For the determinate progress bar:
    • android:max sets the maximum value (usually 100).
    • android:progress sets the initial progress value (usually 0).
  • For the indeterminate progress bar:
    • android:indeterminate="true" makes the progress bar animate indefinitely.

Step 2: Access the Progress Bar in Your Kotlin Activity

In your Kotlin activity (e.g., MainActivity.kt), you need to access the progress bar using its ID.


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

class MainActivity : AppCompatActivity() {
    private lateinit var determinateProgressBar: ProgressBar
    private lateinit var indeterminateProgressBar: ProgressBar

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

        // Initialize the progress bars
        determinateProgressBar = findViewById(R.id.determinateProgressBar)
        indeterminateProgressBar = findViewById(R.id.indeterminateProgressBar)

        // Initially hide the determinate progress bar
        determinateProgressBar.visibility = android.view.View.GONE
    }
}

In this code:

  • findViewById() is used to get a reference to the progress bars from the XML layout.
  • lateinit var is used to declare the progress bars, ensuring they are initialized before being used.
  • The determinate progress bar is initially hidden.

Step 3: Update the Determinate Progress Bar Programmatically

To update the determinate progress bar, you need to change its progress value based on the ongoing task. For example, simulating a download:


import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Button
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var determinateProgressBar: ProgressBar
    private lateinit var indeterminateProgressBar: ProgressBar
    private lateinit var startButton: Button

    private val handler = Handler(Looper.getMainLooper())

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

        // Initialize views
        determinateProgressBar = findViewById(R.id.determinateProgressBar)
        indeterminateProgressBar = findViewById(R.id.indeterminateProgressBar)
        startButton = findViewById(R.id.startButton)

        // Initially hide the determinate progress bar
        determinateProgressBar.visibility = android.view.View.GONE

        startButton.setOnClickListener {
            startDownload()
        }
    }

    private fun startDownload() {
        // Show the determinate progress bar and reset progress
        determinateProgressBar.visibility = android.view.View.VISIBLE
        determinateProgressBar.progress = 0

        // Hide the indeterminate progress bar
        indeterminateProgressBar.visibility = android.view.View.GONE

        // Simulate download progress
        Thread {
            for (i in 1..100) {
                Thread.sleep(50) // Simulate some work

                // Update the progress bar on the main thread
                handler.post {
                    determinateProgressBar.progress = i
                    if (i == 100) {
                        // Hide the determinate progress bar after completion
                        determinateProgressBar.visibility = android.view.View.GONE
                    }
                }
            }
        }.start()
    }
}

<?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"
    tools:context=".MainActivity">

    <ProgressBar
        android:id="@+id/determinateProgressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:max="100"
        android:progress="0"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <ProgressBar
        android:id="@+id/indeterminateProgressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toBottomOf="@id/determinateProgressBar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

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

</androidx.constraintlayout.widget.ConstraintLayout>

In this code:

  • A startButton is added to initiate the download simulation.
  • startDownload() is called when the button is clicked.
  • determinateProgressBar.visibility is set to View.VISIBLE at the start and View.GONE after completion.
  • determinateProgressBar.progress is updated within a loop.
  • A Handler is used to update the UI from a background thread.

Step 4: Show or Hide the Indeterminate Progress Bar Programmatically

The indeterminate progress bar is typically used for tasks where the progress cannot be determined. To show or hide it:


import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Button
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var determinateProgressBar: ProgressBar
    private lateinit var indeterminateProgressBar: ProgressBar
    private lateinit var startButton: Button

    private val handler = Handler(Looper.getMainLooper())

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

        // Initialize views
        determinateProgressBar = findViewById(R.id.determinateProgressBar)
        indeterminateProgressBar = findViewById(R.id.indeterminateProgressBar)
        startButton = findViewById(R.id.startButton)

        // Initially hide the determinate progress bar
        determinateProgressBar.visibility = android.view.View.GONE

        startButton.setOnClickListener {
            startLoadingData()
        }
    }

    private fun startLoadingData() {
        // Show the indeterminate progress bar
        indeterminateProgressBar.visibility = android.view.View.VISIBLE

        // Simulate loading data
        Thread {
            Thread.sleep(3000) // Simulate 3 seconds of loading

            // Update UI on the main thread
            handler.post {
                // Hide the indeterminate progress bar after loading is complete
                indeterminateProgressBar.visibility = android.view.View.GONE
            }
        }.start()
    }
}

<?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"
    tools:context=".MainActivity">

    <ProgressBar
        android:id="@+id/determinateProgressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:max="100"
        android:progress="0"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <ProgressBar
        android:id="@+id/indeterminateProgressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toBottomOf="@id/determinateProgressBar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

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

</androidx.constraintlayout.widget.ConstraintLayout>

In this code:

  • indeterminateProgressBar.visibility is set to View.VISIBLE at the start of loading.
  • After a simulated loading period, indeterminateProgressBar.visibility is set to View.GONE.

Best Practices

  • Update on the Main Thread: Always update UI components, including progress bars, on the main thread to avoid NetworkOnMainThreadException.
  • Use Handlers or Coroutines: To perform background tasks and update progress bars, use Handler or Kotlin Coroutines to avoid blocking the main thread.
  • Provide Meaningful Feedback: Ensure progress bars accurately reflect the status of ongoing tasks to provide useful information to the user.
  • Optimize Performance: Avoid frequent UI updates, which can impact performance. Update progress bars at reasonable intervals.

Conclusion

Updating progress bars programmatically in Kotlin Android XML development is crucial for providing effective user feedback. By following these steps and best practices, you can implement both determinate and indeterminate progress bars to enhance the user experience in your applications. Proper implementation ensures that users are informed about ongoing processes, leading to a more satisfying app interaction.