Android spinner onItemSelected called multiple times after screen rotation

In general, I've found that there are many different events that can trigger the onItemSelected method, and it is difficult to keep track of all of them. Instead, I found it simpler to use an OnTouchListener to only respond to user-initiated changes.

Create your listener for the spinner:

public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {

    boolean userSelect = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        userSelect = true;
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (userSelect) { 
            // Your selection handling code here
            userSelect = false;
        }
    }

}

Add the listener to the spinner as both an OnItemSelectedListener and an OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);

What about just to check if fragment is in resumed state? Somethink like this:

private AdapterView.OnItemSelectedListener mFilterListener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (isResumed()) {
              //your code
            }
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    };

@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        //set mFilterListener
}

It eliminates the rotation problem and also the first setup problem. No flags etc. I was having the same problem with TextWatchers and found this answer with comment, which inspired me for this solution.


To expand on Andres Q.'s answer... If you are using Java 8 you can do this with fewer lines of code by making use of lambda expressions. This method also forgoes the need to create a separate class in order to implement onTouchListener

Boolean spinnerTouched; //declare this as an instance or class variable

spinnerTouched = false;
yourSpinner.setOnTouchListener((v,me) -> {spinnerTouched = true; v.performClick(); return false;});

yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

        if(spinnerTouched){
            //do your stuff here
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        //nothing selected
    }
});

I've found a solution that is working for me.

I have the 3 spinners so onItemSelected is called 3 times at the initial spinner setup. To avoid onItemSelected from firing a method in the initial setup I've created a counter so onItemSelected only fires the method accordingly the counter value.

I've realized that in my situation, if a rotated the screen, onItemSelected is fired again the 3 times, plus a time for each spinner that is not in the position 0.

An example:

I have the 3 spinners and the user changes 2 of them to one of the available option other then position 0 so he ends up with a situation like this:

First spinner - > Item 2 selected
Second spinner -> Item 0 selected (no changes)
Third spinner -> Item 1 selected

Now, wen I rotate the screen, onItemSelected will be fired 3 times for the initial spinner setup plus 2 times for the spinners that aren't at position 0.

@Override
public void onSaveInstanceState(Bundle outState) {
    int changedSpinners = 0;
    if (spinner1.getSelectedItemPosition() != 0) {
        changedSpinners += 1;
    }
    if (spinner2.getSelectedItemPosition() != 0) {
        changedSpinners += 1;
    }
    if (spinner3.getSelectedItemPosition() != 0) {
        changedSpinners += 1;
    }
    outState.putInt("changedSpinners", changedSpinners);
}

I've saved the state in onSaveInstanceState and then, in onCreateView I checked if savedInstanceState != null and if so, extracted changedSpinners from the bundle and updated my counter to act accordingly.