Android: Spinner and ListView with the same ArrayList

Manu Sisko :

I'm trying to implement a selection activity for a given list of items. Each item is checkable, so I have an item with a TextView and a CheckBox. I implemented a ListView for displaying all the options and a Spinner for showing only the "Top Ten" choices, as a subset of the same list. For now I'm showing all the items in both ListView and Spinner.

I want for the items in the ListView to update when the user selects an item in the Spinner (Note: The reverse path works fine, as the Spinner grabs the updated ArrayList each time it dropsdown). I tried to implement setOnItemSelectedListener for my Spinner, and to call notifyOnDataSetChanged() for my ListViewAdapter inside the Listener. But the Listener is only called on collapse and I get a weird (maybe unrelated) warning message.

The onItemSelectedListener for the Spinner only runs when the Spinner gets collapsed. But notifyOnDataSetChanged() seems to ignore the checked status of the items as a change. How can I make the first option run everytime I check an item and have the change get properly received by the ListAdapter?

Here's the Activity.java code:

public class TriageReasonActivity extends BaseActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_triage_reason);

    final String[] select_qualification = {
            "Select Qualification", "10th / Below", "12th", "Diploma", "UG",
            "PG", "Phd"};
    Spinner spinner = (Spinner) findViewById(R.id.top_reasons_spinner);
    ListView symptoms_list  = (ListView) findViewById(R.id.view_list_symptoms);
    ArrayList<Symptoms> listVOs = new ArrayList<>();

    for (int i = 0; i < select_qualification.length; i++) {
        Symptoms reason = new Symptoms();
        reason.setTitle(select_qualification[i]);
        reason.setSelected(false);
        listVOs.add(reason);
    }
    SymptomsListAdapter mListAdapter = new SymptomsListAdapter(this, 0,
            listVOs);
    SymptomsSpinnerAdapter mSpinnerAdapter = new SymptomsSpinnerAdapter(this, 0,
            listVOs);

    symptoms_list.setAdapter(mListAdapter);
    spinner.setAdapter(mSpinnerAdapter);

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
            Log.i("Item selected", "but not cahnged");
            symptoms_list.invalidateViews();
            mListAdapter.notifyDataSetInvalidated();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parentView) {
            Log.i("Not item selected", "but actually it did");
        }

    });
}

The SpinnerCustom Adapter code:

public class SymptomsSpinnerAdapter extends ArrayAdapter<Symptoms>{
private Context mContext;
private ArrayList<Symptoms> listState;
private SymptomsSpinnerAdapter myAdapter;
private boolean isFromView = false;

/*@Override
public void notifyDataSetChanged() {
    super.notifyDataSetChanged();
    //mNotifyOnChange = true;
}*/

public SymptomsSpinnerAdapter(Context context, int resource, List<Symptoms> objects) {
    super(context, resource, objects);
    this.mContext = context;
    this.listState = (ArrayList<Symptoms>) objects;
    this.myAdapter = this;
}

@Override
public View getDropDownView(int position, View convertView,
                            ViewGroup parent) {
    return getCustomView(position, convertView, parent);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return getCustomView(position, convertView, parent);
}

public View getCustomView(final int position, View convertView,
                          ViewGroup parent) {

    final ViewHolder holder;
    if (convertView == null) {
        LayoutInflater layoutInflator = LayoutInflater.from(mContext);
        convertView = layoutInflator.inflate(R.layout.item_reasons, null);
        holder = new ViewHolder();
        holder.mTextView = (TextView) convertView.findViewById(R.id.text);
        holder.mCheckBox = (CheckBox) convertView.findViewById(R.id.checkbox);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.mTextView.setText(listState.get(position).getTitle());

    // To check weather checked event fire from getview() or user input
    isFromView = true;
    holder.mCheckBox.setChecked(listState.get(position).isSelected());
    isFromView = false;

    if ((position == 0)) {
        holder.mCheckBox.setVisibility(View.INVISIBLE);
    } else {
        holder.mCheckBox.setVisibility(View.VISIBLE);
    }
    holder.mCheckBox.setTag(position);
    holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            int getPosition = (Integer) buttonView.getTag();

            if (!isFromView) {
                listState.get(position).setSelected(isChecked);
            }
        }
    });
    return convertView;
}

