Question

How to Download a File in Android and Show Progress in a ProgressDialog

javaandroiddownloadandroid-asynctask

Question

I am building a simple Android application that needs to download an update file. I want to create a function that downloads a file and shows the current download progress in a ProgressDialog.

I already know how to create and display a ProgressDialog, but I am not sure how to:

  • download the file in Android, and
  • update the dialog with the current download progress while the file is being downloaded.

What is a simple way to do this?

A simplified version of the goal would look like this:

// Start a file download
// Show a ProgressDialog
// Update progress as bytes are received
// Save the file locally

Short Answer

By the end of this page, you will understand how file downloading works in Android, how progress is calculated from bytes read, and how to update a ProgressDialog during a download. You will also learn the common structure: open a connection, read the response stream in chunks, write to a file, calculate progress, and update the UI safely.

Concept

In Android, downloading a file usually means reading data from a network stream and writing it to a local file a little at a time.

The important idea is that a file is not usually downloaded all at once. Instead, your app reads it in small chunks such as 1 KB, 4 KB, or 8 KB at a time. After each chunk:

  • you add the number of bytes read to a running total,
  • you write those bytes to a file,
  • and you calculate the progress percentage.

A common progress formula is:

progress = (int) ((downloadedBytes * 100L) / totalBytes);

This only works when the server provides the total file size, usually through the Content-Length header.

Why this matters:

  • Users need feedback during long downloads.
  • Large downloads should not block the main UI thread.
  • Real apps often download images, PDFs, updates, backups, or cached API data.

In older Android code, developers often used AsyncTask with ProgressDialog. That pattern is easy to learn and explains the concept clearly. In modern Android, ProgressDialog is deprecated, and developers usually use a ProgressBar with a background worker such as WorkManager, coroutines, or another async approach. But the core download logic is the same: read chunks, write chunks, update progress.

Mental Model

Imagine filling a bucket with a measuring cup.

  • The bucket is the full file.
  • The measuring cup is one chunk of bytes read from the network.
  • Each time you pour a cup into the bucket, you know how much more has been filled.
  • If you know the bucket's total size, you can calculate a percentage.

So downloading a file is just repeating this process:

  1. take a chunk from the network,
  2. add it to the saved file,
  3. update how full the file is,
  4. repeat until done.

The progress dialog is simply the visual label that says, "The bucket is 37% full."

Take Quiz

Syntax and Examples

The classic beginner-friendly approach in Android Java is:

  1. open a URL connection,
  2. get the file size,
  3. read from an InputStream,
  4. write to a FileOutputStream,
  5. publish progress to the UI.

Here is a simple example using AsyncTask:

private class DownloadFileTask extends AsyncTask<String, Integer, String> {
    private ProgressDialog progressDialog;

    @Override
    protected void onPreExecute() {
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("Downloading");
        progressDialog.setMessage("Please wait...");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMax(100);
        progressDialog.setProgress(0);
        progressDialog.setCancelable(false);
        progressDialog.show();
    }

    @Override
    protected String doInBackground(String... urls) {
        String fileUrl = urls[0];
        String outputPath = urls[1];

        InputStream input = null;
        FileOutputStream output = null;
        HttpURLConnection connection = null;

        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();

            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return "Server returned HTTP " + connection.getResponseCode()
                        + " " + connection.getResponseMessage();
            }

            int fileLength = connection.getContentLength();

            input = connection.getInputStream();
            output = new FileOutputStream(outputPath);

            byte[] data = new byte[4096];
            long total = 0;
            int count;

            while ((count = input.read(data)) != -1) {
                total += count;

                if (fileLength > 0) {
                    int progress = (int) (total * 100 / fileLength);
                    publishProgress(progress);
                }

                output.write(data, 0, count);
            }

            return null;
        } catch (Exception e) {
            return e.toString();
        } finally {
            try {
                if (output != null) {
                    output.close();
                }
                if (input != null) {
                    input.close();
                }
            } catch (IOException ignored) {
            }

            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setProgress(values[0]);
    }

    @Override
    protected void onPostExecute(String result) {
        progressDialog.dismiss();

        if (result != null) {
            Toast.makeText(MainActivity.this, "Download error: " + result, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(MainActivity.this, "Download complete", Toast.LENGTH_SHORT).show();
        }
    }
}

You might call it like this:

new DownloadFileTask().execute(
    "https://example.com/app-update.apk",
    getExternalFilesDir(null) + "/app-update.apk"
);

What this example teaches:

  • doInBackground() runs the download work away from the UI thread.
  • publishProgress() sends progress values to onProgressUpdate().
  • onProgressUpdate() updates the dialog.
  • onPostExecute() runs when the download finishes.

Step by Step Execution

Consider this smaller example:

