Question
How to Download a File in Android and Show Progress in a ProgressDialog
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:
- take a chunk from the network,
- add it to the saved file,
- update how full the file is,
- repeat until done.
The progress dialog is simply the visual label that says, "The bucket is 37% full."
Syntax and Examples
The classic beginner-friendly approach in Android Java is:
- open a URL connection,
- get the file size,
- read from an
InputStream, - write to a
FileOutputStream, - 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 toonProgressUpdate().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:
-
new URL(...)- Creates a URL object for the file location.
-
openConnection()- Opens a network connection to the server.
-
connect()- Actually starts the request.
-
getContentLength()- Gets the total file size in bytes if the server provides it.
-
getInputStream()- Gives you a stream of incoming file data.
-
new FileOutputStream(...)- Opens a local file where bytes will be saved.
-
byte[] buffer = new byte[4]- Creates temporary storage for each chunk read.
- In real apps, this is usually larger, like 4096 or 8192 bytes.
-
input.read(buffer)- Reads up to 4 bytes from the network.
- Returns how many bytes were actually read.
- Returns
-1when there is no more data.
-
total += count- Adds the number of new bytes to the total downloaded.
-
output.write(buffer, 0, count)- Saves those bytes to the file.
-
progress = (int) (total * 100 / fileLength)- Converts the downloaded amount into a percentage.
-
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.
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:
ProgressBarin 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
AsyncTaskin 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 inonProgressUpdate().
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
| Concept | Best for | Strengths | Limitations |
|---|---|---|---|
ProgressDialog | Older Android examples | Simple to understand | Deprecated in modern Android |
ProgressBar in layout | Modern screens | Flexible and recommended | Requires screen UI setup |
AsyncTask | Learning old Android patterns | Easy progress callback methods | Deprecated in modern Android |
Thread + Handler | Simple custom async work | More control | More manual code |
DownloadManager | System-managed downloads | Handles background downloads well | Less custom control over stream handling |
HttpURLConnection | Manual download logic | Full control over reading and progress | More code to manage |
Another useful comparison:
| Approach | Progress control | File handling control | Typical use |
|---|---|---|---|
Manual stream download with HttpURLConnection | High | High | Custom app logic |
DownloadManager | Medium | Lower | Large 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.
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
URLHttpURLConnectionInputStreamFileOutputStreamAsyncTaskProgressDialog
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.
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
ProgressDialogbefore 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.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.