Android Spinner's onItemSelected executed twice when going back to Fragment
Asked Answered
D

3

8

I created a spinner for my Fragment that populates it with data retrieved from an HTTP callout. When the Fragment is first created, I populate the spinner with its selection choices, set its setOnItemSelectedListener and set its initial selection in onCreateView().

    stateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

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


            if (spinnerPosition != position)
            {
                spinnerPosition = position;
                TextView stateSelected = (TextView) view;
                String stateSelectedStr = stateSelected.getText().toString();


                LinearLayout ballotsDisplay = (LinearLayout) getActivity().findViewById(R.id.ballotsDisplay);
                ballotsDisplay.removeAllViews();

                Map<String, String> calloutParams = new HashMap<String, String>();
                calloutParams.put("state", stateSelectedStr);

                // Create and execute AsyncTask to retrieve ballots
                new RetrieveBallots().execute(calloutParams);
            }
        }

        public void onNothingSelected(AdapterView<?> parent) {
            return;
        }
    });

    // Set default selection for spinner
    int defaultState = adapter.getPosition(userState);

    if (defaultState == -1)
    {
        defaultState = 0;
    }

    stateSpinner.setSelection(defaultState);

When the Fragment is created, everything works well, the spinner position is set to its default and the spinner item is selected once as shown in the log below:

5009-5009/com.project.test D/TEST﹕ onCreateView called
5009-5009/com.project.test D/TEST﹕ stateSpinner.setSelection
5009-5009/com.project.test D/TEST﹕ onActivityCreated called
5009-5009/com.project.test D/TEST﹕ onResume called
5009-5009/com.project.test D/TEST﹕ spinner item selected

The problem occurs when I navigate away from the Fragment to another fragment (I store the fragment onto backstack). When I click back to go back to my original Fragment, the spinner seems to have its item selected twice:

5009-5009/com.project.test D/TEST﹕ onCreateView called
5009-5009/com.project.test D/TEST﹕ stateSpinner.setSelection
5009-5009/com.project.test D/TEST﹕ onActivityCreated called
5009-5009/com.project.test D/TEST﹕ onResume called
5009-5009/com.project.test D/TEST﹕ spinner item selected
5009-5009/com.project.test D/TEST﹕ spinner item selected

So I have 2 questions:

1) Why does the spinner register 2 item selection occurrences when returning to it from the Back button.

2) Is there a fix to prevent 2 item selections from occurring? Right now the fragment is being populated with duplicate data since it retrieves the data twice.

** EDIT ** After changing to stateSpinner.setSelection(defaultState, false), I would get a null Pointer exception at ballotsDisplay.removeAllViews(); seems like ballotsDisplay is set to null for some reason with that change

stacktrace:

05-15 07:25:48.303 6153-6153/com.poliseewriters.polisee E/AndroidRuntime﹕ FATAL EXCEPTION: main java.lang.NullPointerException at com.polisee.ballotmeasures.BallotMeasuresFragment$1.onItemSelected(BallotMeasuresFragment.java:287) at android.widget.AdapterView.fireOnSelected(AdapterView.java:882) at android.widget.AdapterView.selectionChanged(AdapterView.java:865) at android.widget.AdapterView.checkSelectionChanged(AdapterView.java:1017) at android.widget.Spinner.layout(Spinner.java:363) at android.widget.AbsSpinner.setSelectionInt(AbsSpinner.java:292) at android.widget.AbsSpinner.setSelection(AbsSpinner.java:269) at com.polisee.ballotmeasures.BallotMeasuresFragment.setStateSpinner(BallotMeasuresFragment.java:314) at com.polisee.ballotmeasures.BallotMeasuresFragment.onCreateView(BallotMeasuresFragment.java:201) at android.support.v4.app.Fragment.performCreateView(Fragment.java:1786) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:953) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1136) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:739) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1499) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:456) at android.os.Handler.handleCallback(Handler.java:605) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4441) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method)

** EDIT: Updated code to prevent onItemSelected from executing twice, needed to add null check for ballotsDisplay **

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    if (savedInstanceState != null) {
        Log.d("TEST", "bundle = " + savedInstanceState.toString());
    }

    Log.d("TEST", "onCreateView called");

    View view = (View) inflater.inflate(R.layout.fragment_ballot_measures, container, false);

    setStateSpinner(view);

    return view;
}


