Android Spinner : Avoid onItemSelected calls during initialization
Asked Answered
T

21

214

I created an Android application with a Spinner and a TextView. I want to display the selected item from the Spinner's drop down list in the TextView. I implemented the Spinner in the onCreate method so when I'm running the program, it shows a value in the TextView (before selecting an item from the drop down list).

I want to show the value in the TextView only after selecting an item from the drop down list. How do I do this?

Here is my code:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class GPACal01Activity extends Activity implements OnItemSelectedListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Spinner spinner = (Spinner) findViewById(R.id.noOfSubjects);

        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,R.array.noofsubjects_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);
    }

    public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
        TextView textView = (TextView) findViewById(R.id.textView1);
        String str = (String) parent.getItemAtPosition(pos);
        textView.setText(str);
    }

    public void onNothingSelected(AdapterView<?> arg0) {
        // TODO Auto-generated method stub

    }
}
Tiller answered 15/11, 2012 at 12:55 Comment(10)
spinners always have a default selected value , if you want to have a spinner with no default selected value , you should create your custom spinner, or create a custom spinner with a empty entry , and in the getView() method , change the visibility of the raw layout to GONEFuttock
How come such an annoying bug still exists in the end of 2018???Phagocyte
@Phagocyte You mean 2020? ;)County
@County you mean 2021?Pietro
Here I've found Perfact answer for this. https://mcmap.net/q/81769/-android-spinner-avoid-onitemselected-calls-during-initializationDanidania
@Phagocyte It's 2021 and the bug is still thereWigwam
@NiravBhandari Thanks, I know various workarounds for this, but why should we do a workaround on Google's bug for already almost a decade?Phagocyte
still in 2022 :)Unpractical
@Phagocyte it's feb 2023 bug is still thereGlinys
@Phagocyte it's september 2023 now, and the bug is still thereElutriate
K
209
spinner.setOnItemSelectedListener(this); // Will call onItemSelected() Listener.

So first time handle this with any Integer value

Example: Initially Take int check = 0;

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
   if(++check > 1) {
      TextView textView = (TextView) findViewById(R.id.textView1);
      String str = (String) parent.getItemAtPosition(pos);
      textView.setText(str);
   }
}

You can do it with boolean value and also by checking current and previous positions. See here

Kurys answered 15/11, 2012 at 13:2 Comment(10)
Awesome man..when i first faced this problem i tried implementing custom spinner..still it didn't work..but your solution worked like a charm..Thanks.Gyno
Doubt: should we use - TextView textView = (TextView) findViewById(R.id.textView1); or TextView textView = (TextView) arg1.findViewById(R.id.textView1);?Tatary
TextView textView = (TextView) findViewById(R.id.textView1); Use it because TextView and Spinner both will be in same layoutKurys
I used boolean as flag.. this flag solution worked for me.. Thank You @KurysButz
Where do you declare check? Outside the getView()? Inside the viewHolder? Where? tried your solution but doesn't work for me.Pantheism
its a patch not a solution i guess.Mycology
Unfortunately, the problem with this is that the framework calls this on init of Spinner with the position 0 which is the first elementTransistorize
I am facing the same problem. Is it some kind of bug? also if I select same item repeatedly, listener does not work after first time. It works only if selection item changes. Any comment, help?Berner
I tried this solution with my same problem, but this woks only one, eg: when I have 2 items on dropdown, it woks when I select any spinner item 1st time, when I try 2nd time its not working properlyConchiolin
When I apply this process with koltin my application is destroyed, I am working with a custm spinner.Courbet
D
189

Just put this line before setting the OnItemSelectedListener

spinner.setSelection(0,false)

This works because setSelection(int, boolean) calls setSelectionInt() internally so that when the listener is added, the item is already selected.

Beware that setSelection(int) won't work, because it calls setNextSelectedPositionInt() internally.

