Spinner onItemSelected called erroneously (without user action)
Asked Answered
C

16

29

I have a spinner which I am showing in a dialog view, and the moment the dialog starts onItemSelected is called. I don't really want to process this but only when user makes the selection. So I either need to prevent this (maybe because no default value is set?), or I need to know it is not the user that is making this selection?

Chesna answered 26/2, 2011 at 3:24 Comment(2)
Some code would be helpful. If you are setting a selection programatically this will cause the onItemSelected to get calledStrap
Duplicate of #2562748 which was asked earlier and contains better answers (specifically this one).Anteversion
B
6

Androider, I have found a solution for this problem and posted it here (with code sample):

Spinner onItemSelected() executes when it is not suppose to

Bobettebobina answered 7/5, 2011 at 0:7 Comment(4)
Hahaha, a link to the question that was closed as a duplicate of this question ... is accepted as the answer to this question :)Clung
the pure clarification of the word "Plot Twist"Grus
Actually the word would be "recursion"Astragalus
Infinite loop. .Wandawander
L
45

Another option in the spirit of Bill Mote's solution is to make the OnItemSelectedListener also an OnTouchListener. The user interaction flag can then be set to true in the onTouch method and reset in onItemSelected() once the selection change has been handled. I prefer this solution because the user interaction flag is handled exclusively for the spinner, and not for other views in the activity that may affect the desired behavior.

In code:

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);
Lorilee answered 11/2, 2015 at 23:26 Comment(2)
The bestest solution ever! Saves me from those unwanted item selects while restoring Fragment's state.Oversew
Absolutely the best solution, much better then adding booleans to ignore the 1st selectionLoquacious
H
24

You can simply add an int count to solve it :)

 sp.setOnItemSelectedListener(new OnItemSelectedListener() {
    int count=0;
    @Override
    public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        if(count >= 1){
            int item = sp.getSelectedItemPosition();
            Toast.makeText(getBaseContext(),
                 "You have selected the book: " + androidBooks[item],
                  Toast.LENGTH_SHORT).show();
        }
        count++;
    }


    @Override
    public void onNothingSelected(AdapterView<?> arg0) {
    }
});
Hug answered 11/4, 2012 at 8:23 Comment(3)
In my case, I just used a boolean isLoaded as that made more sense than a counterCubbyhole
Simple and easy solution !Chian
@Mike Yes, unless you have multiple spinners using the same listener.Magdala
H
11

Beginning with API level 3 you can use onUserInteraction() on an Activity with a boolean to determine if the user is interacting with the device.

http://developer.android.com/reference/android/app/Activity.html#onUserInteraction()

@Override
public void onUserInteraction() {
    super.onUserInteraction();
    userIsInteracting = true;
}

As a field on the Activity I have:

private boolean userIsInteracting;

Finally, my spinner:

    mSpinnerView.setOnItemSelectedListener(new OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> arg0, View view, int position, long arg3) {
            spinnerAdapter.setmPreviousSelectedIndex(position);
            if (userIsInteracting) {
                updateGUI();
            }
        }

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

        }
    });

As you come and go through the activity the boolean is reset to false. Works like a charm.

Heir answered 31/7, 2014 at 23:39 Comment(3)
Thanks a lot.. after doing lots of stuff nothing works in my code because adapter data notify at runtime but solution given by you userIsInteracting resolved the issueWarmth
Can you elaborate on "as you come and go through the activity" - because nothing in your code sets userIsInteracting to false.Futile
You are correct. The interaction that is causing the problem has to do with the spinner being initially populated. It erroneously calls onItemSelected() as items are added to the list. If the list is already populated then you're probably not adding anything again and/or you've left the activity and you're coming back through onCreate() which would reset the value.Heir
B
6

Androider, I have found a solution for this problem and posted it here (with code sample):

Spinner onItemSelected() executes when it is not suppose to

Bobettebobina answered 7/5, 2011 at 0:7 Comment(4)
Hahaha, a link to the question that was closed as a duplicate of this question ... is accepted as the answer to this question :)Clung
the pure clarification of the word "Plot Twist"Grus
Actually the word would be "recursion"Astragalus
Infinite loop. .Wandawander
G
4

