Undesired onItemSelected calls
Asked Answered
M

5

21

I have 36 spinners that I have initialized with some values. I have used onItemSelectedListener with them. As usual, the user can interact with these spinners, firing the onItemSeected function.

One problem is that the call is made during init, but I found solutions to it here and avoided that using a global variable "count" and checking if count > 36 before executing code inside onItemSelected.

My problem is this: The user has the option to click on a button called "Previous", upon which I have to reset SOME of the spinner values.

I tried changing the value of count to 0 before resetting the spinners, and then changing it back to 37 after resetting, but I have come to understand that the onItemSelected is called only after every other function is done executing, so it is called AFTER count is changed back to 37 even though the spinner values are set as soon as they are selected by user.

I need to repeatedly refresh some spinners WITHOUT firing off the onItemSelected function. Can anyone please help me find a solution? Thanks.

Miter answered 13/2, 2014 at 7:36 Comment(4)
Check this question: #2562748 and try following Brad's suggestion about using setSelection().Desultory
I have already tried using setSelection() with false. The code inside onItemSelected was still executed.Miter
@Darth VedarYou can disable and enable the spinner as per your requirement using method setEnabled(true/false);Morbihan
@Morbihan Is this what you are talking about? spin.setEnabled(false); spin.setSelection(pos); spin.setEnabled(true); onItemSelected is still getting called.Miter
M
78

I found a simple and, I think, elegant solution. Using tags. I first created a new XML file called 'tags' and put in the following code:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
  <item name="pos" type="id" />
</resources>

Whenever I myself use spin.setSelection(pos), I also do spin.setTag(R.id.pos, pos), so I am setting the current position as a tag.

Then, in onItemSelected, I am executing code only if(spin.getTag(R.id.pos) != position), where position is the position variable supplied by the function. In this way, my code is executed only when the user is making a selection. Since the user has made a selection, the tag has not been updated, so after the processing is done, I update the tag as spin.setTag(R.id.pos, position).

NOTE: It is important to use the same adapter throughout, or the "position" variable might point to different elements.

EDIT: As kaciula pointed out, if you're not using multiple tags, you can use the simpler version, that is spin.setTag(pos) and spin.getTag() WITHOUT the need for an XML file.

Miter answered 13/2, 2014 at 10:19 Comment(9)
Good solution! Works fine. I tried many things before. Thanks!Akiko
No problem! Glad it worked for you :) Please one up the question AND answer. It might help someone else :)Miter
You can also use the simplified version of setTag(pos) if you do not need to attach several tags to the spinner. This way you do no need to create the xml file for the key.Quag
I have implement the code here: pastebin.com/Jv1Wc70D in case someone needs it.Raul
I'm afraid this doesn't work when other parts of android call setSelection outside of your code, for instance when the view is first inflated or reinflated. Anyone have any ideas about that?Boothe
I have this problem inside a ViewPager. My solution is going to be to keep the last selected value for every spinner in a map<viewId, selection> as I can have 0 to * spinners, then retrieve the selection by viewId from the map every time the callback is triggered. If it hasn't changed, nothing should happen.Superego
Interesting. That should be faster than file I/O, but where do you maintain this Map? I doubt it would work if it was on the main thread.Miter
This is clever. Thank you.Keli
I think that there is another solution using this "tag" method. I mean you can add an OnTouchListener on the spinner, when triggered you know that the user has press the spinner so you can set the tag to a default value (for example 1). Then in the OnSelectedItemListener you check if tag == 1, if yes you do what u need to do when the user change the item; before the function end you set the tag to 0 so the "OnSelectedItemListener" will be "virtually disabled" until the user clicks on the spinner.Strainer
S
17

When Spinner.setSelection(position) is used, it always activates setOnItemSelectedListener()

To avoid firing the code twice I use this solution:

     private Boolean mIsSpinnerFirstCall = true;

    ...
    Spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            //If a new value is selected (avoid activating on setSelection())
            if(!mIsSpinnerFirstCall) {
                // Your code goes gere
            }
            mIsSpinnerFirstCall = false;
        }

        public void onNothingSelected(AdapterView<?> arg0) {
        }
    });
Scrambler answered 22/10, 2014 at 10:58 Comment(0)
M
5

I don't know if this solution is as foolproof as the chosen one here, but it works well for me and seems even simpler:

boolean executeOnItemSelected = false;
spinner.setSelection(pos)

And then in the OnItemSelectedListener

public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    if(executeOnItemSelected){
        //Perform desired action
    } else {
        executeOnItemSelected = true;
    }
}
Musca answered 18/2, 2018 at 12:10 Comment(0)
L
2

The way I solved this is by saving the OnItemSelectedListener first. Then set the OnItemSelectedListener of the Spinner to the null value. After setting the item in the Spinner by code, restore the OnItemSelectedListener again. This worked for me.

See code below:

        // disable the onItemClickListener before changing the selection by code. Set it back again afterwards
        AdapterView.OnItemSelectedListener onItemSelectedListener = historyPeriodSpinner.getOnItemSelectedListener();
        historyPeriodSpinner.setOnItemSelectedListener(null);
        historyPeriodSpinner.setSelection(0);
        historyPeriodSpinner.setOnItemSelectedListener(onItemSelectedListener);
Lactone answered 15/9, 2016 at 15:17 Comment(1)
This is a good idea, but setting the (non-null) listener after calling setSelection() still results in the listener's being called.Filaria
B
1

Here's my solution to this problem. I extend AppCompatSpinner and add a method pgmSetSelection(int pos) that allows programmatic selection setting without triggering a selection callback. I've coded this with RxJava so that the selection events are delivered via an Observable.

package com.controlj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;

import io.reactivex.Observable;

/**
 * Created by clyde on 22/11/17.
 */

public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
    private int lastSelection = INVALID_POSITION;


    public void pgmSetSelection(int i) {
        lastSelection = i;
        setSelection(i);
    }

    /**
     * Observe item selections within this spinner. Events will not be delivered if they were triggered
     * by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
     *
     * @return an Observable delivering selection events
     */
    public Observable<Integer> observeSelections() {
        return Observable.create(emitter -> {
            setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                    if(i != lastSelection) {
                        lastSelection = i;
                        emitter.onNext(i);
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapterView) {
                    onItemSelected(adapterView, null, INVALID_POSITION, 0);
                }
            });
        });
    }

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

    public FilteredSpinner(Context context, int mode) {
        super(context, mode);
    }

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

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
        super(context, attrs, defStyleAttr, mode);
    }
}

An example of its usage, called in onCreateView() in a Fragment for example:

    mySpinner = view.findViewById(R.id.history);
    mySpinner.observeSelections()
        .subscribe(this::setSelection);

where setSelection() is a method in the enclosing view that looks like this, and which is called both from user selection events via the Observable and also elsewhere programmatically, so the logic for handling selections is common to both selection methods.

private void setSelection(int position) {
    if(adapter.isEmpty())
        position = INVALID_POSITION;
    else if(position >= adapter.getCount())
        position = adapter.getCount() - 1;
    MyData result = null;
    mySpinner.pgmSetSelection(position);
    if(position != INVALID_POSITION) {
        result = adapter.getItem(position);
    }
    display(result);  // show the selected item somewhere
}
Baxter answered 22/11, 2017 at 12:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.