Diapause answered 1/6, 2016 at 6:58 Comment(19)
It would be a better answer if you would write how this helps and why.Camouflage
It helps, but wanna how?Pothouse
This is definitely the better answer.Salisbarry
@androiddeveloper what is the disadvantage??Cottonweed
This works because you first set the selection, and then add a listener, but the listener won't be called because you already chosen this selection before. Only new selections will call the listener.Bamako
This works because setSelection(int, boolean) calls setSelectionInt() internally, and you need to set the listener after (instead of before) calling this. Beware that setSelection(int) won't work, because it calls setNextSelectedPositionInt() internally, and this is what led me here.Prepositor
This doesn't work if it's declared during or before onCreateView(). onItemSelected will be called.Mycetozoan
adding the false to the setSelection() fixed my issue, thanks a lot!Imperturbation
Works like a charm!Wheels
Agree with @Bubu, this should be excepted answer. It is the correct solution. Answer from Abhi works but it is just a workaround.Megilp
Great solution! Works for me at least when declared at onViewCreated()Mayman
somehow I need to set itemSelectedListener inside post runnable to make it work. I don't know why.Rill
It worked on some devices but on e.g. a Pixel 4 XL the setting of the SelectionListener code is called too fast that I needed the work around with a boolean value.Destitution
@androiddeveloper Not even close. Just remove animate flag or set it to true and you'll see your listener still gets fired no matter what order you set your selection and listenerUnderthecounter
@Underthecounter Seems the behavior changed for it in the past, or something. Pretty sure I tested it. Otherwise others wouldn't have upvoted it.Bamako
Yes, this is perfect solution.Kinney
This is for sure the best answer.Amal
I still don't understand how a boolean that should affect animation affects onItemSelectedListener... setSelection(0) will trigger the listener, setSelection(0, false) won't. Where false just means "don't animate"... android always blows my mindHeyerdahl
This actually works for me. I found such a solution after a few years.Withdrew
M
70

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.

Mackle answered 31/7, 2014 at 23:42 Comment(10)
Good one Bill..I think this is better solution than the one accepted as answerSuburbicarian
where do you get setmPreviousSelectedIndex ?!?!Glottalized
Typo? :) setPreviousSelectedIndex()Mackle
This won't work in case of nest fragments as onUserInteraction is Activity method. Any other solution?Pascale
Where do I set userIsInteracting to false? I've multiple spinners on screenQuickie
This is working except for the scenario, I cann't pick up the first element itself, I want if I pick the initial item of spinner(then also i am interacting) but its not calling at all.Civics
@Pascale you could pass a mutable boolean.Mouseear
@Quickie do you need to set it to false? Perhaps in onCreate before adding any of the listeners (which I believe is where the issue is happening)?Mouseear
@ErikB nvm, figured it out. Setting it false inside listener works fine.Quickie
@ErikB I would like to know if user interacted with Fragment, not Activity. The activity can have multiple fragments, so he could interact with Activity previously on another Fragment.Timmi
M
27

This worked for me

Spinner's initialization in Android is problematic sometimes the above problem was solved by this pattern.

Spinner.setAdapter();
Spinner.setSelected(false);  // must
Spinner.setSelection(0,true);  //must
Spinner.setonItemSelectedListener(this);

Setting adapter should be first part and onItemSelectedListener(this) will be last when initializing a spinner. By the pattern above my OnItemSelected() is not called during initialization of spinner

Mycology answered 23/6, 2017 at 7:53 Comment(0)
P
15

haha...I have the same question. When initViews() just do like this.The sequence is the key, listener is the last. Good Luck !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Paunchy answered 14/3, 2015 at 1:26 Comment(4)
Good One! Nothing Worked for me whatever i have applied before but this one has worked for me like a charm. Thanks @TreeSouthChildbirth
For me worked spinner.setSelection(position, false) used in the same way. With setSelection(position) method the listener was called during initialization.Ingather
@HugoGresse Try calling spinner.setSelection(0,false); . Thing is, now it will ignore the selection of this position, because it's already selectedBamako
Working perfectly. The buggy Android! I wonder how have they implement it in their projects.Berkshire
L
14

To avoid calling spinner.setOnItemSelectedListener() during initialization

spinner.setSelection(Adapter.NO_SELECTION, true); //Add this line before setting listener
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

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

    }
});
Lauro answered 28/11, 2018 at 9:4 Comment(1)
This works in oncreate as well as on resume, was having a problem with on resume with spinner.setSelection(0,false)Gloomy
A
8

My solution:

protected boolean inhibit_spinner = true;


@Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                int pos, long arg3) {

            if (inhibit_spinner) {
                inhibit_spinner = false;
            }else {

            if (getDataTask != null) getDataTask.cancel(true);
            updateData();
            }

        }
