51

How do you create the setError() (similar to that of a TextView/EditText) function for a Spinner? The following doesn't work:

I tried extending the Spinner class and in the constructor:

ArrayAdapter<String> aa = new ArrayAdapter<String>(getContext(),
                    android.R.layout.simple_spinner_item, android.R.id.text1,
                    items);
            aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            setAdapter(aa);
             tv = (TextView) findViewById(android.R.id.text1);
            // types_layout_list_tv

            ctv = (CheckedTextView) aa.getDropDownView(1, null, null);
            tv2 = (TextView) aa.getView(1, null, null);

setError method:

    public void setError(String str) {
        if (tv != null)
            tv.setError(str);
        if(tv2!=null)
            tv2.setError(str);
        if (ctv != null)
            ctv.setError(str);
    }
2
  • What puzzles me in ur example: Where is the TextView tv, ctv and tv2 coming from and what do they have to do with the spinner? Greetings, Ready4Android Commented Sep 18, 2011 at 9:16
  • Do u have the solution for this ?\
    – Code_Life
    Commented Feb 6, 2012 at 13:04

10 Answers 10

49

Similar to @Gábor's solution, but I didn't need to create my own adapter. I just call the following code in my validate function (i.e. on submit button clicked)

        TextView errorText = (TextView)mySpinner.getSelectedView();                  
        errorText.setError("anything here, just to add the icon");
        errorText.setTextColor(Color.RED);//just to highlight that this is an error
        errorText.setText("my actual error text");//changes the selected item text to this
3
  • 1
    couldn't get that one to work. I have two spinners, both return null on getSelectedView() except for that the solution looked best
    – John
    Commented Dec 17, 2014 at 20:59
  • 2
    @John I saved my android.widget.Spinner as an instance variable in the onCreateView method, and then access it again in another method, in my case the onDoneAction method. Make sure to call the getSelectedView after an item in the spinner is actually selected, i.e. getSelectedItem() also returns non-null Commented Jan 6, 2015 at 14:30
  • 1
    great answer! the selected view it's actually a TextView so this will work as expected! Commented Jul 24, 2015 at 15:29
22

I have a solution that doesn't involve creating an extra edit field but you need your own SpinnerAdapter, as usual.

Make sure you have at least one TextView in the layout you use in your adapter's getView() (you normally have that, anyway).

Add the following function to your adapter (change name to the ID of your TextView):

public void setError(View v, CharSequence s) {
  TextView name = (TextView) v.findViewById(R.id.name);
  name.setError(s);
}

Call the setError() from your code this way:

YourAdapter adapter = (YourAdapter)spinner.getAdapter();
View view = spinner.getSelectedView();
adapter.setError(view, getActivity().getString(R.string.error_message));

Basically, as with any other control, only that you call it on your adapter and you have to provide the view as well.

This will display the error icon on the spinner as it is the case with other controls.

2
  • This is a great solution
    – EladHackim
    Commented Dec 27, 2014 at 14:30
  • When width=wrap_content, error icon is not shown correctly - is hidden under spinner icon
    – Wooff
    Commented Jul 27, 2015 at 8:28
12

Using a hidden TextView to get a pop-up message to appear

This solution involves adding an additional hidden textbox just below the spinner just at the right position to allow the TextView's error dialog to show, whilst also using the TextView set on the spinner's layout XML to allow the red (!) icon to be displayed. So in effect, two textviews are used -- one for the icon, and another (hidden) one to allow the error dialog.

This is what it looks like when not in an error state (use SetError(null)):

Spinner in valid state

This is what it looks like when there is an error (use SetError("my error text, ideally from a resource!")):

Spinner in invalid state

