Show Download progress inside activity using DownloadManager

AndroidProgress Bar

Android Problem Overview


I'm trying to reproduce the same progress that DownloadManager shows in Notification bar inside my app, but my progress never is published. I'm trying to update it using runOnUiThread(), but for some reason it's not been updated.

my download:

String urlDownload = "https://dl.dropbox.com/s/ex4clsfmiu142dy/test.zip?token_hash=AAGD-XcBL8C3flflkmxjbzdr7_2W_i6CZ_3rM5zQpUCYaw&dl=1";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(urlDownload));

request.setDescription("Testando");
request.setTitle("Download");
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "teste.zip");

final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);

final long downloadId = manager.enqueue(request);

final ProgressBar mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);

new Thread(new Runnable() {

	@Override
	public void run() {

		boolean downloading = true;

		while (downloading) {

			DownloadManager.Query q = new DownloadManager.Query();
			q.setFilterById(downloadId);

			Cursor cursor = manager.query(q);
			cursor.moveToFirst();
			int bytes_downloaded = cursor.getInt(cursor
					.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
			int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));

			if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
				downloading = false;
			}

			final double dl_progress = (bytes_downloaded / bytes_total) * 100;

			runOnUiThread(new Runnable() {

				@Override
				public void run() {

					mProgressBar.setProgress((int) dl_progress);

				}
			});

			Log.d(Constants.MAIN_VIEW_ACTIVITY, statusMessage(cursor));
			cursor.close();
		}

	}
}).start();

my statusMessage method:

private String statusMessage(Cursor c) {
	String msg = "???";

	switch (c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
	case DownloadManager.STATUS_FAILED:
		msg = "Download failed!";
		break;

	case DownloadManager.STATUS_PAUSED:
		msg = "Download paused!";
		break;

	case DownloadManager.STATUS_PENDING:
		msg = "Download pending!";
		break;

	case DownloadManager.STATUS_RUNNING:
		msg = "Download in progress!";
		break;

	case DownloadManager.STATUS_SUCCESSFUL:
		msg = "Download complete!";
		break;

	default:
		msg = "Download is nowhere in sight";
		break;
	}

	return (msg);
}

My log is working perfectly, while my download is running says "Download in progress!" and when it is complete "Download complete!", but the same doesn't occurs on my progress, why? I really need some help, other logics to do that are really appreciated

Android Solutions


Solution 1 - Android

You are dividing two integers:

final double dl_progress = (bytes_downloaded / bytes_total) * 100;

As bytes_downloaded is less than bytes_total, (bytes_downloaded / bytes_total) will be 0, and your progress will therefore always be 0.

Change your calculation to

final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);

to obtain the progress in whole (albeit floored) percentiles.

Solution 2 - Android

Paul's answer is correct but with larger downloads you will hit max int pretty quick and start getting a negative progress. I used this to solve the issue:

final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);

Solution 3 - Android

In case if someone needs implementation of download progress retriever from @Victor Laerte's question in Kotlin with RxJava here you go:

DownloadStateRetriever.kt

class DownloadStateRetriever(private val downloadManager: DownloadManager) {

    fun retrieve(id: Long) {
        var downloading = AtomicBoolean(true)

        val disposable = Observable.fromCallable {
            val query = DownloadManager.Query().setFilterById(id)
            val cursor = downloadManager.query(query)

            cursor.moveToFirst()

            val bytesDownloaded = cursor.intValue(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
            val bytesTotal = cursor.intValue(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)

            if (isSuccessful(cursor)) downloading.set(false)
            cursor.close()

            if (bytesTotal == 0) 0.toInt() else ((bytesDownloaded * 100F) / bytesTotal).toInt()
        }
                .subscribeOn(Schedulers.newThread())
                .delay(1, TimeUnit.SECONDS)
                .repeatUntil { !downloading.get() }
                .subscribe {
                    Timber.i("Subscribed to $id. progress: $it")
                }
    }

    private fun isSuccessful(cursor: Cursor) = status(cursor) == DownloadManager.STATUS_SUCCESSFUL

    private fun status(cursor: Cursor) = cursor.intValue(DownloadManager.COLUMN_STATUS)
}

I have added extensions to cursor for more code clarity:

CursorExtensions.kt

import android.database.Cursor

fun Cursor.column(which: String) = this.getColumnIndex(which)
fun Cursor.intValue(which: String): Int = this.getInt(column(which))
fun Cursor.floatValue(which: String): Float = this.getFloat(column(which))
fun Cursor.stringValue(which: String): String = this.getString(column(which))
fun Cursor.doubleValue(which: String): Double = this.getDouble(column(which))

Solution 4 - Android

As paul said, you are dividing two integers, with result always <1.

Always cast your number before divide, which calculate and return in float-point.

Don't forget to handle DivByZero.

final int dl_progress = (int) ((double)bytes_downloaded / (double)bytes_total * 100f);

Solution 5 - Android

And in case if someone need to implementation download progress with kotlin and flows - i hope this help someone. This is @rahimli version with adding flows instead of RxJava

Retriever

private fun retrieve(id: Long) = flow {
        val downloading = AtomicBoolean(true)

        while (downloading.get()) {
            val query = DownloadManager.Query().setFilterById(id)
            val cursor = downloadManager.query(query)

            cursor.moveToFirst()

            val bytesDownloaded = cursor.intValue(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
            val bytesTotal = cursor.intValue(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)

            if (isSuccessful(cursor)) downloading.set(false)
           
            cursor.close()

            emit(DownloadingState.Downloading(bytesDownloaded, bytesTotal))

            if (downloading.get()) delay(1000)
        }
    }.flowOn(Dispatchers.IO)

Some helpers/extensions

    private fun isSuccessful(cursor: Cursor) = status(cursor) == DownloadManager.STATUS_SUCCESSFUL
    private fun status(cursor: Cursor) = cursor.intValue(DownloadManager.COLUMN_STATUS)
    private fun Cursor.column(which: String) = this.getColumnIndex(which)
    private fun Cursor.intValue(which: String): Int = this.getInt(column(which))

    sealed class DownloadingState {
        data class Downloading(val downloadedBytes: Int, val totalBytes: Int) : DownloadingState() {
            val progress = if (totalBytes == 0) 0 else ((downloadedBytes * 100) / totalBytes)
        }
        object Failure : DownloadingState()
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVictor LaerteView Question on Stackoverflow
Solution 1 - AndroidPaul LammertsmaView Answer on Stackoverflow
Solution 2 - AndroidJustinMorrisView Answer on Stackoverflow
Solution 3 - AndroidrahimliView Answer on Stackoverflow
Solution 4 - AndroidDennis CView Answer on Stackoverflow
Solution 5 - AndroidSolo4View Answer on Stackoverflow