0
votes

How do I update the text of a button to a listview through another class without modifying the text of the other buttons on the list?

Ex:


ITEM 1 [DOWNLOAD]


ITEM 2 [DOWNLOADING... 60%]


ITEM 3 [DOWNLOADING... 40%]


ITEM 4 [DOWNLOAD]


Actually, works but scrolling the listview, other buttons have your values changed too.. I need to create a list of mídia ready for download, but when I click in a download button, the download starts, the percent updates but other buttons have your text changed too...

I would like to update the text "downloadBt" buttons of the listView items through my class Downloader, making progress on them: Downloading ... 30%

What is the best form to make this?


Solution:

Create a new instance of List:

private static class ViewHolder {
    protected ImageView cover;
    protected TextView issueNumber;
    protected TextView details;
    protected Button downloadBt;
    protected Button moreBt;
    protected View convertView;  <<<
    protected ViewGroup parent; <<<
}

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; list.get(position).setListPosition(position);

    if (convertView == null) {
        LayoutInflater inflater = context.getLayoutInflater();
        convertView = inflater.inflate(R.layout.list_item_list_issue, null);

        viewHolder = new ViewHolder();
        viewHolder.cover = (ImageView) convertView.findViewById(R.id.issue_list_item_cover);
        viewHolder.issueNumber = (TextView) convertView.findViewById(R.id.issue_list_item_number);
        viewHolder.details = (TextView) convertView.findViewById(R.id.issue_list_item_details);
        viewHolder.downloadBt = (Button) convertView.findViewById(R.id.list_item_issue_download);
        viewHolder.moreBt = (Button) convertView.findViewById(R.id.list_item_issue_more);
        viewHolder.parent = parent;
        viewHolder.convertView = convertView;

        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.issueNumber.setText(list.get(position).getIssue());
    viewHolder.downloadBt.setText(list.get(position).getDownloadStatus());

    try{
        vList.remove(position);
    }catch(Exception e){
        e.printStackTrace();
    }
    vList.add(position, viewHolder);
    return convertView;
}

And in my Downloader class, send the position and the Adapter instance. I have been created a method refresh in my adapter class:

   public void refresh(int position){
        if((listView.getFirstVisiblePosition() <= position)&&(position <= listView.getLastVisiblePosition()))
            getView(position, vList.get(position).convertView, vList.get(position).parent);
    }

And this update only a selected item in the listview.

2

2 Answers

1
votes

!!UPDATE!!

It is clear from the log that you are trying to update the button from a different thread other than UI thread. The UI objects/views should be updated only from the UI thread and UI thread should not be used for long running operations. These are basics you are supposed to know. I strongly recommend you go through the docs and/or videos for better understanding.

The solution is to use AsyncTask instead of Runnable and AsyncTask has a method progressUpdate() that runs on the UI thread. Alternatively, you can have your Runnable as a subclass in Activity and use the method runOnUiThread() for updating the view.

I also notice problem with your thread pools as you associate them with the objects in list view, which means, if there are 10 convertView objects, you'll have 10 thread pools with 50 threads. This will degrade the performance and you're essentially using only one thread from this pool. You need to get strong with your basics and revisit the solution.


Inside a list view, the item objects are reused, which is the convertView argument in the getview()method in the Adapter. So, when convertView is null, you create the item view object for the first time and as you scroll through, the same object is re-used instead of creating a new one as this saves CPU cycles( and hence battery) in terms of avoiding garbage collection and creating new objects. In other words, if you have a list of 100 items and only 10 items can be fit on your screen only 10 objects will be created even though you scroll through all the 100 items as the same 10 objects are re-used as the screen responds to scrolling. This is true for the child views as well(i.e., the buttons as well).

From your code, I see that you are using Button objects in your download threads. Button objects are also re-used. Check if this could be the problem.

Check this video if you haven't already.

1
votes

The problem is that you are trying to update the button itself, you should update the items wrapped by adapter and call on the main thread adapter.notifyDataSetChanged(); in order to get your list view refreshed (trigger other calls to getView for visible positions). So your ComicDownloader should accept a comic object and a reference to the adapter itself. Sundeep is right, convertView (toghether with the button in it) gets reused (and this is a normal and wanted behaviour) and you should'n rely on it.

This line: viewHolder.downloadBt.setText(list.get(position).getDownloadStatus()); should be called just before returning the convertView, and not only when convertView is null, doing so you make sure the returned view is up to date according to requested position, no matter if is reused or fresh created.

EDIT:

In your DownloadListener implementation, IssueListAdapter.this.notifyDataSetChanged(); should be called on the main thread. Currently, it's called directly from your run() call, wich is executed in background. Because this listener already knows which comic is updated, you can have a singleton for all your comics. Also, since your ComicDownloader is final, it will be related to first position that 'created' it, that means that wen you'll start a download, your button will update correct, but the comic downloaded behind may not be the one you think it is. To solve this, I recomend to create a ComicDownloader when button is clicked (you can also share one clickListener instance and retreive current comic for pressed button - setting a tag related to current position for button is a correct way to do it).

In your ComicDownloader class, executorService should be static (one pool shared across downloaders).