There Is No Material Design Spinner for Android

Robert Mirabelle
8 min readJun 3, 2019

--

Short rant: Google has a dream. A terrible, monomaniacal dream. Material Design portends the worldwide unification of both the aesthetic and language of design . Never-mind that the design itself is rooted in the absolute human travesty that is flat design. What about the hubris of imposing a single design upon the entire world? Witness the end of creative endeavor. Witness the end of individualism. Witness the dumbing down of humanity. Witness Donald Trump.

Material Design Components for Android, (MDCA) in version 1.2.1 at the time of this writing, has some serious head-scratchers that are guaranteed to waste copious amounts of your precious time and sanity. This article will cover one of the most insidious:

There is No Material Design Spinner.

You read this correctly. Right now, you’re asking yourself:

How the HELL could Material Design Components for Android NOT include a Spinner component? Every Android app needs a Spinner! How am I expected to implement a single-select dropdown list without a Spinner?

Want to read this story later? Save it in Journal.

There is only Exposed Dropdown Menu.

Actually, there is a Material Design Spinner. It’s just not called a Spinner. And it’s not implemented like a Spinner, either. The remainder of this article will correct your foolish understanding of reality while delivering yet another sad truth regarding your Android development experience: You now have lots of extra work to do. Yay! Let’s get started!

Before we get started, it’s important to note that although this article will help you generate a proper Material Design Spinner, the spinner itself is arguably somewhat worthless due to a critical UI failure of the menu, which will regularly pop up instead of dropping down. Spinner menus should always drop down. But instead of automatically expanding the current UI downward to make room for the menu to drop down, if your spinner is located anywhere below the half the screen height, the menu will pop up instead. I consider this to be a serious UI anti-pattern. But FAR worse, the menu will regularly appear over the top of the spinner itself, completely blocking the spinner. When this happens, you will be unable to see the spinner’s hint OR its currently selected item. This is a much more egregious anti-pattern.

In my own Android apps, I no longer use spinners, unless I can guarantee that the spinner will be shown at or near the top of the current screen. For all other cases, I’m forced to render a text field that when clicked, renders a DialogFragment containing a RecyclerView of the spinner’s items. When an item is selected, I close the dialog and set the text field’s text to the selected item. It’s a serious pain, but there you have it. If these limitations are OK with you, then read on to discover how to properly implement a material spinner.

Learn this: The Material Design Spinner component you are looking for has been silently replaced with Exposed Dropdown Menu. Exposed Dropdown Menu is a term coined by the Material Design team to describe a dropdown menu with the selected item displayed above the dropdown.

The problem is, Android already has both a component and a term that describes this behavior: It’s called a Spinner.

In their attempt to unify the entire universe of design, the Material Design team has decided that the name Spinner is not universally applicable as a Material Design concept in every context, and thus will not suffice in any context, including in Android itself! They expect you to attend Material Design University to absorb all of their universal design language before you can leverage any of it. How very Android.

After reading mountains of literature about things you likely care nothing about, the Material Design team assumes you’ll automagically realize that you never really wanted a Spinner, because there’s no such thing. What you really want is an Exposed Dropdown Menu. And armed with your superior understanding, you’ll also automagically know to search under Menu in their Android Components documentation, instead of searching for the Spinner you erroneously thought you wanted. Genius.

The awfulness of this can hardly be quantified.

Implementing a Material Design Spinner

To instantiate a Material Design Spinner, ahem, Exposed Dropdown Menu, instead of the straightforward Spinner XML you’re used to, you’re now expected to do this:

res/layout/act_main.xml

Not the end of the world, but far less than ideal, and clearly a hack.

Don’t forget a whole ‘nuther layout for each Spinner item (we’ll configure Widget.MyApp.SpinnerItem below):

res/layout/material_spinner_item.xml

Chief among the litany of problems with this hack is that with it, the Material Design team has casually forced a complete conceptual parity mismatch upon the entire Android developer community. You’re now expected to think in terms of a Spinner, but code in terms of a TextInputLayout and an AutoCompleteTextView.

For instance, how do you detect when the user selects a new Spinner item? Intuitively, we expect to listen for selection changed or selected item changed. But there is no onItemSelectedListener for Material Spinners, because a Material Spinner isn’t a Spinner. Instead, we’re forced to add a text changed listener to the AutocompleteTextView to get what we want. See the problem here? You think in terms of item selected. But you code in terms of text changed — just one example of the parity mismatch between a proper Spinner and the Android team’s horrifying idea for replacing it.

It Doesn’t Look Right

The real fun begins with styling. Oh boy, we’re in for a special ride on this one. After only 900 hours of effort, I’ve managed to figure out what I consider to be the right way to style an ExposedDropdownMenu . This includes properly styling the parent TextInputLayout as well as its child AutoCompleteTextView, including the popup menu that acts as the spinner’s dropdown.

This style is part of my MyApp theme which is based on Theme.MaterialComponents.DayNight , which defines core material styles and colors like colorPrimary, colorSecondary, etc.

res/values/styles.xml

