0

I am developing an app in which I need a ListView whose rows have a TextView, 2 CheckBox and a Spinner.

However, I am experiencing issues with onItemSelected() of the Spinner, as it gets called each time it is displayed for each row. In this method I am updating database records with the selected option, but as Android calls it automatically, every time the items get reset because Android calls it with position 0 and this is the value updated in the database.

I have read a lot of links about the issue with onItemSelected() and some hacks, but all of them are to use without a ListView. Any points here?

I have tried to track in a List which positions are actually displayed to make it work but it does not. I think it is because of the recycling in Android that causes the troubleshooting method get called for Spinners already shown!

So the point is: How can I differenciate a real call to onItemSelected() because of a user selection from the Android call when displaying the Spinner?

Here is the code of my adapter that extends SimpleCursorAdapter.

Thank you so much in advance.

public ParticipationAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
    super(context, layout, c, from, to);
    mActivity = (Activity)context;
    ParticipationComment.ParticipationCommentManager commentManager = new ParticipationComment.ParticipationCommentManager(mActivity);
    mParticipationCommentsCursor = commentManager.get();
    mActivity.startManagingCursor(mParticipationCommentsCursor);
    commentManager.detach();
    mPositionsOfCursorIds = getPositionsOfCursorIds(mParticipationCommentsCursor);
    mSpinnerPositionsDisplayed = new ArrayList<Integer>();
}

@Override
public View getView(final int participationPosition, View convertView, ViewGroup parent) {
    final Cursor participationsCursor = getCursor();
    mActivity.startManagingCursor(participationsCursor);
    participationsCursor.moveToPosition(participationPosition);
    View participationRow;
    if (convertView == null) {
        participationRow = LayoutInflater.from(mActivity).inflate(R.layout.participation_row_student, null);
    } else {
        mSpinnerPositionsDisplayed.remove((Integer)convertView.getTag());
        participationRow = convertView;
    }
    participationRow.setTag(participationPosition);
    Spinner commentSpinner = (Spinner)participationRow.findViewById(R.id.participation_comment_id_spinner);
    SimpleCursorAdapter commentSpinnerAdapter = new SimpleCursorAdapter(
            mActivity,
            android.R.layout.simple_spinner_item,
            mParticipationCommentsCursor,
            new String[] {DatabaseManager.NAME},
            new int[] {android.R.id.text1}
    );
    commentSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    commentSpinner.setAdapter(commentSpinnerAdapter);
    long participationCommentId = participationsCursor.getLong(participationsCursor.getColumnIndex(DatabaseManager.PARTICIPATION_COMMENT_ID));
    if (participationCommentId != 0) {
        commentSpinner.setSelection(mPositionsOfCursorIds.get(participationCommentId));
    }
    commentSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            participationsCursor.moveToPosition(participationPosition);
            if (!mSpinnerPositionsDisplayed.contains(participationPosition)) {
                // Android calls this method the first time a Spinner is displayed,
                // to differentiate from a real user click we check if the current Spinner's position
                // in the ListView is being shown
                mSpinnerPositionsDisplayed.add(participationPosition);
            } else {
                ParticipationComment participationComment = new ParticipationComment((Cursor)parent.getItemAtPosition(position));
                Participation.ParticipationManager participationManager = new Participation.ParticipationManager(mActivity);
                Participation participation = new Participation(participationsCursor);
                participation.setConnectionProfileParticipationCommentId(participationComment.getConnectionProfileId());
                participation.setParticipationCommentId(participationComment.getIdOpenErp());
                participation.setChanged(true);
                participationManager.update(participation);
                participationManager.detach();
            }
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            // Not used
        }
    });
    TextView studentName = (TextView)participationRow.findViewById(R.id.participation_student_name);
    studentName.setText(participationsCursor.getString(participationsCursor.getColumnIndex(DatabaseManager.NAME)));
    CheckBox expectedPresent = (CheckBox)participationRow.findViewById(R.id.participation_expected_present_value);
    expectedPresent.setChecked(participationsCursor.getInt(participationsCursor.getColumnIndex(DatabaseManager.EXPECTED_PRESENT)) == 1);
    CheckBox present = (CheckBox)participationRow.findViewById(R.id.participation_present_value);
    present.setChecked(participationsCursor.getInt(participationsCursor.getColumnIndex(DatabaseManager.PRESENT)) == 1);
    return participationRow;
}
Caumons
  • 9,341
  • 14
  • 68
  • 82

2 Answers2

3

A better way is to use a AlertDialog Variant.. like this.. and create a button which initially has the first selection as its Text and its changed based on the AlertDialog choice..

ngesh
  • 13,398
  • 4
  • 44
  • 60
  • Thank you so much for your answer! Tomorrow I will try it and say something to you! But I will use `public AlertDialog.Builder setCursor (Cursor cursor, DialogInterface.OnClickListener listener, String labelColumn)` instead, because I have the origin data in a db table. I think that with this approach I will be able to allow the user to select none of the choices, that is what I actually need and is not possible using the Spinner. – Caumons Apr 12 '12 at 16:58
  • Thank you so, so much! I have finally implemented the selection inside the ListView with buttons and alertDialogs as you suggested. This works great! I used the method `setSingleChoiceItems()` in the dialog, as it allows you to define a default selection. I have upvoted and accepted your answer to give you +25 rep! :) – Caumons Apr 16 '12 at 11:31
  • @Caumons .. thats great complement...:) – ngesh Apr 16 '12 at 11:59
2

What about using a small flag to discard first call of ItemSelected ?

Snicolas
  • 37,840
  • 15
  • 114
  • 173
  • Where and how could I do this? I am out of ideas... :( I am already keeping track of a List with the positions shown... So, if the current position is not there and `onItemSelected()` is called then it is made by Android, else it is a user click but... It seems that Android recalls it when already displaying and this is hurting me! I think this is because of the recycling pattern done with Android (convertView) – Caumons Apr 12 '12 at 12:49
  • I think that you should reset your counter every time a view is created or recycled (method getView of your adapter). Keep a counter for each view, reset it as the view is asked by the getView method. Discard first events of each view. – Snicolas Apr 12 '12 at 12:51
  • I think that this approach will get out my hands because I do not know exactly how is implemented the recycling and where I should reset the counter. I will try the other solution proposed by @sandy that seems much easier. I keep thinking that Google boys should improve this undesired behaviour of the method... Thank you anyway! :) – Caumons Apr 12 '12 at 17:03
  • @Snicolas: How do I check the first call of ItemSelcted? – rahstame Apr 07 '14 at 08:39
  • @lordzen With a boolean flag. – Snicolas Apr 07 '14 at 14:14