URL url = new URL("https://example.com/file.zip");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();

int fileLength = connection.getContentLength();
InputStream input = connection.getInputStream();
FileOutputStream output = new FileOutputStream("/storage/emulated/0/Download/file.zip");

byte[] buffer = new byte[4];
int count;
long total = 0;

while ((count = input.read(buffer)) != -1) {
    total += count;
    output.write(buffer, 0, count);

    int progress = (int) (total * 100 / fileLength);
    publishProgress(progress);
}

Let’s walk through it:

  1. new URL(...)

    • Creates a URL object for the file location.
  2. openConnection()

    • Opens a network connection to the server.
  3. connect()

    • Actually starts the request.
  4. getContentLength()

    • Gets the total file size in bytes if the server provides it.
  5. getInputStream()

    • Gives you a stream of incoming file data.
  6. new FileOutputStream(...)

    • Opens a local file where bytes will be saved.
  7. byte[] buffer = new byte[4]

    • Creates temporary storage for each chunk read.
    • In real apps, this is usually larger, like 4096 or 8192 bytes.
  8. input.read(buffer)

    • Reads up to 4 bytes from the network.
    • Returns how many bytes were actually read.
    • Returns -1 when there is no more data.
  9. total += count

    • Adds the number of new bytes to the total downloaded.
  10. output.write(buffer, 0, count)

    • Saves those bytes to the file.
  11. progress = (int) (total * 100 / fileLength)

    • Converts the downloaded amount into a percentage.
  12. publishProgress(progress)

    • Sends that percentage to the UI update method.

If fileLength were 1000 bytes, and after a few loops total became 250, then:

progress = (250 * 100) / 1000; // 25

So the dialog would show 25%.

Real World Use Cases

This pattern appears in many Android apps:

  • App updates
    • Downloading an APK or update package.
  • Document downloads
    • Saving PDFs, invoices, reports, or spreadsheets.
  • Media downloads
    • Downloading audio, video, or image files for offline use.
  • Offline caching
    • Storing map data, configuration files, or product catalogs.
  • Backup and restore tools
    • Downloading exported user data from cloud storage.
  • Enterprise apps
    • Pulling policy files, databases, or secure attachments.

In all of these cases, users benefit from knowing:

  • that the app is still working,
  • how much is left,
  • and whether the download succeeded or failed.
Take Quiz

Real Codebase Usage

In real Android projects, developers usually combine file download logic with a few common patterns.

Validation before download

Before downloading, code often checks:

  • whether the URL is valid,
  • whether storage is available,
  • whether the app has needed permissions,
  • whether the device has internet access.

Example guard clause:

if (downloadUrl == null || downloadUrl.isEmpty()) {
    return;
}

Early return on HTTP errors

A real codebase usually stops immediately if the response is not successful.

if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
    return "Download failed";
}

Progress only when size is known

Some servers do not send Content-Length. In that case, developers may:

  • show an indeterminate spinner,
  • show downloaded bytes instead of percent,
  • or skip percentage updates.

Resource cleanup

Streams and connections must always be closed, even on failure.

This avoids:

  • leaked file handles,
  • corrupted files,
  • wasted memory and network resources.

Writing to app-specific storage

Instead of writing anywhere on the device, many apps write to app-controlled folders such as:

File file = new File(getExternalFilesDir(null), "update.apk");

This is simpler and safer than using unrestricted shared storage.

Error handling and user feedback

Typical projects report:

  • network timeout,
  • no internet,
  • server error,
  • insufficient storage,
  • canceled download.

Modern note

In newer Android apps, developers usually avoid ProgressDialog because it is deprecated. Instead, they often use:

  • ProgressBar in the layout,
  • notifications for background downloads,
  • WorkManager,
  • coroutines or other async tools.

But the byte-reading and progress-calculation logic is still the same.

Common Mistakes

Here are some common beginner mistakes when implementing downloads in Android.

1. Doing network work on the main thread

Broken example:

URL url = new URL("https://example.com/file.zip");
InputStream input = url.openStream();

If this runs on the UI thread, Android may throw a NetworkOnMainThreadException.

How to avoid it:

  • Use AsyncTask in older learning examples.
  • In modern apps, use a background worker or coroutine.

2. Updating the UI directly from background code

Broken example:

progressDialog.setProgress(progress);

inside doInBackground().

Why it is wrong:

  • UI updates should happen on the main thread.

How to avoid it:

  • Use publishProgress() and update in onProgressUpdate().

3. Ignoring count when writing bytes

Broken example:

output.write(buffer);

Why it is wrong:

  • The buffer may not be completely full on the last read.
  • You should only write the actual number of bytes read.

Correct version:

output.write(buffer, 0, count);