Notice the use of the materialThemeOverlay. The purpose of a material theme overlay is to define a custom theme that can be applied to specific components. But strangely, you use a material theme overlay BOTH to style the current component AND to gain critical access to styling its children. In this case, we leverage an overlay to style not just the outer box around our spinner, but also the child AutoCompleteTextView.

Almost forgot: In the style above, we set

<item name=”boxStrokeColor”>@color/material_input_border</item>

@color/material_input_border is a state color list created in res/color/material_input_border.xml that handles the border color around the spinner’s box depending on the enabled, hovered, focused and active states:

res/color/material_input_border.xml

Populating a Material Spinner

Next up, let’s populate our Material Design Spinner (or, for our fellow Manchurian candidates, Exposed Dropdown Menu) with items:

MainActivity.kt

Everything seems fine here. Same ridiculously insane amount of code to implement a simple control as everything else in Android, so what’s the problem?

The problem is that instead of instantiating and wrapping a Spinner component, you’re now instantiating and wrapping an AutoCompleteTextView. Both components feature the dropdown menu (popup menu in Android reversed logic) we’re really after, so where’s the problem?

First, you’re expected to enter text into an AutoCompleteTextView, but you never type text into a Spinner. So you’re forced to remember to set the android:editable=”false” attribute in your XML or your Spinner won’t behave correctly. But upon doing so, Android Studio will rightly inform you that the editable attribute is deprecated. Great. You have to use a deprecated attribute to get modern behavior. Insane, but true.

Second, and FAR worse, the default behavior of AutoCompleteTextView is to filter the dropdown menu to only those items that match the text you typed. Well, we’ve just disabled editing, so no one will be typing anything into the box (our Spinner). So we should be fine, right?

Wrong.

Check out what happens when you programmatically select an item from the list.

MainActivity.kt

If you forget the second false parameter, the one that instructs the AutoCompleteTextView to NOT to perform filtering, then whenever you set the selected item programmatically, all dropdown items except the selected item will disappear from the list!

Dear God, please help me.

Data Binding your Material Spinner

Finally, let’s talk Data Binding. Because our Material Spinner (I simply refuse to say Exposed Dropdown Menu) can only be populated with Strings (because it’s actually just an AutoCompleteTextView and not the Spinner we expect), if the underlying property we’re binding to is anything other than a String, we’re going to have to craft a whole new data binding converter, to convert to and from String and the data type we need.

In my case, my DB stores the selected day of the week as an Integer called dow_index. To get my Spinner to work with two-way data binding, I had to first manually craft a new data binding converter:

utils/Converter.kt

Android requires data binding converter classes (e.g.Converter) to be defined as static classes. Hence the likely unfamiliar Kotlin syntax object Converter.

With my converter class defined, I then use my converter in my data binding expression like so:

res/layout/act_main.xml

Note the invocation/usage of the Converter in the data binding expression. IMO, the syntax is crap and reveals a core problem with the Android SDK, namely that you are required to learn and remember “magical” things. In this case, in order to obtain an instance of our converter, we’re required to know and use the magical INSTANCE property. Then, we’re supposed to magically know and remember that Android provides ‘context’ property for passing the current Context into our data binding conversion method. In this case, we store weekday names in values.xml and thus our conversion methods require Context in order to obtain the strings. BTW, having to provide Context just to obtain a string is one of the many things that makes the Android SDK the worst thing on Earth.

But wait! There’s more (code to write)

Suppose the selected day of the week index in your ViewModel is currently set to 2 (Tuesday). You open your app and see your Material Spinner (sorry, AutoCompleteTextView, I mean Exposed Dropdown Menu) with Tuesday successfully selected and rejoice to the heavens.

Then you click on the Spinner to reveal its dropdown menu and select a different day, and another part of your soul dies as you witness Tuesday is now the ONLY option in the menu. Welcome back to square one. Your AutoCompleteTextView is doing its default thing and filtering the list of options to those that match the current text. But now the selected text is data bound and there’s nothing you can do about it!

Dude, just kill me now. Please?

Actually, there is something you can do about it. And you’re going to love it.

Simply subclass ArrayAdapter and override the getFilter method to return a new instance of a subclassed Filter that does nothing.

Oh, right. Of course. That’s what I meant to say. Because I’m a genius, who knows everything that ever was or will be.

Here’s our manually-coded-up replacement for ArrayAdapter:

utils/MaterialSpinnerAdapter.kt

And finally, we replace our earlier usage of ArrayAdapter with our new subclass that performs no filtering:

MainActivity.kt

Conclusion

So, it took me the better portion of 652 days to implement a Spinner that’s styled like a Material Design component — or conversely, a Material Design Exposed Dropdown Menu that functions like a Spinner.

All this living Hell on Earth in part because Android is the Frankenstein's Monster of SDKs and in part because the Material Design team has simply done away with the venerable Spinner, despite the fact that it’s precisely what is needed.

Just witness the deluge of code we’ve had to arduously craft:

200+ lines of code in 2 languages in 7 files in 4 directories using 5 different APIs

All of the above to implement one of the most common UI components in the most popular mobile platform on Earth. I wish I was kidding. This would be comical if it weren’t nearly criminal.

Did I mention that the Android SDK is the worst thing on Earth?

I hope this helps. Best of luck ;-)

📝 Save this story in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

Responses (17)