Search code examples
javaandroidlistviewandroid-sqliteandroid-cursoradapter

How to remove all but the checked item from a CursorLoader?


I have an activity that displays a listview of doctor's for a user to select. Each listview item contains a checkbox and a doctor's name. My end goal is that when the user checks a doctor, all others are removed and only the selected doctor remains.

In the adapter class, I have the following code snippet:

viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isChecked) {
            swapCursor(cursor);
        }
    }
});

I have code that stores the previous cursor so it can be restored if a doctor is unchecked, but I have removed that to simplify this question. Currently, when I check the checkbox nothing happens. I have tried adding notifyDataSetChanged() but the adapter has no registered observers so that made no difference. How can I let the activity know that an item has been selected?

From the documentation I know I should register a DataSetObserver but I don't know which object should be registered and how.

EDIT

Here is some more from my Adapter class:

public MyAdapter(Context context, Cursor cursor, int flags){
    super(context, cursor, flags);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
    View view = LayoutInflater.from(context).inflate(R.layout.adapter_layout, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(view);
    view.setTag(viewHolder);
    return view;
}

@Override
public void bindView(View view, final Context context, final Cursor cursor) {
    ViewHolder viewHolder = (ViewHolder) view.getTag();

    String firstName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_FIRSTNAME));
    String lastName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_LASTNAME));
    String suffix = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_SUFFIX));

    viewHolder.nameView.setText(firstName + " " + lastName + ", " + suffix);

    // Should I add something here?
    viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        }
    });
}

public static class ViewHolder{
    public final CheckBox checkBox;
    public final TextView nameView;

    public ViewHolder(View view){
        checkBox = (CheckBox) view.findViewById(R.id.chooseDoctorCheckBox);
        nameView = (TextView) view.findViewById(R.id.chooseDoctorLabel);
    }
}

The adapter is used in an activity that implements a CursorLoader to load the data into a listview. In the activity, I am able to call doctorAdapter.swapCursor(someCursorIWant); but I can't reference the selected doctor. Something I have tried:

// Set currentDoctor (a property of the adapter class) if a new one is checked.
// Again, other checks removed for simplicity.
viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isChecked){
            currentDoctor = cursor;
            notifyDataSetChanged();
        }
    }
});

And then in the activity:

// Set observers
mDoctorAdapter.registerDataSetObserver(new DataSetObserver() {
    @Override
    public void onChanged() {
        Cursor c = mDoctorAdapter.getCheckedDoctor();
        if(c != null)
           mDoctorAdapter.swapCursor(c);
    }
});

But it doesn't do what I want it to.


Solution

  • I have solved the problem. What I did was adjust the bindView() method to store the id of a doctor when it is selected:

    public void bindView(View view, Context context, Cursor cursor) {
        ViewHolder viewHolder = (ViewHolder) view.getTag();
    
        final long id = cursor.getLong(cursor.getColumnIndex(DoctorEntry._ID));
        String firstName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_FIRSTNAME));
        String lastName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_LASTNAME));
        String suffix = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_SUFFIX));
    
        viewHolder.nameView.setText(firstName + " " + lastName + ", " + suffix);
    
        viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked){
                    selectedDoctorId = id;
                } else{
                    selectedDoctorId = 0;
                }
                notifyDataSetChanged();
            }
        });
    }
    

    Then, I registered a new observer in the activity, and in the onChanged() method I pull the id and use a new Uri to query for just that doctor:

    // Add observer for doctor
    mDoctorAdapter.registerDataSetObserver(new DataSetObserver() {
        @Override
        public void onChanged() {
            if(!systemEntered && mDoctorAdapter.getSelectedDoctorId() != 0){
                Cursor c = getContentResolver().query(
                        DoctorEntry.buildDoctorUri(mDoctorAdapter.getSelectedDoctorId()),
                        DOCTOR_COLUMNS,
                        null,
                        null,
                        null
                );
                systemEntered = true;
                mDoctorAdapter.swapCursor(c);
                systemEntered = false;
            }
        }
    });
    

    I had to use a boolean flag called systemEntered to prevent an infinite loop. Without it, calling swapCursor() sets off onChanged() again, and I requery, and so on and so forth. Now, when I check the checkbox of a specific doctor, all others are removed from the list. Thanks to all who took their time to look into this.


    EDIT

    Based on suggestions from the comments, I have changed this to use callbacks instead. In my adapter class, I included the following interface and method:

    private DoctorAdapterCallbacks mCallbacks;
    
    viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(isChecked && mCallbacks != null){
                mCallbacks.onDoctorChecked(id);
            }
        }
    });
    
    public void onRegisterCallbacks(Activity activity){
        mCallbacks = (DoctorAdapterCallbacks) activity;
    }
    
    public static interface DoctorAdapterCallbacks{
        void onDoctorChecked(long id);
    }
    

    And in my activity (which implements the interface) I have added the following code:

    // Set callbacks (This is in the onCreate method)
    mDoctorAdapter.onRegisterCallbacks(this);
    
    @Override
    public void onDoctorChecked(long id) {
        Bundle args = new Bundle();
        args.putLong(SELECTED_DOCTOR_ID, id);
        getSupportLoaderManager().initLoader(SELECTED_DOCTOR_LOADER, args, this);
    }
    

    I have created another CursorLoader that is intended to handle only a single doctor, and that loader will replace the previous one (of all doctors). Thanks again for all the help and suggestions.