Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't find spinner after rotating the screen

I'm using 'Fragment' to obtain a layout with two 'fragments' so my class extends 'Fragment'.

I'm loading a 'ListView' on the left 'fragment' and on the right 'fragment' I have another modified 'ListView'. Here, I have a 'Spinner' that I have to change it's color.

I have this code:

private void loadSpinner(int value) {
    //Not relevant code

    adapter = new ArrayAdapter<CharSequence>(getActivity().getApplicationContext(), android.R.layout.simple_spinner_item, data);

    adapter.setDropDownViewResource(R.layout.spinner);

    spinner.setAdapter(adapter);
}

@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    String value = parent.getItemAtPosition(position).toString();

   ((TextView) parent.getChildAt(0)).setTextColor(getResources().getColor(R.color.black));

  //Some code
}

The code above is working as expected UNTIL I rotate the screen of my device. Here, I'm getting a nullpointerexception at ((TextView) parent.getChildAt(0)).setTextColor(getResources().getColor(R.color.black));.

If I comment the above line, since I'm saving the state before rotating the screen, after I rotate the screen, my data is restored and all works as expected EXCEPT the spinner that is on light color thus being poorly visible.

I understand that I can create my own 'spinner' layout and solve this matter but I would like to know how can I solve this.

like image 278
Favolas Avatar asked Dec 29 '13 22:12

Favolas


1 Answers

After screen rotation, your Spinner view has no children views attached yet (you can verify it by calling parent.getChildCount()), thus your parent.getChildAt(0) returns null, resulting in NPE.

Explanations

After activity creation, Android displays its content by calling onMeasure() followed by onLayout() for each view in the layout hierarchy.

Spinner will have its children views created, populated with data from adapter, and attached during call to onLayout(), as you can see here.

So, normally for your Spinner you will have sequence : onMeasure -> onLayout -> onItemSelected
and since onLayout is called before onItemSelected - everything works fine for you during initial activity startup.

Now, lets have a look what happens when you rotate the screen:

Before Activity is destroyed its onSavedInstanceState gets called which propagates the call to Spinner's onSavedInstanceState that will save current selected position.

After rotation, your activity is re-created and then its onRestoreInstanceState is called.

when Activity#onRestoreInstanceState is called it will eventually call to Spinner's onRestoreInstanceState, which has the following code :

    SavedState ss = (SavedState) state;
    //...some code
    if (ss.selectedId >= 0) {
        mDataChanged = true;
        //...some more code
    }

As you can see, it will always set mDataChanged flag if previous state of the spinner was saved with onSavedInstanceState.

Then, onMeasure() is called, which has the following piece of code :

    if (mDataChanged) {
        handleDataChanged();
    }

Implementation of handleDataChanged() will eventually call fireOnSelected(), triggering Spinner's onItemSelected.

Thus onItemSelected gets called during onMeasure, before onLayout.
And as explained above, Spinner has no children before its onLayout is called.

Hope the problem is clear now.

Workaround

To confirm that behaviour and resolve NPE you could simply provide empty implementation for onSavedInstanceState/onRestoreInstanceState in your activity (without call to super implementation) :

@Override 
protected void onSaveInstanceState(Bundle outState) { /* do nothing */ }

This would prevent setting mDataChanged and onMeasure will not trigger fireOnSelected.

Extra note

Not calling Activity super. implementation when overriding onSavedInstanceState/onRestoreInstanceState is definitely not recommended, as a minimum you will not be able to automatically save states of your internal views (including your spinner).
I encourage you to only do that to verify the behavior explained above and to see that NPE is gone.

The preferable solution is to define TextView with desired color for you spinner item in a .xml layout and use it for constructing ArrayAdapter for the spinner.

like image 142
kiruwka Avatar answered Oct 23 '22 03:10

kiruwka