4. Dividing by an unknown file size

Broken example:

int progress = (int) (total * 100 / fileLength);

If fileLength is -1 or 0, the result is invalid.

How to avoid it:

if (fileLength > 0) {
    int progress = (int) (total * 100 / fileLength);
    publishProgress(progress);
}

5. Forgetting to close streams

Broken example:

InputStream input = connection.getInputStream();
FileOutputStream output = new FileOutputStream(path);
// no close calls

Why it is bad:

  • It can leak resources.
  • The file may not be fully saved.

How to avoid it:

  • Close streams in finally.

6. Assuming progress is always available

Some servers do not provide content length.

How to avoid it:

  • Handle the case where percentage cannot be computed.
  • Use an indeterminate progress indicator if needed.

7. Saving to an invalid path

Make sure the destination file path exists and is writable.

Safer example:

File outputFile = new File(getExternalFilesDir(null), "download.bin");

Comparisons

ConceptBest forStrengthsLimitations
ProgressDialogOlder Android examplesSimple to understandDeprecated in modern Android
ProgressBar in layoutModern screensFlexible and recommendedRequires screen UI setup
AsyncTaskLearning old Android patternsEasy progress callback methodsDeprecated in modern Android
Thread + HandlerSimple custom async workMore controlMore manual code
DownloadManagerSystem-managed downloadsHandles background downloads wellLess custom control over stream handling
HttpURLConnectionManual download logicFull control over reading and progressMore code to manage

Another useful comparison:

ApproachProgress controlFile handling controlTypical use
Manual stream download with HttpURLConnectionHighHighCustom app logic
DownloadManagerMediumLowerLarge or long-running downloads

If your main goal is to learn how progress works, manual downloading with HttpURLConnection is excellent because you can see every step.

If your goal is to ship a robust download feature quickly, DownloadManager is often easier for Android apps.

Take Quiz

Cheat Sheet

// Core formula
int progress = (int) ((downloadedBytes * 100L) / totalBytes);
// Basic download loop
byte[] buffer = new byte[4096];
int count;
long total = 0;

while ((count = input.read(buffer)) != -1) {
    total += count;
    output.write(buffer, 0, count);
}
// AsyncTask progress flow
publishProgress(progress);   // from doInBackground()
onProgressUpdate(...);       // updates UI

Rules to remember

  • Do network work off the main thread.
  • Read the file in chunks.
  • Add each chunk size to a running total.
  • Only compute percentage if total file size is known.
  • Update UI on the main thread.
  • Always close streams and disconnect connections.
  • Use output.write(buffer, 0, count) instead of writing the whole buffer.

Common classes

  • URL
  • HttpURLConnection
  • InputStream
  • FileOutputStream
  • AsyncTask
  • ProgressDialog

Edge cases

  • getContentLength() may return -1
  • network may fail halfway through
  • file path may be invalid
  • server may return 404 or 500
  • storage may be full

FAQ

How do I show file download progress in Android?

Read the file in small chunks, keep a running total of bytes downloaded, calculate a percentage using the total file size, and update the UI with that value.

Can I update a ProgressDialog from doInBackground()?

No. UI updates should not happen directly from background code. With AsyncTask, use publishProgress() and update the dialog in onProgressUpdate().

What if the server does not send the file size?

Then you usually cannot calculate a percentage. You can show an indeterminate loading indicator instead.

Is ProgressDialog still recommended in Android?

No. ProgressDialog is deprecated. It is still useful for understanding the concept, but modern apps usually use a ProgressBar or system notifications.

What class can I use to download files in Android?

For manual control, developers often use HttpURLConnection. For system-managed downloads, DownloadManager is another good option.

Why does Android reject network calls on the main thread?

Because network operations can be slow and would freeze the UI. Android requires long-running work to happen in the background.

How do I save the downloaded file locally?

Create a FileOutputStream pointing to a writable file path, then write each chunk of bytes as it arrives.

What is the easiest way to calculate progress percentage?

Use:

int progress = (int) ((downloadedBytes * 100L) / totalBytes);

Make sure totalBytes > 0 first.

Take Quiz

Mini Project

Description

Build a small Android feature that downloads a file from a URL and shows the progress as a percentage while saving the file locally. This project demonstrates the full download flow: opening a network connection, reading data in chunks, updating progress, handling errors, and writing the result to storage.

Goal

Create a working downloader that saves a file to app storage and updates a ProgressDialog from 0 to 100%.

Requirements

  • Create a background task that accepts a file URL and destination path.
  • Show a horizontal ProgressDialog before the download starts.
  • Download the file in chunks and save it locally.
  • Update the progress dialog as bytes are received.
  • Show a success or error message when the download finishes.
Take Quiz

Keep learning

Related questions

View more