I have a class which takes an Enum class as constructor argument. There are also additional type bounds for this class parameter, as there is also an interface required. When I try to determine this class at runtime between 2 Enum classes, both of which could be used directly as the ctor argument, I get a compile error.
e: /Users/julian/AndroidStudioProjects/Twinkle/app/src/main/kotlin/letstwinkle/com/twinkle/LoginActivity.kt: (278, 51): Type parameter bound for DE in constructor InitialEmptyEnumAdapter(context: Context, enumClass: Class, resource: Int = …, dropDownResource: Int = …) where DE : Enum
is not satisfied: inferred type CapturedTypeConstructor(out Any) is not a subtype of Displayable
Code for the EnumAdapter and the Enums are below
@InverseBindingMethods(
InverseBindingMethod(type = Spinner::class, attribute = "selectionEnum", method = "getSelectedItem", event = "app:selectionAttrChanged")
)
/**
* If isOptional, then position 0 is a "No response" item.
*/
open class EnumAdapter<DispEnum>(val context: Context,
enumClass: Class<DispEnum>,
@LayoutRes var resource: Int = android.R.layout.simple_spinner_item,
@LayoutRes var dropDownResource: Int = android.R.layout.simple_spinner_dropdown_item,
val isOptional: Boolean = false,
@StringRes val nullDisplay: Int = R.string.no_response) :
BaseAdapter() where DispEnum : Displayable, DispEnum : Enum<DispEnum>
{
protected val inflater: LayoutInflater = LayoutInflater.from(context)
// temp public for crash report
val konstants: Array<DispEnum> = enumClass.enumConstants
override fun getItem(position: Int): DispEnum? {
if (isOptional) {
return if (position == 0) null else konstants[position - 1]
}
return konstants[position]
}
override fun getItemId(position: Int): Long = position + 0L
override fun getCount(): Int = konstants.size + if (isOptional) 1 else 0
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return createViewFromResource(position, convertView, parent, resource)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return createViewFromResource(position, convertView, parent, dropDownResource)
}
private fun createViewFromResource(position: Int, convertView: View?, parent: ViewGroup, resource: Int): View {
val view = convertView ?: inflater.inflate(resource, parent, false)
val textView: TextView
try {
textView = view as TextView
} catch (e: ClassCastException) {
Log.e("EnumAdapter", "You must supply a resource ID for a TextView")
return view
}
val enum = getItem(position)
textView.text = enum?.displayString(context.resources) ?: context.getString(this.nullDisplay)
return view
}
open fun optionalPosition(e: Enum<*>?): Int {
e?.let { return it.ordinal + 1}
return 0
}
}
// An initial empty variant. Sacrifices optional value for a non-value default.
class InitialEmptyEnumAdapter<DE>(context: Context,
enumClass: Class<DE>,
@LayoutRes resource: Int = android.R.layout.simple_spinner_item,
@LayoutRes dropDownResource: Int = android.R.layout.simple_spinner_dropdown_item)
: EnumAdapter<DE>(context, enumClass, resource, dropDownResource, true, R.string.choose) where DE : Displayable, DE : Enum<DE> {
override fun getItem(position: Int): DE? {
return if (position == count) null else konstants[position]
}
override fun optionalPosition(e: Enum<*>?): Int {
e?.let { return it.ordinal }
return this.count
}
override fun getCount(): Int = konstants.size
}
enum class Intent(val jsonStr: String, val resID: Int) : Displayable {
SeriousRelationship("serious", R.string.intent_serious),
NonexclusiveDating("casual", R.string.intent_nonexclusive),
OpenPartner("open", R.string.intent_open_partners),
Friends("friend", R.string.intent_friendship),
FriendsSameGender("friendsame", R.string.intent_friendship_samegender),
;
override fun toString() = jsonStr
override fun displayString(res: Resources): String = res.getString(resID)
}
enum class PG13Intent(val realIntent: Intent) : Displayable {
SeriousRelationship(Intent.SeriousRelationship),
Friends(Intent.Friends),
FriendsSameGender(Intent.FriendsSameGender),
;
override fun displayString(res: Resources): String = realIntent.displayString(res)
override fun toString() = realIntent.toString()
}
Here’s the attempt to use it
val intentClass = if (this.resources.getBoolean(R.bool.usePG13Intent)) PG13Intent::class.java else Intent::class.java
this.reg2Binding!!.intentPicker.adapter = InitialEmptyEnumAdapter(this, intentClass,
resource = R.layout.spinner_item)
Yes, the Intent
there is actually imported as my Intent class, not Android. As you can see, what I’m trying to do is use Enums in the adapter, which I use to great success in many places, but in this case I am trying to reduce the options at runtime, basically the same Enum, filtered. I can imagine another way to do this, but was wondering if this way could work.