Android ProgressBar in ListView while using DownloadManager
D

3

7

I have a ListView in which each item represents a PDF file. When the user clicks on an item, the application must download the file on external storage. Now the download doesn't work properly, but that's not the question. I want a ProgressBar, spinning wheel style, to appear next to each item of the list while the file is downloaded.

My problem is : I can't find how to make the spinning wheel appear. Before the Download Manager I tried it with an AsyncTask and the spinning wheel worked.

Here is my code :

CategoryActivity.java (Activity of the ListView)

@Override
public void onItemClick(AdapterView<?> parent, View view,
    int position, long id) {
    // Récupère les valeurs depuis ListItem
    udl = ((TextView) view.findViewById(R.id.udl)).getText().toString();
    // This is the spinning wheel
    loader = ((ProgressBar) view.findViewById(R.id.spinWheel2));

    filepath = dirpath + udl + ".pdf";
    File file = new File(filepath);
    if (file.exists()) {

        // If the file exists, I open it

    }else{ // Else I download it
        // Setting the spinning wheel to VISIBLE
        loader.setVisibility(View.VISIBLE);

        SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
        url2 = codeSaveUrl.getString("defaut", ""); // Code Organisation
        // Constructing the uriString
        uri = url10 + url2 + "&file=" + udl ;

        Uri myuri = Uri.parse(uri);
        DownloadManager mgr=(DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
        mgr.enqueue(new DownloadManager.Request(myuri)
        .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
        .setAllowedOverRoaming(false)
        .setTitle(udl + ".pdf")
        .setDescription("Téléchargement en cours")
        .setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));                    

        // Hiding the spinning wheel
        loader.setVisibility(View.GONE);

    }
}

Note that if I don't make the spinning wheel disappear, it will always be visible after click on the item. With the line to hide it, it doesn't even appear.


EDIT :
I added a BroadcastReceiver.

Put these two lines in onCreate() :

final DownloadManager mgr=(DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

And added this :

BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            loader.setVisibility(View.GONE);
        }
    };

    @Override
    public void onDestroy(){
        super.onDestroy();
        unregisterReceiver(onComplete);
    }

EDIT 2 : Ok so here are some changes I made :

I save the id of the download in a variable :

lastDownload = mgr.enqueue(new DownloadManager.Request(myuri)
                .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
                .setAllowedOverRoaming(false)
                .setTitle(udl + ".pdf")
                .setDescription("Téléchargement en cours")
                .setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED));

Depending on the status of the download, I want to do different things :

BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload));
            if(c.moveToFirst()){
                int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                switch(x){
                case DownloadManager.STATUS_PAUSED:
                case DownloadManager.STATUS_PENDING:
                case DownloadManager.STATUS_RUNNING:
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    loader.setVisibility(View.GONE);
                    break;
                case DownloadManager.STATUS_FAILED:
                    //TODO: retry download
                    break;
                }
            }

        }
    };

The problem is, the spinning wheel only hides for the last item clicked in the listView. I tried with debug mode, but the program has a correct behavior (meaning loader.setVisibility(View.GONE) is called for every download). I don't know why the spinning wheel won't hide except for the last item clicked.


EDIT : 3 I know why the spinning wheel won't hide except for the last item clicked.

When I click on multiple items, lastDownload takes the id of the last one clicked. So in the broadcast receiver, it does the case of the last item clicked times the number of items clicked.
I tried with changing lastDownload into an array/table of longs, and comparing it with referenceId, which I believe is the id contained in the intent.
Here is the new code (y=0 and ld is the number of items clicked):

BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) { 
            long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            if(y <ld){
                if(lastDownload[y] == referenceId){

            Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload[y]));
            if(c.moveToFirst()){
                int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                switch(x){
                case DownloadManager.STATUS_PAUSED:
                case DownloadManager.STATUS_PENDING:
                case DownloadManager.STATUS_RUNNING:
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    loader.setVisibility(View.GONE); // This loader is the one of the last item clicked
                    break;
                case DownloadManager.STATUS_FAILED:
                    //TODO: retry download
                    break;
                }

            }

                       y=y+1;

                   }
            }   
        }
    };

