0

I am attempting to update UI elements in a ListView row from my custom adapter. I have no problems when updating from within getView(). But the UI does not update from within my onCheckedChangedListener nor from a method called in onPostExecute() of my AsyncTask. I am using the View Holder pattern, here is my code.

public class InstanceAdapter extends ArrayAdapter<MyInstance> {

    Context context;
    int layoutResourceId;
    List<MyInstance> data = null;
    static InstanceHolder holder = null;
    static Timer myTimer;

    public InstanceAdapter(Context context, int layoutResourceId,
            List<MyInstance> data) {
        super(context, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        if (row == null) {
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);

            holder = new InstanceHolder();
            holder.spinner = (ProgressBar) row.findViewById(R.id.progressBar);
            holder.state = (TextView) row.findViewById(R.id.instanceState);


            row.setTag(holder);
        } else {
            holder = (InstanceHolder) row.getTag();
        }

        final MyInstance instance = data.get(position);
        holder.instanceName.setText(instance.getName());
        holder.state.setText(instance.getState());
        holder.toggle.setChecked(instance.stateSwitch);
        holder.toggle.setEnabled(instance.getEnabled());

        holder.toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {

                if (isChecked) {

                    // this does not work
                    holder.spinner.setVisibility(View.VISIBLE);

                    new StartInstancesTask(instance).execute();

                } else {

                    // this does not work
                    holder.spinner.setVisibility(View.VISIBLE);

                }

            }

        });

        return row;
    }

    @Override
    public MyInstance getItem(int position) {
        return data.get(position);
    }

    static class InstanceHolder {
        TextView state;
        ProgressBar spinner;
    }

    protected static class StartInstancesTask extends
            AsyncTask<Void, Void, String> {

        private MyInstance instance;
        private String result;

        public StartInstancesTask(MyInstance instance) {
            this.instance = instance;
        }

        @Override
        protected String doInBackground(Void... params) {
            result = EC2.startInstance(instance.getId());

            return result;
        }

        protected void onPostExecute(final String result) {

            updateState(result);

        }
    }

    public static void updateState(String result) {

        // these ui updates do not work ...

        if (result.equals("pending")) {
            holder.spinner.setVisibility(View.VISIBLE);
        } else if (result.equals("running")) {
            holder.spinner.setVisibility(View.GONE);
        } else if (result.equals("shutting-down")) {
            holder.spinner.setVisibility(View.VISIBLE);
        } else if (result.equals("terminated")) {
            holder.spinner.setVisibility(View.GONE);
        } else if (result.equals("stopping")) {
            holder.spinner.setVisibility(View.VISIBLE);
        } else if (result.equals("stopped")) {
            holder.spinner.setVisibility(View.GONE);
        }

        holder.state.setText(result);

    }
}

I had put in logging statements, and updateResult() is definitely being called correctly. Any ideas?

littleK
  • 19,521
  • 30
  • 128
  • 188
  • you are changing the view, which is recycled, when you should be changing the data. – njzk2 Dec 06 '13 at 14:03
  • and your holder reference changes at every getView call. it can only point to one thing at a time, and that thing is the last view holder that was rendered. – njzk2 Dec 06 '13 at 14:04

2 Answers2

0

Use holder.toggle.setTag for getting holder instance for clicked row to update views. try as:

InstanceHolder clicked_holder = null; // to get selected row views

in getView save current row holder using holder.toggle.setTag :

holder.toggle.setEnabled(instance.getEnabled());
holder.toggle.setTag(holder);
holder.toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                    clicked_holder=(InstanceHolder)buttonView.getTag();
                    //...your code here..     
            }

        });

now use clicked_holder instead of holder which hold last row to updated views in selected row

ρяσѕρєя K
  • 132,198
  • 53
  • 198
  • 213
  • So clicked_holder should be global and holder should be local to getView()? – littleK Dec 06 '13 at 14:33
  • declare `clicked_holder` as global – ρяσѕρєя K Dec 06 '13 at 14:37
  • OK, I have it mostly working now. But I still have some crashes. I think that this is because I am referencing clicked_holder directly in my updateState() method without a getTag() assignment. How can I reference the spinner in updateStatus()? – littleK Dec 06 '13 at 14:45
  • @littleK : put null check before accessing views from `clicked_holder` and also add one more `paramter` in `StartInstancesTask` constructor and `updateStatus` to get clicked_holder – ρяσѕρєя K Dec 06 '13 at 14:47
  • Did that, same error. Looks like I am not on the main UI thread according to the error: "CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views." It works if I wrap the contents of updateResult() in a Runnable (see answer from http://stackoverflow.com/questions/3280141/android-calledfromwrongthreadexception-only-the-original-thread-that-created). Is that acceptable? I thought that anything called from onPostExecute() is safe? – littleK Dec 06 '13 at 15:04
  • @littleK make sure you are updating or accessing any UI element inside startInstance method? – ρяσѕρєя K Dec 06 '13 at 15:09
0

Take a look at this post: http://android.kuffs.co.uk/2013/12/custom-list-adapter-with-icon-and.html

Updating the UI in this case is as simple as editing the properties of the CheckedListItem (or similar) class and calling NotifyDatasetChanged on the adapter.

Kuffs
  • 35,581
  • 10
  • 79
  • 92