1

I have a layout, default_label.xml, like so:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/label"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Then, I have this class, which basically allows me to set the default button text for a spinner:

public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {

    protected static final int EXTRA = 1;
    protected SpinnerAdapter adapter;
    protected Context context;
    protected int nothingSelectedLayout;
    protected int nothingSelectedDropdownLayout;
    protected LayoutInflater layoutInflater;
    protected TextView label;

    /**
     * Use this constructor to have NO 'Select One...' item, instead use
     * the standard prompt or nothing at all.
     *
     * @param spinnerAdapter        wrapped Adapter.
     * @param nothingSelectedLayout layout for nothing selected, perhaps
     *                              you want text grayed out like a prompt...
     * @param context               Context
     */
    public NothingSelectedSpinnerAdapter(
            SpinnerAdapter spinnerAdapter,
            int nothingSelectedLayout, Context context) {

        this(spinnerAdapter, nothingSelectedLayout, -1, context);
    }

    /**
     * Use this constructor to Define your 'Select One...' layout as the first
     * row in the returned choices.
     * If you do this, you probably don't want a prompt on your spinner or it'll
     * have two 'Select' rows.
     *
     * @param spinnerAdapter                wrapped Adapter. Should probably return false for isEnabled(0)
     * @param nothingSelectedLayout         layout for nothing selected, perhaps you want
     *                                      text grayed out like a prompt...
     * @param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
     *                                      the dropdown.
     * @param context                       Context
     */
    public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
                                         int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
        this.adapter = spinnerAdapter;
        this.context = context;
        this.nothingSelectedLayout = nothingSelectedLayout;
        this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
        layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public final View getView(int position, View convertView, ViewGroup parent) {
        // This provides the View for the Selected Item in the Spinner, not
        // the dropdown (unless dropdownView is not set).
        if (position == 0) {
            return getNothingSelectedView(parent);
        }
        return adapter.getView(position - EXTRA, null, parent); // Could re-use
        // the convertView if possible.
    }

    public int getPosition(String value) {
        int index = 0;
        for (int i = 1; i < getCount() + EXTRA; i++) {
            if (getItem(i).equals(value)) {
                index = i;
                break;
            }
        }
        return index;
    }


    public TextView getNothingSelectedView(ViewGroup parent) {
        label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
        return label;
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        // Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
        // Spinner does not support multiple view types
        if (position == 0) {
            return new View(context);
        }
        if (adapter.getItem(position-EXTRA).toString().equals("")){
            View view = nothingSelectedDropdownLayout == -1 ?
                    new View(context) :
                    getNothingSelectedDropdownView(parent);
            view.setEnabled(false);
            view.setOnClickListener(null);
            return view;
        }

        // Could re-use the convertView if possible, use setTag...
        return adapter.getDropDownView(position - EXTRA, null, parent);
    }

    protected View getNothingSelectedDropdownView(ViewGroup parent) {
        return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
    }

    @Override
    public int getCount() {
        int count = adapter.getCount();
        return count == 0 ? 0 : count + EXTRA;
    }

    @Override
    public Object getItem(int position) {
        return position == 0 ? null : adapter.getItem(position - EXTRA);
    }

    @Override
    public int getItemViewType(int position) {
        return 0;
    }

    @Override
    public int getViewTypeCount() {
        return 1;
    }

    @Override
    public long getItemId(int position) {
        return position >= EXTRA ? adapter.getItemId(position - EXTRA) : position - EXTRA;
    }

    @Override
    public boolean hasStableIds() {
        return adapter.hasStableIds();
    }

    @Override
    public boolean isEmpty() {
        return adapter.isEmpty();
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        adapter.registerDataSetObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        adapter.unregisterDataSetObserver(observer);
    }

    @Override
    public boolean areAllItemsEnabled() {
        return false;
    }

    @Override
    public boolean isEnabled(int position) {
        return position != 0; // Don't allow the 'nothing selected'
        // item to be picked.
    }

}

What I want to be able to do is change the text of the TextView, something like:

NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
myAdapter.setText("Custom Label");
mySpinner.setAdapter(myAdapter);

However, when I try adding the following method to the above NothingSelectedSpinnerAdapter class:

public void setText(String text) {
    label.setText(text);
}

I get the following error:

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

What should I change?

  • Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Mosius Oct 12 '18 at 18:17
  • Yeah, no. This is not a duplicate of that basic, non-specific, question. I understand what a NullPointerException is, but I don't understand the causation here, which is why I'm asking. – 6sad1f6asdf Oct 12 '18 at 18:18

1 Answers1

0

Take a look at the following three lines:

NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());

The Adapter is instantiated, label is null

myAdapter.setText("Custom Label");

The TextView label is still null, that's why label.setText(); causes a NullPointerException

mySpinner.setAdapter(myAdapter);

Now the runtime will be able to draw the Spinner items. This involves repeated calling of getView() (at least once for every item which should be drawn). After getView() has been executed for position = 0, you will have assigned a value to label. From now on, you can safely call setText() on it.

If you want to be able to call myAdapter.setText("Custom Label"); whenever you like without having to bother about the inner workings of the Adapter, then you can introduce a field private String mLabelText and implement the method as follows

public void setText(String labelText){
    mLabelText = labelText;
    if (label != null){
        label.setText(labelText);
    }
}

One last step: don't forget to set the text right after initializing label

public TextView getNothingSelectedView(ViewGroup parent) {
    label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
    label.setText(mLabelText);
    return label;
} 
Bö macht Blau
  • 11,639
  • 4
  • 30
  • 53