Spinner's onItemSelected callback called twice after a rotation if non-zero position is selected

When I create my activity, I setup a Spinner, assigning it a listener and an initial value. I know that the onItemSelected callback is called automatically during application initialization. What I find strange is that this happens twice when the device is rotated, causing me some problems that I will have to circumvent someway. This does not happen if the spinner initial selection is zero. I was able to isolate the issue, here’s the simplest activity triggering it:

public class MainActivity extends Activity implements OnItemSelectedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.i("Test","Activity onCreate");
    setContentView(R.layout.activity_main);
    ((Spinner)findViewById(R.id.spinner1)).setSelection(2);
    ((Spinner)findViewById(R.id.spinner1)).setOnItemSelectedListener(this);
}
@Override
public void onItemSelected(AdapterView<?> spin, View selview, int pos, long selId)
{
    Log.i("Test","spin:"+spin+" sel:"+selview+" pos:"+pos+" selId:"+selId);
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {}
}

And here’s the logcat shown when the application is started and then the device rotated:

  • How to run apk file online?
  • “Service MeasurementBrokerService is in use” is showing in my application process
  • Corona SDK free alternatives
  • Android: Custom Spinner Layout
  • Setting ActionMode Background programmatically
  • EditText focus inconsistent across layouts
  •     I/Test( 9881): spin:android.widget.Spinner@4052f508 sel:android.widget.TextView@40530b08 pos:2 selId:2
        I/Test( 9881): Activity onCreate
        I/Test( 9881): spin:android.widget.Spinner@40535d80 sel:android.widget.TextView@40538758 pos:2 selId:2
        I/Test( 9881): spin:android.widget.Spinner@40535d80 sel:android.widget.TextView@40538758 pos:2 selId:2
    

    Is this the expected behaviour? Am I missing something?

    Related posts:

    Android Centering Item in RecyclerView
    Every WebKit-based browser crashes sites using Omniture. Why?
    Uploading compress image to server using retrofit
    Android phonegap application having issues with SQlite and local storage on Samsung Galaxy devices
    Set underline text to TextView in android programatically
    What is the difference between a background and foreground service?
  • Drawable advantage over bitmap for memory in android
  • How To Find Android Google Play Services Version
  • Android: Eclipse autocomplete does not work in xml files
  • Create Options Menu for RecyclerView-Item
  • Multiple push messages: The content of the adapter has changed but ListView did not receive a notification
  • How to lock/unlock the screen with Pattern/Password mode in Android?
  • 7 Solutions collect form web for “Spinner's onItemSelected callback called twice after a rotation if non-zero position is selected”

    Managed to find a solution in another stackoverflow question:

    spinner.post(new Runnable() {
        public void run() {
            spinner.setOnItemSelectedListener(listener);
        }
    });
    

    In general, there seem to be many events that trigger the onItemSelected call, and it is difficult to keep track of all of them. This solution allows you to only respond to user-initiated changes using an OnTouchListener.

    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);
    

    This is what i did:

    Do a local variable

    Boolean changeSpinner = true;
    

    On the saveInstanceMethod save the selected item position of the spinner

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("ItemSelect",mySpinner.getSelectedItemPosition());
    }
    

    Then on the activity created get that int from savedInstanceState and if the int is != 0 then set the boolean variable on false;

    @Override
        public void onActivityCreated(Bundle savedInstanceState) {
    
        if (savedInstanceState!=null) {
            if (savedInstanceState.getInt("ItemSelect")!=0) {
               changeSpinner = false;
            }
        }
    
    }
    

    And for last on the OnItemSelected from the spinner do this

    mySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        public void onItemSelected(AdapterView<?> parent,android.view.View v, int position, long id) {
            if (changeSpinner) {
               [...]
            } else {
               changeSpinner= true;
            }
        });
    

    So, the first time when is called is not going to do anything, just make the boolean variable true, and the second time is going to execute the code.
    Maybe not the best solution but it work.

    Just use setSelection(#, false) before setting the listener:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        spinner.setSelection(2, false);
        spinner.setOnItemSelectedListener(this);
    }
    

    The key is the second parameter, that says to not animate the transition, executing the action immediately and then preventing the onItemSelected being fired twice when called in onCreate.

    Try this:

    boolean mConfigChange = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        mConfigChange = false;
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mainf);
    
        Log.i("SpinnerTest", "Activity onCreate");
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.colors,
                android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        ((Spinner) findViewById(R.id.spin)).setAdapter(adapter);
    
         ((Spinner) findViewById(R.id.spin)).setSelection(2);
        ((Spinner) findViewById(R.id.spin)).setOnItemSelectedListener(this);
    
    }
    
    @Override
    protected void onResume() {
        mConfigChange = true;
        super.onResume();
    }
    
    @Override
    public void onItemSelected(AdapterView<?> spin, View selview, int pos, long selId) {
        if (!mConfigChange)
            Log.i("Test", "spin:" + spin + " sel:" + selview + " pos:" + pos + " selId:" + selId);
        else
            mConfigChange = false;
    }
    

    You can just call to setSelection once you know have the list of items and the position to be selected, in that way you avoid onItemSelected to be called twice.

    I’ve created an article about what I think is a better approach How to avoid onItemSelected to be called twice in Spinners

    The first time the onItemSelected runs, the view is not yet inflated. The second time it is already inflated. The solution is to wrap methods inside onItemSelected with if (view != null).

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (view != null) { 
            //do things here
    
        }
    }
    
    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.