Here is the excerpt of the spinner's layout XML. There is a RelativeLayout used to ensure that the TextView is as close as possible to the spinner, and has just enough paddingRight to ensure that the arrow on the message dialog is aligned just under the red error (!) icon. The hidden (fake) TextView is positioned relative to the Spinner.

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="top|left"
        >

        <Spinner
            android:id="@+id/spnMySpinner"
            android:layout_width="400dp"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:dropDownSelector="@drawable/selector_listview"
            android:background="@android:drawable/btn_dropdown"
            android:paddingBottom="0dp"
            android:layout_marginBottom="0dp"
            />

        <!-- Fake TextView to use to set in an error state to allow an error to be shown for the TextView -->
        <android.widget.TextView
            android:id="@+id/tvInvisibleError"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_alignRight="@+id/spnMySpinner"
            android:layout_alignBottom="@+id/spnMySpinner"
            android:layout_marginTop="0dp"
            android:paddingTop="0dp"
            android:paddingRight="50dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            />

    </RelativeLayout>

Note: @drawable/selector_listview defined outside the scope of this solution. See example here of how to get this to work, as it's off topic for this answer.

Here is the code to make it work. Just call the SetError(errMsg) either with null to clear the error, or with text to set it in an error state.

/**
 * When a <code>errorMessage</code> is specified, pops up an error window with the message
 * text, and creates an error icon in the secondary unit spinner. Error cleared through passing
 * in a null string.
 * @param errorMessage Error message to display, or null to clear.
 */
public void SetError(String errorMessage)
{
    View view = spnMySpinner.getSelectedView();

    // Set TextView in Secondary Unit spinner to be in error so that red (!) icon
    // appears, and then shake control if in error
    TextView tvListItem = (TextView)view;

    // Set fake TextView to be in error so that the error message appears
    TextView tvInvisibleError = (TextView)findViewById(R.id.tvInvisibleError);

    // Shake and set error if in error state, otherwise clear error
    if(errorMessage != null)
    {
        tvListItem.setError(errorMessage);
        tvListItem.requestFocus();

        // Shake the spinner to highlight that current selection 
        // is invalid -- SEE COMMENT BELOW
        Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
        spnMySpinner.startAnimation(shake);

        tvInvisibleError.requestFocus();
        tvInvisibleError.setError(errorMessage);
    }
    else
    {
        tvListItem.setError(null);
        tvInvisibleError.setError(null);
    }
}

In the SetError function above, example there is some additional code that causes the text in the Spinner to shake when the error is set. This is not required to make the solution work, but is a nice addition. See here for the inspiration for this approach.

Kudos to @Gábor for his solution which makes use of the TextView on the Spinner's item layout XML. The code View view = spnMySpinner.getSelectedView(); (based on @Gábor's solution) is necessary, because it gets the currently displayed TextView, rather than using a findViewById, which would just get the first TextView in the list (based on the Resource ID provided), and hence would not work (to display the red (!) icon) if very first item in the list is not selected.

1
  • In this part i have a linear layout, any clues? TextView tvListItem = (TextView)view; Commented Jun 1, 2018 at 19:17
8

This can be done without using a custom layout or adapter.

((TextView)spinner.getChildAt(0)).setError("Message");

The only downside to this approach is that the little popup with the error text will not show when the icon is tapped.

0
5

I would suggest you to put an empty EditText right behind your spinner.

On the xml set that EditText

android:enabled="false"
    android:inputType="none"

Now when you want to set an error to your spinner, simply set that error to the EditText.

Remember not to set that EditText to invisibille / gone. It won't work that way.

Also, note that by this method you can decide exactly where you want your error to appear.

1
  • 3
    One limitation with placing an EditText behind the Spinner is that parts of the error checking behavior are broken. Although the red exclamation point indicating an error shows up, clicking the exclamation point will not display a popup with the error message, like it normally does.
    – Theo
    Commented Jul 23, 2014 at 16:25
1

Thanks Gabor for your fantastic solution. In furtherance to your point, my solution is thus:

Custom Adapter

    public class RequiredSpinnerAdapter<T> extends ArrayAdapter<T> {
        public RequiredSpinnerAdapter(Context context, int textViewResourceId,
                                      java.util.List<T> objects) {
            super(context, textViewResourceId, objects);
        }

        int textViewId = 0;

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = super.getView(position, convertView, parent);
            if (view instanceof TextView) {
                textViewId = view.getId();
            }
            return view;
        }

        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            View row = super.getView(position, convertView, parent);
            return (row);
        }

        public void setError(View v, CharSequence s) {
            if(textViewId != 0){
                TextView name = (TextView) v.findViewById(textViewId);
                name.setError(s);
            }
        }
    }

