Android Spinner selection Android Spinner selection android android

Android Spinner selection


To workaround you need to remember the last selected position. Then inside of your spinner listener compare the last selected position with the new one. If they are different, then process the event and also update the last selected position with new position value, else just skip the event processing.

If somewhere within the code you are going to programatically change spinner selected position and you don't want the listener to process the event, then just reset the last selected position to the one you're going to set.

Yes, Spinner in Android is painful. I'd even say pain starts from its name - "Spinner". Isn't it a bit misleading? :) As far as we're talking about it you should also be aware there's a bug - Spinner may not restore (not always) its state (on device rotation), so make sure you handle Spinner's state manually.


Hard to believe that a year and a half later, the problem still exists and continues to boggle people...

Thought I'd share the workaround I came up with after reading Arhimed's most useful post (thanks, and I agree about spinners being painful!). What I've been doing to avoid these false positives is to use a simple wrapper class:

import android.util.Log;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemSelectedListener;public class OnItemSelectedListenerWrapper implements OnItemSelectedListener {    private int lastPosition;    private OnItemSelectedListener listener;    public OnItemSelectedListenerWrapper(OnItemSelectedListener aListener) {        lastPosition = 0;        listener = aListener;    }    @Override    public void onItemSelected(AdapterView<?> aParentView, View aView, int aPosition, long anId) {        if (lastPosition == aPosition) {            Log.d(getClass().getName(), "Ignoring onItemSelected for same position: " + aPosition);        } else {            Log.d(getClass().getName(), "Passing on onItemSelected for different position: " + aPosition);            listener.onItemSelected(aParentView, aView, aPosition, anId);        }        lastPosition = aPosition;    }    @Override    public void onNothingSelected(AdapterView<?> aParentView) {        listener.onNothingSelected(aParentView);    }}

All it does is trap item selected events for the same position that was already selected (e.g. the initial automatically triggered selection for position 0), and pass on other events to the wrapped listener. To use it, all you have to do is modify the line in your code that calls the listener to include the wrapper (and add the closing bracket of course), so instead of, say:

mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {    ...});

you'd have this:

mySpinner.setOnItemSelectedListener(new OnItemSelectedListenerWrapper(new OnItemSelectedListener() {    ...}));

Obviously once you've tested it, you could get rid of the Log calls, and you could add the ability to reset the last position if required (you'd have to keep a reference to the instance, of course, rather than declaring on-the-fly) as Arhimed said.

Hope this can help someone from being driven crazy by this strange behaviour ;-)


In the past I've done things like this to distinguish

internal++; // 'internal' is an integer field initialized to 0textBox.setValue("...."); // listener should not act on this internal settinginternal--;

Then in textBox's listener

if (internal == 0) {  // ... Act on user change action}

I use ++ and -- rather than setting a boolean value to 'true' so that there is no worry when methods nest other methods that might also set the internal change indicator.