4

Previously, in project targeted KitKat, I have a fairly simple ArrayAdapter being used in Spinner without any trouble.

CountryArrayAdapter.java

public class CountryArrayAdapter extends ArrayAdapter<Country> {
    private static class ViewHolder0 {
        public TextView textView0;
    }

    private static class ViewHolder1 {
        public CheckedTextView checkedTextView0;
    }

    private static List<Country> getValidCountries() {
        List<Country> countries = new ArrayList<Country>(Arrays.asList(Country.values()));
        return Collections.unmodifiableList(countries);
    }

    public CountryArrayAdapter(Context context) {
        super(context, R.layout.country_spinner_item, validCountries);
        this.setDropDownViewResource(R.layout.country_spinner_dropdown_item);
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;

        if (rowView == null) {
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.country_spinner_dropdown_item, null);
            ViewHolder1 viewHolder1 = new ViewHolder1();
            viewHolder1.checkedTextView0 = (CheckedTextView)rowView.findViewById(R.id.checked_text_view_0);

            rowView.setTag(viewHolder1);
        }

        final ViewHolder1 holder = (ViewHolder1)rowView.getTag();
        final CheckedTextView checkedTextView0 = holder.checkedTextView0;

        Country country = validCountries.get(position);
        checkedTextView0.setText(country.humanString);
        checkedTextView0.setCompoundDrawablesWithIntrinsicBounds(country.icon, 0, 0, 0);

        return rowView;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;

        if (rowView == null) {
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.country_spinner_item, null);
            ViewHolder0 viewHolder0 = new ViewHolder0();
            viewHolder0.textView0 = (TextView)rowView.findViewById(R.id.text_view_0);

            rowView.setTag(viewHolder0);
        }

        final ViewHolder0 holder = (ViewHolder0)rowView.getTag();
        final TextView textView0 = holder.textView0;

        Country country = validCountries.get(position);
        textView0.setText(country.humanString);
        textView0.setCompoundDrawablesWithIntrinsicBounds(country.icon, 0, 0, 0);

        return rowView;
    }

    private static final List<Country> validCountries = getValidCountries();
}

After migrating to project targeted Marshmallow, we would get the following when clicking on the spinner.

java.lang.NullPointerException
    at android.widget.TextView.checkForRelayout(TextView.java:6556)
    at android.widget.TextView.setText(TextView.java:3813)
    at android.widget.TextView.setText(TextView.java:3671)
    at android.widget.TextView.setText(TextView.java:3646)
    at com.example.yccheok.bug.CountryArrayAdapter.getDropDownView(CountryArrayAdapter.java:55)

NPE exception is being thrown at

checkedTextView0.setText(country.humanString);

This is how the ArrayAdapter being used.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Spinner countrySpinner = (Spinner)this.findViewById(R.id.country_spinner);
        final CountryArrayAdapter countryArrayAdapter = new CountryArrayAdapter(this);
        countrySpinner.setAdapter(countryArrayAdapter);
    }
}

The 2 XML files, which is being used in getDropDownView and getView are fairly simple. I don't see how they can go wrong.

country_spinner_dropdown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/checked_text_view_0"
    style="?android:attr/spinnerDropDownItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:minHeight="48dp"
    android:drawablePadding="10dp" />

country_spinner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text_view_0"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:drawablePadding="10dp"
    android:gravity="center_vertical" />

A minimal project to demonstrate the problem can be downloaded from : https://www.dropbox.com/s/k5jvodoyh6ux0ka/bug.zip?dl=0

Any idea what's goes wrong on my ArrayAdapter?

A "hack" solution

I can easily prevent crash, by modify the code in getDropDownView,

from

viewHolder1.checkedTextView0 = (CheckedTextView)rowView.findViewById(R.id.checked_text_view_0);
rowView.setTag(viewHolder1);

to

viewHolder1.checkedTextView0 = (CheckedTextView)rowView.findViewById(R.id.checked_text_view_0);
viewHolder1.checkedTextView0.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.LayoutParams.WRAP_CONTENT));
rowView.setTag(viewHolder1);

I get this idea from NullPointerException at TextView.checkForRelayout() while setText()
But it still doesn't explain why it doesn't happen on KitKat target. Also, the proposed solution looks like a "hack" to me.

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

2 Answers2

6

Code has been added recently which relies on spinner item views having their LayoutParams properly set. All views are expected to have LayoutParams for proper layouting anyway. If you don't specify the parent view when inflating your layout, the inflater can not set the LayoutParams that are specified for the root element in your XML layout. This is the proper fix:

rowView = inflater.inflate(R.layout.country_spinner_dropdown_item, parent, false);

and also

rowView = inflater.inflate(R.layout.country_spinner_item, parent, false);
BladeCoder
  • 12,779
  • 3
  • 59
  • 51
0

Try to change your view holder to this

//RecyclerView holder that holds the structure of single item
static class ViewHolder1 extends RecyclerView.ViewHolder {
    TextView checkedTextView0;

    public ViewHolder1(View itemView) {
        super(itemView);
        checkedTextView0 = (TestView) itemView.findViewById(R.id.your_text_view)
    }
}
Ivan V
  • 3,024
  • 4
  • 26
  • 36