Android spinner onItemSelected called multiple times after screen rotation
Asked Answered
T

4

11

I have a layout with three spinners. They differ in the option presented in the drop-down.
In my onCreateView I have a method to setup the spinners. Inside that method I have something like this:

  mySpinner = (Spinner) view.findViewById(R.id.my_spinner);
  ArrayAdapter<String> mySpinner =
            new ArrayAdapter<String>(getActivity(), R.layout.background,
                    new ArrayList<String>(Arrays.asList(getResources().getStringArray(R.array.spinner_one_data))));
  mySpinner.setDropDownViewResource(R.layout.spinner_text);
  mySpinner.setAdapter(mySpinner);
  mySpinner.setOnItemSelectedListener(this);

As I said, my other two spinners are almost the same but with different options.

I know that onItemSelected is called once for every spinner in a "first setup" so I have a flag to prevent this problem. With this flag solution, my spinners are working as expected.

The problem is when I select in each spinner an option and then rotate the screen. Now, onItemSelected is called 6 times instead the 3 times that I was expecting (I've set a flag to manage this situation of the 3 times calling).

Why Is it happening and hoe should I handle this?

Telemachus answered 2/1, 2015 at 17:20 Comment(4)
Have you handled screen rotation in your mainefestPeterkin
No. I do not want to change the manifest. Should I do that?Telemachus
Not changing the mainefest but adding a handler to tell the app not to redraw the elements on orientation change otherwise redrawing the elements will fire the methods which I assume is what happens in your casePeterkin
Google this. On orientation change handler - android mainefest.Peterkin
T
1

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.

Telemachus answered 3/1, 2015 at 11:32 Comment(0)
H
37

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);
Hangman answered 11/2, 2015 at 23:42 Comment(4)
Best answer to handle android's worst widget.Englishism
You're a hero. I've been on this for ages. If I could give you 100 up votes I wouldExcept
Excellent!! One of the elegant solution in so!Stearin
This doesn't handle the click for item at position 0.Siliqua
T
2

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
    }
});
Tabanid answered 22/12, 2018 at 5:0 Comment(0)
T
1

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.

Telemachus answered 3/1, 2015 at 11:32 Comment(0)
D
0

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.

Dutybound answered 5/1, 2021 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.