I didn't write the part where I put the variables back to 0, but for now the code pretty much works as expected. The only problem remaining is that the spinning wheel I make disappear is the spinning wheel of the last item clicked. And I know why. Because this line : loader = ((ProgressBar) view.findViewById(R.id.spinWheel2)); is located in the onItemClicked method. I don't think I can put it anywhere else because the view it is in is not the view of the activity.

Long story short : I must find a way to access the progress bar of the item/view I clicked on, knowing that I can click on multiple items before the first one reaches the Broadcast Receiver.


EDIT : 4 Ok so I did this :

y,z, and ld are set to 0 at the begining.

When an item is being clicked :

// Loader of the clicked item is made visible
loader[z].setVisibility(View.VISIBLE);

// Construction of the URL
SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
url2 = codeSaveUrl.getString("defaut", ""); // Organization code
uri = url10 + url2 + "&file=" + udl ;

// URL parse to URI
Uri myuri = Uri.parse(uri);

// Enqueue file to downloads, with notification. Storage of download id in a table
lastDownload[ld] = mgr.enqueue(new DownloadManager.Request(myuri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false)
.setTitle(udl + ".pdf")
.setDescription("Téléchargement en cours")
.setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));

// Increment variables for next downloads
ld=ld+1;
z=z+1;

Broadcast Receiver :

// Broadcast Receiver called when a download is finished
BroadcastReceiver onComplete = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        // referenceId is the download's id for which the method is called
        long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        // If y (=0 at the beginning) is inferior to the number of downloads
        if(y <ld){
            // If the id of the download corresponds to the one for which the method is called
            if(lastDownload[y] == referenceId){
                // We define a cursor depending on the id
                Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload[y]));
                if(c.moveToFirst()){
                    // Download status recovery
                    int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch(x){
                    // If download is paused, pending or running, we do nothing
                    case DownloadManager.STATUS_PAUSED:
                    case DownloadManager.STATUS_PENDING:
                    case DownloadManager.STATUS_RUNNING:
                        break;
                    // If file has successfully been downloaded, loader is hidden
                    case DownloadManager.STATUS_SUCCESSFUL:
                        loader[y].setVisibility(View.GONE); 
                        // Increment y to go to next download
                        y=y+1;
                        break;
                    // If download failed, it is retried
                    case DownloadManager.STATUS_FAILED:
                        //TODO: retry download
                        break;
                    }
                }
            }
        }
    }
};

Works ok, except when an item of a small file is being clicked while a big file is being downloaded. The small file takes the priority and the download manager doesn't follow the order of the tables anymore, causing the loading wheel to not disappear.


EDIT : 5

I found a way to do what I wanted, see my answer.

Thanks for your help.

Deipnosophist answered 22/8, 2014 at 12:46 Comment(1)
#7825335 This is also a good solution for this case .Hepcat
D
2

Ok so I managed to do what I wanted to do with a HashTable :

// HashTable to store download id's and loaders
Hashtable<Long, SyncedProgressBar> storeTable = new Hashtable<Long, SyncedProgressBar>();

In my onClickListener method, after loader[z] and lastDownload[ld] take their value, I put them in the HashTable : ( the downloa id will be the key, the loader will be the value )

// Storing download id and loader in HashTable
storeTable.put(lastDownload[ld], loader[z]);

In my Broadcast Receiver's onReceive method, instead of doing this :

if(lastDownload[y] == referenceId)

I look if the HashTable contains the download id of the intent :

if(storeTable.containsKey(referenceId))

And I put the correct value in the loader :

loader[y] = storeTable.get(referenceId);

Then I just have to put the loader's visibility to GONE where I want. This solution works for me, but I will update it if I find something new.

Here is my new code :

In the onClickListener method : ( not complete here )

// Loader of the clicked item is made visible
loader[z].setVisibility(View.VISIBLE);

// Construction of the URL
SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
url2 = codeSaveUrl.getString("defaut", ""); // Organization code
uri = url10 + url2 + "&file=" + udl ;

// URL parse to URI
Uri myuri = Uri.parse(uri);

// Enqueue file to downloads, with notification. Storage of download id in a table
lastDownload[ld] = mgr.enqueue(new DownloadManager.Request(myuri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false)
.setTitle(udl + ".pdf")
.setDescription("Téléchargement en cours")
.setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));