I have solved this problem a different way as I noticed sometimes the onItemSelected method was called twice on orientation change or sometimes once.

It seemed to depend on the current spinner selection. So a flag or counter didn't work for me. Instead I record the system time in BOTH onResume() and onCreate using widgetsRestartTime = System.currentTimeMillis()

widgetsRestartTime is declared as a double and as an intance variable.

Now in the onItemSelected() method I check the current time again and subtract widgetsRestartTime from it as follows: if (System.currentTimeMillis() - widgetsRestartTime > 200) { // user triggered event - so this is a real spinner event so process it } else { // system generated event e.g. orientation change, activity startup. so ignore }

Garrett answered 27/10, 2011 at 20:39 Comment(0)
L
3

I had the same problem, and I solved it by remembering the previous selected spinner value. On each onItemSelected call I compare the new value with the previous, and if it's the same I don't process the code further.

Largehearted answered 18/5, 2012 at 14:19 Comment(1)
I too did the same as i had to remember the postiion for some other reasons. Thanks buddy for the hintVega
A
3

I had the same problem. But the accepted solution didn't work for me because I have multiple spinners on my form, and some of them are hidden, and when the spinners are hidden, the listener does not fire, so counting is not a solution for me.

I have found another solution though:

  1. Set a default TAG on spinner while initializing it, in onCreate() of your activity or onCreateView of your Fragment, and before setting the listener
  2. In the listener, check the tag, if it is present, then delete it and do not execute the code, by returning.

That's it, it works like a charm!

Example in the listener:

        if(((String)parent.getTag()).equalsIgnoreCase(TAG_SPINNERVALUE))
        {
            parent.setTag("");
            return;
        }

P.S. And this works for the same listener set to multiple spinners!

Assignee answered 2/7, 2012 at 9:23 Comment(2)
I don't understand why you compare the content of the String. I think it is more elegant to set a constant TAG_INITIALIZING="";(or any other object) and then to compare with `` if (parentView.getTag() == TAG_INITIALIZING) {parentView.setTag(null);return;}``Dread
Ye, but you forget that next time you must check against null, and if anywhere else you don't do this check, you will get NullReferenceException. It's more safe to use like that. Also, like this, you can reuse the tag for any other string value, for example I need such a thing in my app :) (i.e. I check against multiple values of the tag)Assignee
R
2

Yes what you are seeing is correct and is the default behaviour,You cannot prevent this. The OnItemSelected callback is called on initial load. After that initial load it is only triggered whenever user changes selection or if you change the selection from within your code. You can have a indicator which can tell you whether the event was a result of initial load and ignore the first event if you do not want to process it.

Router answered 26/2, 2011 at 17:35 Comment(8)
I suppose the indicator would be a flag of some sort inside the onclick.Chesna
@Chesna Could you tell me please how did you solved this? I tried with a flag in onCreate(). It works when the application is first created but I have problem after orientation change. Thank youEtherege
new View.OnClickListener() { Boolean autoSelected=true; // declare here public void onClick(View v) {Chesna
someSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int index, long arg3) {Chesna
if (autoSelected==false){ autoSelected=true; viewDialog.cancel(); }else { autoSelected=false; }Chesna
where someSpinner is pulled out of ViewOnClickListner from dialog viaChesna
Spinner someSpinner = (Spinner) dialogView. findViewById(R.id.viewSpin);Chesna
What do you mean by "what you are seeing is correct"? Do you mean the OP has correctly observed the actual behavior, or do you mean the behavior is correct (according to the documentation)? The documentation says that setOnItemSelectedListener() will "Register a callback to be invoked when an item in this AdapterView has been selected." One could argue that an item in the adapterview gets selected internally during initialization, but that's not clear, intuitive, nor guaranteed from what the doc says.Magdala
S
2

If you also are shopping for an initially unselected Spinner, you can go with

@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
    if(pos != 0) {
        pos--;

        // your code here
    }
}

Since item 0 is never possible to select. Cheers :-)

