1

Is there a way to tell kotlin that the type T accepted by this function could be Long OR Int or String?

In this example map: Map<String, T>? could receive a Map.

import android.content.Context
import android.content.Intent

const val KEY_COMMAND = "KEY_COMMAND"
const val KEY_EXTRA = "KEY_EXTRA"

class IntentBuilder {
    companion object {

        fun <T> getIntent(
            context: Context,
            clazz: Class<Any>,
            command: Command,
            map: Map<String, T>?
        ): Intent where T : Long {
            val intent = Intent(context, clazz)
            intent.putExtra(KEY_COMMAND, command)
            if (map != null) {
                map.entries.forEach {
                    intent.putExtra(it.key, it.value)
                }
            }
        }
    }
}

I would like to allow this function to receive a Map <String, Long> or Map<String, String> value.

So intent.putExtra(it.key, it.value) would not emply in a compilation error.

I thought it would be possible to do this using where.

alexpfx
  • 6,412
  • 12
  • 52
  • 88
  • 1
    I don't think so. I use [this extension](https://github.com/AjahnCharles/android.helpers/blob/master/helpers/src/main/java/ajahncharles/android/helpers/NavigationExt.kt), and call it like: `start(MyKotlinClass::class) { putExtra(...); putExtra(...) }` – charles-allen Oct 14 '19 at 02:34
  • Yes, It can be done, i used Map.Entry for using there value as by map entry there value can be used easily. one more stackoverflow question : https://stackoverflow.com/questions/37464679/how-to-work-with-maps-in-kotlin – Rahul Khatri Oct 14 '19 at 06:28
  • 1
    You can have more than one upper bound, but they all must be satisfied simultaneously, it would mean `T` must be a subtype of `Int` _and_ `String`, not "or". – Alexey Romanov Oct 14 '19 at 07:55

1 Answers1

1

You can solve your problem by encapsulating getIntent in a generic class with a private constructor and providing its instances parameterized with acceptable types:

class IntentFactory<in T> private constructor() {
    companion object {
        val int = IntentFactory<Int>()
        val long = IntentFactory<Long>()
        val string = IntentFactory<String>()
    }

    fun getIntent(
        context: Context,
        clazz: Class<*>,
        command: Command,
        map: Map<String, T>?
    ): Intent {
        // your implementation
    }
}

Use case:

IntentFactory.int.getIntent(
    context,
    clazz,
    command,
    mapOf("1" to 1)
)

UPD: If you mean that map can contain Int, Long and String values simultaneously, you should create a class that wraps objects of these types. I also suggest adding key property to this class:

class IntentExtra<out T> private constructor(val key: String, val value: T) {
    companion object {
        operator fun invoke(key: String, value: Int) = IntentExtra(key, value)
        operator fun invoke(key: String, value: Long) = IntentExtra(key, value)
        operator fun invoke(key: String, value: String) = IntentExtra(key, value)
    }
}

Now your function may accept List<IntentExtra<*>>:

fun getIntent(
    context: Context,
    clazz: Class<*>,
    command: Command,
    extra: List<IntentExtra<*>>?
): Intent {
    // your implementation
}

Use case:

IntentFactory.getIntent(
    context,
    clazz,
    command,
    listOf(
        IntentExtra("int", 1),
        IntentExtra("long", 1L),
        IntentExtra("string", "s")
    )
)

You can also create a DSL to make client code a little bit cleaner:

class IntentExtraDSL(private val intent: Intent) {
    private fun extra(key: String, value: Any) {
        intent.putExtra(key, value)
    }

    infix fun String.extra(value: Int) = extra(this, value)
    infix fun String.extra(value: Long) = extra(this, value)
    infix fun String.extra(value: String) = extra(this, value)
}
fun getIntent(
    context: Context,
    clazz: Class<*>,
    command: Command,
    extra: IntentExtraDSL.() -> Unit
): Intent {
    val intent = Intent(context, clazz)
    intent.putExtra(KEY_COMMAND, command)
    IntentExtraDSL(intent).extra()
    return intent
}

Use case:

IntentFactory.getIntent(
    context,
    clazz,
    command
) {
    "int" extra 1
    "long" extra 1L
    "string" extra "s"
}
IlyaMuravjov
  • 2,352
  • 1
  • 9
  • 27