2

As in the title -

Is there any way to determine the type of the item (typically either a String, if adapted from a resource, or a generic Object, if adapted programmatically) obtained from a Spinner in onItemSelected? In this case, I'm referring to one filled programmatically in Java, but as far as I know, it would be the same for one created from a String resource array.

Obviously, however, we create our Spinner, we know what type everything in it is. If it's from resources, probably Strings. If done programmatically, some kind of Object or possibly View. However, the signature for onItemSelected looks like this:

@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
    // actions

    // to get the item, you would usually do something like:
    parent.getItemAtPosition(pos);
}

And obviously, the type we get from this is ?, in other words, a generic Object.

So, if we know exactly what type it is, we can, of course, do this (example shown with an Integer):

Object itemSelected = parent.getItemAtPosition(pos);

if (itemSelected instanceof Integer) doSomeMath((Integer) itemSelected);

But this doesn't exactly strike me as elegant. It works, certainly, at least in the simple cases I've given it (like Integer). It might even be useful for handling multiple different types of Spinner all running off the same listener - I haven't actually tried that.

But it seems like there should be a way to have the signature simply be something like:

public void onItemSelected(AdapterView<Integer> parent, View view, int pos, long id)
VerumCH
  • 2,995
  • 2
  • 15
  • 22
  • What's the goal here? Do you want to use the same `OnItemSelectedListener` for multiple spinners? – simon Jan 18 '18 at 13:15
  • No, not necessarily. Just wondering if there's a better way than `instanceof` to determine the type of Object retrieved from a Spinner. – VerumCH Jan 18 '18 at 13:31
  • I don't think there is. Anyway, instead of doing something based on the item's type I'd rather check which spinner (`parent.getId()`) triggered the `onItemSelected` call and then act accordingly. – simon Jan 18 '18 at 14:01
  • True - in the case of having multiple spinners handled by the same listener, at least, that seems like it would be the best way to do it. I thought about that after, as well. But regardless, you usually need to know the type to do something with it even if you do know the exact spinner it came from. – VerumCH Jan 18 '18 at 15:06
  • Possible duplicate of [java: Class.isInstance vs Class.isAssignableFrom](https://stackoverflow.com/questions/3949260/java-class-isinstance-vs-class-isassignablefrom) – Stav Saad Jan 18 '18 at 15:30
  • Possible duplicate of [Avoiding instanceof in Java](https://stackoverflow.com/questions/2790144/avoiding-instanceof-in-java) – bric3 Jan 18 '18 at 15:59
  • Add some a key mapped on every possible types to each of your Spinner Items and you can iterate on your keys instead of using "instance of" on every possible types. (if item.getKey() == SpinnerItemType.INTEGER) doStuff(...) – Tristan Jan 18 '18 at 16:45

2 Answers2

0

Is using 'instanceof' the only way to determine the type of an item returned from a Spinner in “onItemSelected”?

It is not the only way. But it it probably the best way.

Here are some alternatives:

if (itemSelected.getClass().getName().equals("java.lang.Integer")) {
     doSomeMath((Integer) itemSelected);
}

if (itemSelected.getClass() == Integer.class) {
     doSomeMath((Integer) itemSelected);
}

try {
    doSomeMath((Integer) itemSelected);
} catch (ClassCastException ex) {
    // squash
}

Notes:

  1. None of these alternatives is recommended. Seriously.
  2. The instanceof operator tests for subclasses as well, unlike the first two alternatives.
  3. The third version will also squash ClassCastException if it is thrown in the doSomeMath method.
  4. Problems arise with null. The instanceof operator returns false for null, but all of these alternatives will throw an NPE.

Unfortunately, your alternative won't work. The AdapterView.OnItemSelectedListener interface declares that method's first parameter with that type. The interface is not generic.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Is it possible to extend the `OnItemSelectedListener` interface to create a custom version of `onItemSelected` with a particular type? Or even extend it as a generic version? For example: `GenericItemSelectedListener extends AdapterView.OnItemSelectedListener` with `public void onItemSelected(AdapterView parent...)` ? – VerumCH Jan 18 '18 at 15:33
  • If the other argument types are different too, yes. Otherwise no. But it is unclear that you would achieve anything. How would you get the android framework to call that method? – Stephen C Jan 18 '18 at 22:35
0

But it seems like there should be a way to have the signature [...]

It seems but it can't be done because AdapterView.getItemAtPosition(int) returns Object anyway. ListView or Spinner don't know or care what type of items the adapter contains.

Obviously, however, we create our Spinner, we know what type everything in it is.

Exactly, since you're the ultimate master of what data you put in the adapter you should be able to cast it safely

@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
     final int item = (int) parent.getItemAtPosition(pos);
     doSomeMath(item);
}

Instead of checking for an always true condition it would just crash when it encounters unexpected data.

Get into this mindset: If something happens if it's not supposed to happen the code should throw an exception. This gives you a chance to fix any errors instead of never knowing if there's anything wrong.

Another option would be having the adapter stored in a member variable and accessing it from the listener

private MyAdapter adapter;

@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
     final int item = adapter.getItem(pos);
     doSomeMath(item);
}

This assumes your adapter overrides getItem(int) and returns the appropriate type.

Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124