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[];
urls[];
;
;
;
{
(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
(connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ connection.getResponseCode()
+ + connection.getResponseMessage();
}
connection.getContentLength();
input = connection.getInputStream();
output = (outputPath);
[] data = [];
;
count;
((count = input.read(data)) != -) {
total += count;
(fileLength > ) {
() (total * / fileLength);
publishProgress(progress);
}
output.write(data, , count);
}
;
} (Exception e) {
e.toString();
} {
{
(output != ) {
output.close();
}
(input != ) {
input.close();
}
} (IOException ignored) {
}
(connection != ) {
connection.disconnect();
}
}
}
{
progressDialog.setProgress(values[]);
}
{
progressDialog.dismiss();
(result != ) {
Toast.makeText(MainActivity., + result, Toast.LENGTH_LONG).show();
} {
Toast.makeText(MainActivity., , Toast.LENGTH_SHORT).show();
}
}
}
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:
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
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 when writing bytes
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 |
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
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.
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
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.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.