Error not satisfying generic type bounds due to inferred type CapturedTypeConstructor(out ANy)


#1

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.