Assembly answered 23/9, 2013 at 15:32 Comment(0)
C
6

You can do this by this way:

AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            //set the text of TextView
        }

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

        }
    });

yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            yourSpinner.setOnItemSelectedListener(listener);
        }

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

        }
    });

At first I create a listener and attributed to a variable callback; then i create a second listener anonymous and when this is called at a first time, this change the listener =]

Caduceus answered 11/6, 2014 at 2:30 Comment(0)
L
5

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) { 
            userSelect = false;
            // Your selection handling code here
        }
    }

}

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

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Locally answered 7/3, 2017 at 3:39 Comment(0)
S
4

create a boolean field

private boolean inispinner;

inside oncreate of the activity

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (!inispinner) {
                inispinner = true;
                return;
            }
            //do your work here
        }

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

        }
    });
Sophomore answered 28/2, 2019 at 11:47 Comment(0)
L
4

Try this

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Lazarus answered 21/5, 2019 at 21:55 Comment(1)
100ms is enoughTenerife
B
3

Similar simple solution that enables multiple spinners is to put the AdapterView in a collection - in the Activities superclass - on first execution of onItemSelected(...) Then check to see if the AdapterView is in the collection before executing it. This enables one set of methods in the superclass and supports multiple AdapterViews and therefor multiple spinners.

Superclass ...

private Collection<AdapterView> AdapterViewCollection = new ArrayList<AdapterView>();

   protected boolean firstTimeThrough(AdapterView parent) {
    boolean firstTimeThrough = ! AdapterViewCollection.contains(parent);
    if (firstTimeThrough) {
       AdapterViewCollection.add(parent);
     }
    return firstTimeThrough;
   }

Subclass ...

public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
      if (! firstTimeThrough(parent)) {
        String value = safeString(parent.getItemAtPosition(pos).toString());
        String extraMessage = EXTRA_MESSAGE;
        Intent sharedPreferencesDisplayIntent = new         Intent(SharedPreferencesSelectionActivity.this,SharedPreferencesDisplayActivity.class);
    sharedPreferencesDisplayIntent.putExtra(extraMessage,value);
    startActivity(sharedPreferencesDisplayIntent);
  }
  // don't execute the above code if its the first time through
  // do to onItemSelected being called during view initialization.

}

Bitterling answered 18/7, 2013 at 16:26 Comment(0)
S
3

Code

spinner.setOnTouchListener(new View.OnTouchListener() { 
@Override public boolean onTouch(View view, MotionEvent motionEvent) { isSpinnerTouch=true; return false; }});

holder.spinner_from.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int slot_position, long l) {
                if(isSpinnerTouch)
                {
                    Log.d("spinner_from", "spinner_from");
                    spinnerItemClickListener.onSpinnerItemClickListener(position, slot_position, AppConstant.FROM_SLOT_ONCLICK_CODE);
                }
                else {

                }
            }

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

            }
        });
Sommers answered 17/10, 2019 at 6:37 Comment(0)
S
2

This worked for me:

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
Silver answered 27/9, 2019 at 8:3 Comment(1)
100ms is enoughTenerife
O
1

You could achieve it by setOnTouchListener first then setOnItemSelectedListener in onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
Orwin answered 19/11, 2015 at 8:6 Comment(1)
I love this. Even though it creates a new listener every time a user touches the view. So i prefer to cache the first created listener and reuse it.Straightway
T
1

Based on Abhi's answer i made this simple listener

class SpinnerListener constructor(private val onItemSelected: (position: Int) -> Unit) : AdapterView.OnItemSelectedListener {

    private var selectionCount = 0

    override fun onNothingSelected(parent: AdapterView<*>?) {
        //no op
    }

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        if (selectionCount++ > 1) {
           onItemSelected(position)
        }
    }
}
Tatting answered 6/10, 2019 at 11:8 Comment(0)
D
1

You can create custom OnItemSelectedListener like this. I've taken val check=0 and in onItemSelected() method i did check if its count is 0? If 0 means its called during initialization. So simply ignore it.

I've also called separate abstract method called onUserItemSelected() I'll call this method is check > 0. This works perfectly fine for me.

