Spinner : onItemSelected not called when selected item remains the same
Asked Answered
B

12

56

I have a OnItemSelectedListener for my Spinner, but it is not called when the selected item is the same as the previous one. Apparently the OnClickListener is not an option for a Spinner. I need to catch everytime a user click on an item. Any idea?

Maybe the fact that this Spinner is inside the ActionBar disturbs normal behavior?

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.tracklist_menu, menu);
    Spinner spinner = (Spinner) menu.findItem(R.id.option_ordering_spinner)
            .getActionView();
    spinner.setAdapter(mSpinnerAdapter);
    spinner.setSelection(PrefsHelper.getOrderingSpinnerPos(prefs));
    spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view,
                int position, long id) {
            String str = "selected";
            System.out.println(str);
            if (optionMenuInitialized) {

                switch (position) {
                case 0:
                    // rdm
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_RESHUFFLE_PLAYLIST));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                case 1:
                    // artist
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_ORDER_PLAYLIST_BY_ARTIST));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                case 2:
                    // folder
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_ORDER_PLAYLIST_BY_FOLDER));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                }
                PrefsHelper.setOrderingSpinnerPos(prefEditor, position);
                prefEditor.commit();
            }
            optionMenuInitialized = true;
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });
}
Bina answered 1/6, 2012 at 16:46 Comment(3)
Do you read this post #3928571Unsphere
yup, and that didn't help... anyway, I just found the solution, and am about to write it down here =)Bina
This is not related but setting Spinner visibility to VIEW.GONE also cause this problem.Commonweal
B
90

Ok, I finally found a solution, by creating my own class extending Spinner :

public class MySpinner extends Spinner {
OnItemSelectedListener listener;

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

@Override
public void setSelection(int position) {
    super.setSelection(position);
    if (listener != null)
        listener.onItemSelected(null, null, position, 0);
}

public void setOnItemSelectedEvenIfUnchangedListener(
        OnItemSelectedListener listener) {
    this.listener = listener;
}
}
Bina answered 27/6, 2012 at 13:53 Comment(12)
Can you share some thoughts on how to achieve the same functionality for ActionBar navigation list?Howell
@benoffi7 Where is your answer ?Rafaelof
@dhams Sorry, I make a mistake with my answer and I delete it.Bluma
@Bina This line listener.onItemSelected(null, null, position, 0); give me a NPE, can you please tell me how can i get the AdapterView & View to pass in the above line. ThanksIlljudged
@AlexSemeniuk For everyone who wants to reselect an item in the ActionBar spinner, I found a little solution: https://mcmap.net/q/507110/-reselecting-spinner-item-in-actionbarArchaize
in my case, onitemselectedlistener returns null and does not workHonour
Just a comment that may be useful. In case you are using this custom View in an XML layout you should change the <Spinner> tag for <com.myproject.CustomSpinner>Lott
How do I get the parent and view in listener.onItemSelected(null, null, position, 0);?Flyte
Saved me much time :) Thank you!Rosaniline
I have made a small modification to avoid duplicate events posted in case you've got both listeners: @Override public void setSelection(int position) { int oldSelection = getSelectedItemPosition(); super.setSelection(position); if (this.listener != null && oldSelection == position) { this.listener.onItemSelected(null, null, position, 0); } }Outsize
@Bina How do I call this class. Please help meFurriery
To also get the adapter/parent (adapterView) set following instead of null: listener.onItemSelected(this, getSelectedView(), position, 0); @JesusAlmaral-HackaprendeAlwitt
N
26

I found out this work instead of the one provided

/** Spinner extension that calls onItemSelected even when the selection is the same as its previous value */
public class NDSpinner extends Spinner {

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

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

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

  @Override public void
  setSelection(int position, boolean animate)
  {
    boolean sameSelected = position == getSelectedItemPosition();
    super.setSelection(position, animate);
    if (sameSelected) {
      // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
      getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
    }
  }

  @Override public void
  setSelection(int position)
  {
    boolean sameSelected = position == getSelectedItemPosition();
    super.setSelection(position);
    if (sameSelected) {
      // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
      getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
    }
  }
}
Nickels answered 25/8, 2014 at 2:14 Comment(3)
You will get an NPE if no OnItemSelectedListener is set and the same item is selected.Ronn
This one is working for me too, the accepted answer did notCongratulate
To give credit to the original author: https://mcmap.net/q/272016/-how-can-i-get-an-event-in-android-spinner-when-the-current-selected-item-is-selected-againDomesday
A
6

Here a little better implementation:

public class SpinnerPlus extends Spinner {
    AdapterView.OnItemSelectedListener listener;

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

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        if (listener != null)
            listener.onItemSelected(this, getSelectedView(), position, 0);
    }

    public void setOnItemSelectedEvenIfUnchangedListener(
            AdapterView.OnItemSelectedListener listener) {
        this.listener = listener;
    }
}
Antiseptic answered 25/3, 2016 at 9:10 Comment(0)
C
6

To make your spinner change despite the value of the last index selected just use a:

spinner.setSelection(0); 

before your other selection is called

spinner.setSelection(number); 

this way, the spinner will trigger two times the OnItemSelected event. Just make sure the second time it does whatever you need.

Crispy answered 30/6, 2017 at 22:52 Comment(2)
what if the same item position is 0?Tellford
spinner.setSelection(0); if(number!=0) spinner.setSelection(number);Crispy
P
6

Rewrote the common solution but with:

  1. androidx in mind
  2. extended from AppCompatSpinner
  3. use built-in OnItemSelectedListener listener instead of creating own one
  4. added initial listener call hack

Here:

import android.content.Context;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatSpinner;


public class FixedSpinner extends AppCompatSpinner {
    // add other constructors that you need
    public FixedSpinner(Context context, int mode) {
        super(context, mode);
    }

    private void processSelection(int position) {
        boolean sameSelected = position == getSelectedItemPosition();
        final OnItemSelectedListener listener = getOnItemSelectedListener();
        if (sameSelected && listener != null) {
            // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
            listener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
    }

    @Override
    public void setSelection(int position) {
        processSelection(position);
        super.setSelection(position);
    }

    @Override
    public void setSelection(int position, boolean animate) {
        processSelection(position);
        super.setSelection(position, animate);
    }

    @Override
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        // This hack fixes bug, when immediately after initialization, listener is called
        // To make this fix work, first add data, only then set listener
        // Having done this, you may refresh adapter data as many times as you want
        setSelection(0, false);
        super.setOnItemSelectedListener(listener);
    }
}
Projectile answered 22/8, 2019 at 14:32 Comment(2)
Awesome, thanks. This fixed a problem I had with a spinner.Kortneykoruna
boolean sameSelected = position == getSelectedItemPosition(); Always TRUE, getSelectedItemPosition() method return the same value as position.Indigotin
I
5

Here is a better implementation -

Custom Spinner Class -

import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatSpinner;

public class CSpinner extends AppCompatSpinner {

    private int lastPosition = 0;

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

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        boolean sameSelected = lastPosition == getSelectedItemPosition();
        OnItemSelectedListener onItemSelectedListener = getOnItemSelectedListener();
        if (sameSelected && onItemSelectedListener != null) {
            onItemSelectedListener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
        lastPosition = position;
    }
}

Setting Listener -

spn.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            Log.d("onItemSelected", String.valueOf(position));
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            
        }
    });
Indigotin answered 14/7, 2020 at 11:25 Comment(0)
L
2

If it's still actual, correct call of the callback should be

@Override
public void setSelection(int position) {
    super.setSelection(position);
    if(listener != null)
        listener.onItemSelected(this, getChildAt(position), position, 0);
}
Lillia answered 9/1, 2014 at 17:52 Comment(5)
This is not the correct callback. First, you should use getItemIdAtPosition(position) as the last param, at second getChildAt(position) should not be used in this case. Spinners use View recycling just like ListViews, so getChildAt cannot be used the retrive the position'th item from the adapter.Mitchiner
You're right about the last param to get correct ID. But to get view it's fine to use getChildAt() as we either get a correct view if it exists or null if it doesn't. In most cases the code gets called when an item is clicked and thus visible in a spinner, so getChildAt() returns correct view. At least I've never encountered a situation where wrong view was returned.Lillia
No, you are not correct. For example, the spinner has 100 items, the last is clicked. But there are only 10 visible. In this case, getChildAt() will always return null with 99 position becuase it does not have 100 child Views. You cannot rely on getChildAt() with any AdapterViews. You could call onItemSelected(this, getSelectedView(), position, getItemIdAtPosition(position));.Mitchiner
Maybe I'm mistaken, but do you want to say that getChildAt() returns only views for index 0-9 (for 10 visible items)? I didn't try but it sounds weird. I would expect it should return view from the underlaying adapter for all items in adapter - more specifically it should return view if it's visible or null when it's not. I'll try when I have some spare time which I don't have right now. Spinner with 100 items (index 0-99) will return null when I click 100th item (pos 99)? I don't understand. When I click the last item, then it must be visible and getChildAt(99) must return it. Or not?Lillia
Are you aware of view recycling? There are no Views for every item, only some Views are added which are necessary. That's why getChild(99) will return null. 10 and 99 were only examples, clearly it depends on the current scenario.Mitchiner
S
1

Simplest solution :

spinner.performItemClick(view,position,id)

Schlesien answered 6/7, 2015 at 11:37 Comment(0)
I
0

I had same problem, I solved it by setting onItemSelectedListener every time adapter changes items.

Inexpedient answered 22/7, 2019 at 9:51 Comment(0)
S
0

Further from the answer from @Vishal Yadav, if you want to set an initial position without triggering the OnItemSelectedListener by calling spinner.setSelection(pos, false); then the custom spinner should be:

public class CSpinner extends AppCompatSpinner {

    private int lastPosition = 0;

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

    @Override
    public void setSelection(int position, boolean animate) {
        OnItemSelectedListener listener = getOnItemSelectedListener();
        setOnItemSelectedListener(null);
        super.setSelection(position, animate);
        lastPosition = position;
        setOnItemSelectedListener(listener);
    }

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        boolean sameSelected = lastPosition == getSelectedItemPosition();
        OnItemSelectedListener onItemSelectedListener = getOnItemSelectedListener();
        if (sameSelected && onItemSelectedListener != null) {
            onItemSelectedListener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
        lastPosition = position;
    }
}
Shulamith answered 27/11, 2021 at 15:19 Comment(0)
D
0
 class MySpinner(context: Context, attrs: AttributeSet?) : AppCompatSpinner(context, attrs) {
       
 override fun setSelection(position: Int) {
            if (position == selectedItemPosition) {
                onItemSelectedListener?.onItemSelected(this, selectedView, position, selectedItemId)
            }
            super.setSelection(position)
        }
    }

Works for me, probably can be useful for you guys

Ps. don't forget to add MySpinner to your xml file instead of AppCompatSpinner

Doley answered 24/5 at 14:10 Comment(0)
P
-1

i found a simple solution

just call setAdapter again in place of notifyDataSetChanged for the second spinner

Petree answered 17/5, 2020 at 11:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.