Swami answered 23/6, 2013 at 16:10 Comment(3)
This is definitely the best solution - will work for multiple spinners as well - thanks :-)Subgenus
I think you're assuming that the item at position 0 is the prompt. Even so, what if the user changes his mind, and decides to select a second time. Then his choice will be bumped down one? Not desirable behavior. I liked the concept though, and implemented with a boolean field instead.Linettelineup
I believe the NothingSelectedSpinnerAdapter never changes the number of items, however uses isEnabled(int position) false for index 0.Swami
M
2

It worked for me,

private boolean isSpinnerInitial = true;

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

    if(isSpinnerInitial)
    {
        isSpinnerInitial = false;
    }
    else  {
        // do your work...
    }

}
Manvel answered 21/1, 2015 at 8:42 Comment(0)
L
1

I also looked for a good solution on the internet but didn't find any that satisfied my needs. So I've written this extension on the Spinner class so you can set a simple OnItemClickListener, which has the same behaviour as a ListView.

Only when an item gets 'selected', the onItemClickListener is called.

Have fun with it!

public class MySpinner extends Spinner
{
    private OnItemClickListener onItemClickListener;


    public MySpinner(Context context)
    {
        super(context);
    }

    public MySpinner(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public MySpinner(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener inOnItemClickListener)
    {
        this.onItemClickListener = inOnItemClickListener;
    }

    @Override
    public void onClick(DialogInterface dialog, int which)
    {
        super.onClick(dialog, which);

        if (this.onItemClickListener != null)
        {
            this.onItemClickListener.onItemClick(this, this.getSelectedView(), which, this.getSelectedItemId());
        }
    }
}
Loreenlorelei answered 3/1, 2012 at 15:20 Comment(2)
It looks like it may fail on HC and ICS. Has anyone seen that before?Theosophy
It does fail on ICS, seems it only works on Gingerbread_MR1 and below.Varden
S
1

You should do like this.The sequence is the key.

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Santasantacruz answered 14/3, 2015 at 1:42 Comment(0)
M
1

You can try to set the spinner using two arguments, like this:

spinner.setSelection(count, false);

So, put this before the set OnItemSelectedListener :

spinner.setSelection(0,false);

You can check more from the developers page:

https://developer.android.com/reference/android/widget/Spinner.html

Mclyman answered 13/3, 2018 at 14:45 Comment(0)
O
0

If you dont mind using a position for promt you can do something like this every time you want to do staff on the spinner. First set selection to the promt:

spinner.setselected(0);
spinner.add("something");
...

And then do something like that when selection happens:

spinner.ItemSelected += (object sender, AdapterView.ItemSelectedEventArgs e) => 
            {
                if (spinner.SelectedItemPosition != 0)
                {
                   //do staff
                } 
            }
Oceanography answered 9/2, 2017 at 22:30 Comment(0)
T
0

The issue is that we are setting listener before the spinner has done rendering. And once it renders, it does select it's datasets 1st option automatically (which is fine), but that causes our listener to trigger.

The solution is to wait for the specific spinner to be laid out, and only then set the listener:

binding.spinnerLanguage.post( new Runnable() {
    @Override
    public void run() {
        binding.spinnerLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                GeneralUtils.log(TAG, "finally, not triggering on initial render");
            }

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

            }
        });
    }
});
Teenager answered 9/7, 2021 at 8:47 Comment(0)
D
0

This is the Kotlin version of Andres Q's answer with some improvements.

Anonymous interface implementation:

val listener = object : AdapterView.OnItemSelectedListener, OnTouchListener
{
    var userSelect: Boolean = false

    override fun onItemSelected(
        parent: AdapterView<*>?,
        view: View?,
        position: Int,
        id: Long
    )
    {
        if (userSelect)
        {
            // Your selection handling code here
            userSelect = false
        }
    }

    override fun onNothingSelected(parent: AdapterView<*>?)
    {
        //nothing to do
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(view: View?, event: MotionEvent?): Boolean
    {
        if (event?.action == MotionEvent.ACTION_DOWN)
        {
            userSelect = true
        }
        return false
    }
}

Then, Set the listener to the spinner as both an OnItemSelectedListener and an OnTouchListener:

aSpinner.setOnTouchListener(listener)
aSpinner.onItemSelectedListener = listener
Delude answered 27/7, 2023 at 12:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.