private void setStateSpinner(View view) {

    try {
        states = Utilities.getAllStateNames();
    }
    catch (Exception e) {
        Log.e("Error", "Error retrieving names: " + e.getMessage());
    }

    Spinner stateSpinner = (Spinner) view.findViewById(R.id.stateSpinner);

    final ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), R.layout.ballotmeasures_state_spinner, states);
    adapter.setDropDownViewResource(R.layout.ballotmeasures_state_spinner_dropdown);

    stateSpinner.setAdapter(adapter);

    stateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

        // Callback method to invoke when a state has been selected
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
        {
            Log.d("TEST", "spinner item selected");

                TextView stateSelected = (TextView) view;
                String stateSelectedStr = stateSelected.getText().toString();

                // Remove all currently displayed views in the layout
                LinearLayout ballotsDisplay = (LinearLayout) getActivity().findViewById(R.id.ballotsDisplay);
                if (ballotsDisplay != null)
                {
                    ballotsDisplay.removeAllViews();
                }

                Map<String, String> calloutParams = new HashMap<String, String>();
                calloutParams.put("state", stateSelectedStr);

                // AsyncTask to execute data retrieval 
                new RetrieveBallots().execute(calloutParams);
            }

        public void onNothingSelected(AdapterView<?> parent) {
            return;
        }
    });

    // Set default selection for spinner
    int defaultState = adapter.getPosition(userState);

    if (defaultState == -1)
    {
        defaultState = 0;
    }

    Log.d("TEST", "stateSpinner.setSelection");
    stateSpinner.setSelection(defaultState, false);
}
Doughboy answered 15/5, 2015 at 6:49 Comment(0)
S
10

Use stateSpinner.setSelection(defaultState, false); in place of stateSpinner.setSelection(defaultState);

Swaggering answered 15/5, 2015 at 7:3 Comment(9)
I did what you said but now i get a nullpointerexception thrown at: LinearLayout ballotsDisplay = (LinearLayout) getActivity().findViewById(R.id.ballotsDisplay); ballotsDisplay.removeAllViews(); Any idea why this is happening now after using setSelection(...,false)?Doughboy
Please check if ballotsDisplay is getting inflated properly. Seems like its null.Swaggering
It does. If I switch it back to stateSpinner.setSelection(default), I don't receive that null pointer exception. Everything works fine, except for the double item selection when i hit back. I inflate the entire layout (ballotsDisplay is in it) before I set the spinner. Only when i change it to setSelection(default, false) does this exception occurDoughboy
Can you please post your stacktrace.Swaggering
check the top post forstack traceDoughboy
what is the key difference between setSelection(int position) vs. setSelection(int position, boolean animate)? Looked through the Spinner documentation and the animate parameter isn't really explained.Doughboy
If you use the later one with animated parameter onItemSelected will not be called when you do setSelection. If you don't use the animated param setSelection will immediately call onItemSelected.Swaggering
Ah okay. Do you know why the ballotsDispaly is null during the creation of the fragment if I use setSelection(int, boolean), but is NOT null if I use setSelection(int)? I can't seem to figure this part out either.Doughboy
Thanks for you help in this Manish. I posted the code for the class. The part I am confused about is why ballotsDisplay would be null (I added a null check for it) when i use setSelection(int, boolean). However, it is not null when I use setSelection(int). This situation only occurs during the initial creation of the fragment.Doughboy
S
9

The problem is the onItemSelected() callback gets called twice by Android Framework (maybe by design), the first time the view parameter is null, and the second time it is instantiated.

You cannot prevent 2 item selections from occurring, but you can check if the view variable is null, if not, do the rest things.

Selfregulating answered 15/5, 2015 at 8:22 Comment(1)
For some reason when using a Spinner with an OnItemSelectedListener in Kotlin you can end up with some weird crashes, because of this issue and because using an OnItemSelectedListener as an anonymous class (instantiation directly on the setOnItemSelectedListener Android Studio didn't make the View nullable, which caused an exception on runtime, because Kotlin expected the View to be non-null, which it wasn't obviously. Thanks, you pointed me in the right direction :-)Copalm
D
1

Add condition view!=null in ItemSelectedListener like this:

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

                        if(view!=null)
                        {//write your code
                        }
                    }

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

                    }
                });
Device answered 13/12, 2019 at 11:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.