Use adapter for Spinner

ArrayAdapter<String> arrayAdapter = new RequiredSpinnerAdapter<String>(PropertyAdd.this, R.layout.checked, status_arr);
    marketstatus_spinner.setAdapter(arrayAdapter);
    marketstatus_spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                                   int arg2, long arg3) {

            // Put code here
        }

        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
           // Put code here
        }
    });

Check for validation

private boolean checkValidation() {
    if(marketstatus_spinner.getSelectedItem().toString().equals("")){
        RequiredSpinnerAdapter adapter = (RequiredSpinnerAdapter)marketstatus_spinner.getAdapter();
        View view = marketstatus_spinner.getSelectedView();
        adapter.setError(view, "Please select a value");

        return false;
    }
}
1
  • 1
    Add one for saying "In furtherance"! :-) Commented Jul 8, 2015 at 10:50
0

I guess that Spinner is not the right place to put this method. In case of Spinner you should select one value and to values in the Spinner should be filtered on the level of your adapter. Thus, a user can choose only those values that are in the Spinner.

1
  • 3
    I agree, but in scenario with 0 item being a "Select.." which is possible there the setError would be necessary if users selection stays on index 0
    – Bojan
    Commented Oct 16, 2013 at 10:49
0

You can create your own adapter (extends BaseAdapter implements SpinnerAdapter). That way, you can access the TextViews that are displayed in the spinner. (getView and createViewFromResource methods - example: ArrayAdapter) When you add an empty list item to allow the user to keep the field empty until it becomes mandatory (first item in the spinner), you can store it's TextView as a private member in the adapter. Then, when it comes time to call setError("...") from the Activity or Fragment, you can call it on the adapter who can pass it to the empty TextView.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(position, convertView, parent, mTextViewId);
}

private View createViewFromResource(int position, View convertView, ViewGroup parent, int resource) {
    View view;
    TextView text;

    if (convertView == null) {
        view = inflater.inflate(resource, parent, false);
    } else {
        view = convertView;
    }

    try {
        text = (TextView) view;
    } catch (ClassCastException e) {
        Log.e(TAG, "You must supply a resource ID for a TextView", e);
        throw new IllegalStateException("MyAdapter requires the resource ID to be a TextView", e);
    }

    MyItem i = getItem(position);
    String s = (null != i) ? i.toString() : "";
    text.setText(s);

    if ("".equals(s) && null == mEmptyText) {
        this.mEmptyText = text;
    }

    return view;
}

public void setError(String errorMessage) {
    if (null != mEmptyText) {
        mEmptyText.setError(errorMessage);
    } else {
        Log.d(TAG, "mEmptyText is null");
    }
}
1
  • 1
    In similar case(as your example) I hane only icon without text error
    – Helpa
    Commented May 2, 2013 at 15:30
0

Actually this is very is , you just need to have only one TextView in your View and then get the selected View from your spinner using getSelectedView() if the main View in your selected View is a TextView then directly Cast your View to TextView and setError like this :

((TextView) jobCategory.getSelectedView()).setError("Field Required");

Else if The Textview isn't directly the MAIN View then you need to find it by ID and cast it again and setError this way :

 ((TextView) jobCategory.getSelectedView().findViewById(R.id.firstName)).setError("Field Required");
0

I would suggest you to put an empty Edit Text right behind spinner and add these attributes

android:visibility="visible"

android: enabled="false"

android:inputType="none"

and add following in your code when user click on button or for event

            if (YourSpinner.getSelectedItemPosition() > 0) {
          
            String  item= String.valueOf(Yourspinner.getSelectedItem());
             editTextForError.setVisibility(View.GONE);
            Toast.makeText(MainActivity.this,item,Toast.LENGTH_SHORT).show();

            }
           else
            {
              
              editTextForError.requestFocus();
              editTextForError.setError("Erro Message");
            }
 

Not the answer you're looking for? Browse other questions tagged or ask your own question.