abstract class MySpinnerItemSelectionListener : AdapterView.OnItemSelectedListener {

abstract fun onUserItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long)
private var check = 0
override fun onItemSelected(
    parent: AdapterView<*>?,
    view: View,
    position: Int,
    id: Long
) {
    if (++check > 1) {
        onUserItemSelected(parent, view, position, id)
    }
}

override fun onNothingSelected(parent: AdapterView<*>?) {}

}

And then you can set listener like this.

mySpinner.onItemSelectedListener =  object : MySpinnerItemSelectionListener() {
        override fun onUserItemSelected(
            parent: AdapterView<*>?,
            view: View?,
            position: Int,
            id: Long
        ) {
            //your user selection spinner code goes here
        }

    }
Danidania answered 28/9, 2021 at 19:47 Comment(0)
K
0

Had the same problem and this works for me:

I have 2 spinners and I update them during init and during interactions with other controls or after getting data from the server.

Here is my template:

public class MyClass extends <Activity/Fragment/Whatever> implements Spinner.OnItemSelectedListener {

    private void removeSpinnersListeners() {
        spn1.setOnItemSelectedListener(null);
        spn2.setOnItemSelectedListener(null);
    }

    private void setSpinnersListeners() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spn1.setOnItemSelectedListener(MyClass.this);
                spn2.setOnItemSelectedListener(MyClass.this);
            }
        }, 1);
    }

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

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

    }
}

When the class is initiating use setSpinnersListeners() instead of directly setting the listener.

The Runnable will prevent the spinner from firing onItemSelected right after the you set their values.

If you need to update the spinner (after a server call etc.) use removeSpinnersListeners() right before your update lines, and setSpinnersListeners() right after the update lines. This will prevent onItemSelected from firing after the update.

Keeter answered 22/10, 2015 at 15:4 Comment(0)
R
0

For me, Abhi's solution works great up to Api level 27.

But it seems that from Api level 28 and upwards, onItemSelected() is not called when listener is set, which means onItemSelected() is never called.

Therefore, I added a short if-statement to check Api level:

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {

            if(Build.VERSION.SDK_INT >= 28){ //onItemSelected() doesn't seem to be called when listener is set on Api 28+
                check = 1;
            }

            if(++check > 1) {
                //Do your action here
            }
        }

I think that's quite weird and I'm not sure wether others also have this problem, but in my case it worked well.

Reginareginald answered 5/3, 2020 at 17:24 Comment(0)
I
0

I placed a TextView on top of the Spinner, same size and background as the Spinner, so that I would have more control over what it looked like before the user clicks on it. With the TextView there, I could also use the TextView to flag when the user has started interacting.

My Kotlin code looks something like this:

private var mySpinnerHasBeenTapped = false

private fun initializeMySpinner() {

    my_hint_text_view.setOnClickListener {
        mySpinnerHasBeenTapped = true //turn flag to true
        my_spinner.performClick() //call spinner click
    }

    //Basic spinner setup stuff
    val myList = listOf("Leonardo", "Michelangelo", "Rafael", "Donatello")
    val dataAdapter: ArrayAdapter<String> = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, myList)
    my_spinner.adapter = dataAdapter

    my_spinner.onItemSelectedListener = object : OnItemSelectedListener {

        override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {

            if (mySpinnerHasBeenTapped) { //code below will only run after the user has clicked
                my_hint_text_view.visibility = View.GONE //once an item has been selected, hide the textView
                //Perform action here
            }
        }

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

Layout file looks something like this, with the important part being that the Spinner and TextView share the same width, height, and margins:

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true" />

            <TextView
                android:id="@+id/my_hint_text_view"
                android:layout_width="match_parent"
                android:layout_height="35dp"                
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true"
                android:gravity="center_vertical"
                android:text="*Select A Turtle"
                android:textColor="@color/green_ooze"
                android:textSize="16sp" />

        </FrameLayout>

I'm sure the other solutions work where you ignore the first onItemSelected call, but I really don't like the idea of assuming it will always be called.

Insectivore answered 30/6, 2020 at 16:13 Comment(0)
S
0

I solved this problem like this: In the activity lifecycle method whose name is onResume(): I added Spinner.setOnItemSelectedListener(this);

As a result, when our spinner call onclick method in the initialize, it does not work. onResume method starts working when the finished Android page is displayed.

Subternatural answered 12/6, 2022 at 11:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.