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.
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.