@Override
public int getCount() {
    return listState.size();
}

@Override
public Symptoms getItem(int position) {
    if( position < 1 ) {
        return null;
    }
    else {
        return listState.get(position-1);
    }
}

@Override
public long getItemId(int position) {
    return 0;
}

private class ViewHolder {
    private TextView mTextView;
    private CheckBox mCheckBox;
}
}

Here's the (almost identical) ListAdapter:

public class SymptomsListAdapter extends BaseAdapter implements ListAdapter {
private Context mContext;
private ArrayList<Symptoms> listState;
private boolean isFromView = false;


public SymptomsListAdapter(Context context, int resource, List<Symptoms> objects) {
    this.mContext = context;
    this.listState = (ArrayList<Symptoms>) objects;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return getCustomView(position, convertView, parent);
}

public View getCustomView(final int position, View convertView,
                          ViewGroup parent) {

    ViewHolder holder;
    if (convertView == null) {
        LayoutInflater layoutInflator = LayoutInflater.from(mContext);
        convertView = layoutInflator.inflate(R.layout.item_reasons, null);
        holder = new SymptomsListAdapter.ViewHolder();
        holder.mTextView = (TextView) convertView.findViewById(R.id.text);
        holder.mCheckBox = (CheckBox) convertView.findViewById(R.id.checkbox);
        convertView.setTag(holder);
    } else {
        holder = (SymptomsListAdapter.ViewHolder) convertView.getTag();
    }


    holder.mTextView.setText(listState.get(position).getTitle());

    // To check weather checked event fire from getview() or user input
    isFromView = true;
    holder.mCheckBox.setChecked(listState.get(position).isSelected());
    isFromView = false;

    if ((position == 0)) {
        holder.mCheckBox.setVisibility(View.INVISIBLE);
    } else {
        holder.mCheckBox.setVisibility(View.VISIBLE);
    }
    holder.mCheckBox.setTag(position);
    holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            int getPosition = (Integer) buttonView.getTag();

            if (!isFromView) {
                listState.get(position).setSelected(isChecked);
            }
        }
    });
    return convertView;
}

@Override
public int getCount() {
    return listState.size();
}

@Override
public Symptoms getItem(int position) {
    if( position < 1 ) {
        return null;
    }
    else {
        return listState.get(position-1);
    }
}

@Override
public long getItemId(int position) {
    return 0;
}

private class ViewHolder {
    public TextView mTextView;
    public CheckBox mCheckBox;
}
}

And here's the warning I'm getting:

W/art: Before Android 4.1, method int android.support.v7.widget.DropDownListView.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView

EDIT: Adding the layouts and the model class in case they may cause an issue: Activity Layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="demo.hb.activity.visit.TriageReasonActivity">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textFontWeight="6dp"
        android:textSize="30sp"
        android:layout_margin="20dp"
        android:textAlignment="center"
        android:textColor="#000000"
        android:text="What is the reason for your visit?" />

    <Spinner
        android:id="@+id/top_reasons_spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/btn_dropdown"
        android:spinnerMode="dropdown"/>



    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="end">

        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/view_list_symptoms"
            android:layout_above="@+id/next_btn"
            android:layout_alignParentTop="true"/>

    </RelativeLayout>
</LinearLayout>

</FrameLayout>

Item layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="20dp"
    android:text="text"
    android:textAlignment="gravity" />

<CheckBox
    android:id="@+id/checkbox"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true" />
</RelativeLayout>

Model Class:

public class Symptoms {
private String title;
private boolean selected;

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

public boolean isSelected() {
    return selected;
}

public void setSelected(boolean selected) {
    this.selected = selected;
}
}
BlackHatSamurai :

The reason that nothing is changing is because you haven't implemented the method to handle the data set changes. You need to handle how the data is reloaded in your adapter:

public class SymptomsListAdapter extends BaseAdapter implements ListAdapter {
  ...
  public void refreshData(ArrayList<Symptoms> objects){
    this.listState = (ArrayList<Symptoms>) objects;
    notifyDataSetChanged();
  }
  ...
}

This link does a great job of explaining how the notifyDataSetInvalidated() works (or in your case, why it's not working).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=161965&siteId=1