// Storing download id and loader in HashTable
storeTable.put(lastDownload[ld], loader[z]);

// Increment variables for next downloads
ld=ld+1;
z=z+1;

Broadcast Receiver :

// Broadcast Receiver called when a download is finished
BroadcastReceiver onComplete = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        // referenceId is the download's id for which the method is called
        long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        // If y (=0 at the beginning) is inferior to the number of downloads
        if(y <ld){
            // If the HashTable contains the Key-download-id for which the method is called
            if(storeTable.containsKey(referenceId)){
                // Loader takes the value for the key
                loader[y] = storeTable.get(referenceId);
                // We define a cursor depending on the id
                Cursor c = mgr.query(new DownloadManager.Query().setFilterById(referenceId));
                if(c.moveToFirst()){
                    // Download status recovery
                    int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch(x){
                    // If download is paused, pending or running, we do nothing
                    case DownloadManager.STATUS_PAUSED:
                    case DownloadManager.STATUS_PENDING:
                    case DownloadManager.STATUS_RUNNING:
                        break;
                    // If file has successfully been downloaded, loader is hidden
                    case DownloadManager.STATUS_SUCCESSFUL:
                        loader[y].setVisibility(View.GONE); 
                        // Increment y to go to next download
                        y=y+1;
                        break;
                    // If download failed, it is retried
                    case DownloadManager.STATUS_FAILED:
                        //TODO: retry download
                        break;
                    }
                }
            }
        }
    }
};
Deipnosophist answered 28/8, 2014 at 9:22 Comment(2)
Hi, can you please provide the full solution, i want to do a similar task but i want to show progress of each downoad and the number of files currently being downloadedZelda
whats the SyncedProgressBar class?Anecdotic
E
1

Please note that DownloadManager.enqueue is asynchronous, it means mgr.enqeue returns almost immediately and after that your current code sets spinner back to invisible.

To hide spinner you have to register a broadcast receiver to receive notification when download completes. You then have to find corresponding spinner and hide it. Please note that download can fail (and notification is still sent in this case).

CommonsWare has posted an example showing how to work with DownloadManager.

Empirical answered 22/8, 2014 at 13:16 Comment(4)
Added the broadcast receiver. Now the spinners get visible when I click on the corresponding items, but some of them stay visible after the download completes.Deipnosophist
I edited my question, but didn't implement the download failing case yet.Deipnosophist
Your code hides spinner only for last item clicked. That is because loader gets his value in onItemClick. You have to store download id returned by mgr.enqueue. Intent received in broadcastreceiver contains id of download just finished. Using this information you have to find spinner you want to disable.Empirical
Ok so if I do it like in the link you provided, I can store the download id in a variable with lastDownload = mgr.enqueue(...);. Then in the BroadcastReceiver I guess I have to use setFilterById(lastDownload), but I don't know where to put loader.setvisibility(View.GONE) in that caseDeipnosophist
D
-1

I'd use an AsyncTask for that, and make the wheel appear / disappear in onPreCreate & onPostCreate

private class MyDownloader extends AsyncTask<String, Void, File>{

    @Override
    protected void onPreExecute() {
        loader.setVisibility(View.VISIBLE);
    }

    @Override
    protected File doInBackground(String... params) {
        //Download and save file here, and return the result, which will be fed into the onPostExecute method
        return file;
    }

    @Override
    protected void onPostExecute(File file) {
        super.onPostExecute(file);
        loader.setVisibility(View.GONE);
    }
}

Then start the task with new MyDownloader().execute("linktopdf"); or something like that (what you put in .execute will be fed into the doInBackground method)

Doorway answered 22/8, 2014 at 13:14 Comment(2)
Mind you that you will have to download the file in a synchronus way inside doInBackground, so that it will block the AsyncTask.Doorway
Can't do that, the user must be able to download multiple files simultaneously (or click on multiple files and they download one at a time), and must be able to do something else during the download. I already tried using AsyncTask but there are spinning wheel problems too (sometimes doesn't disappear, sometimes is cloned on another item out of the screen)Deipnosophist

© 2022 - 2024 — McMap. All rights reserved.