lam
lam

Reputation: 565

How to declare a variable with two types in Kotlin, like val x: Int or String

I'm gonna write a method like:

object UIBehavior {
   fun dialog(context: Context, title: Int | String, message: Int | String){
     val dialogObj = AlertDialog.Builder(context)
     dialogObj.setTitle(title)
     dialogObj.setMessage(message)
   }
}

The methods dialogObj.setTitle and dialogObj.setMessage allow two types of parameters, and how can I delare the parameter that can let the method dialog allow only two types Int and String?

Upvotes: 14

Views: 27127

Answers (6)

Saharsh
Saharsh

Reputation: 453

Solution :


We can declare a variable with multiple types in Kotlin by combining :

  1. Generics,
  2. Sealed classes and
  3. when expression for sealed class.

Code Example :

sealed class ABClass<out T>
{
    data class SealedClassA<out ClassA>(val value: ClassA) : ABClass<ClassA>()
    data class SealedClassB<out ClassB>(val value: ClassB) : ABClass<ClassB>()
}

fun main() {

    val a : ClassA = ClassA()
    var b : ClassB = ClassB()
    var s : String = "Just a string"

    var c : ABClass<Any> = ABClass.SealedClassA(a)
        c = ABClass.SealedClassB(b) // Works

        c = ABClass()               // Error : Sealed types cannot be instantiated
        c = ABClass.SealedClassA(s) // Error : class java.lang.String cannot be cast to class Token
        c = ABClass.SealedClassB(s) // Error : class java.lang.String cannot be cast to class Break

    when(c)
    {
        is ABClass.SealedClassA -> { println("ABClassClassA ${(c.value as ClassA).AVariable}") }
        is ABClass.SealedClassB -> { println("ABClassClassB ${(c.value as ClassB).BVariable}") }
    }

}

open class ClassA
{
    var AVariable : Int = 1
}
open class ClassB
{
    var BVariable : Int = 10
}

Explanation:

Sealed class ABClass has generic type T.

The subsequent direct subclasses (SealedClassA & SealedClassB) of the sealed class are restricted to only be :

ClassA for data class SealedClassA or
ClassB for data class SealedClassB

Thus constraining to type : ClassA or ClassB.

Composite class Definition

sealed class ABClass<out T>
{
    data class SealedClassA<out ClassA>(val value: ClassA) : ABClass<ClassA>()
    data class SealedClassB<out ClassB>(val value: ClassB) : ABClass<ClassB>()
}

Usage Definition

var c : ABClass<Any>

Any allows to store any type variable. But due to the definition of the sealed class only ClassA or ClassB types is allowed, others will give error thus putting constraint the type.

Allowed

c = ABClass.SealedClassA(a)
c = ABClass.SealedClassB(b)

Not Allowed

c = ABClass()               // Error : Sealed types cannot be instantiated
c = ABClass.SealedClassA(s) // Error : class java.lang.String cannot be cast to class Token
c = ABClass.SealedClassB(s) // Error : class java.lang.String cannot be cast to class Break

Using the when expression.

It's possible to verify all cases, and else clause is not required.

when(c)
{
    is ABClass.SealedClassA -> { println("ABClassClassA ${(c.value as ClassA).AVariable}") }
    is ABClass.SealedClassB -> { println("ABClassClassB ${(c.value as ClassB).BVariable}") }
}

Result

Variable c can store essentially only two types ClassA and ClassB.

Upvotes: 1

Arhan Ashik
Arhan Ashik

Reputation: 71

  1. Generics can be the solution for you.
fun <T>dialog(context: Context, title: T, message: T){
   if(title !is String && title !is Int) throw InvalidParameterException()
   val dialogObj = AlertDialog.Builder(context)
   dialogObj.setTitle(title)
   dialogObj.setMessage(message)
}
  1. Also using Any can be a solution too:
fun dialog(context: Context, title: Any, message: Any){
   if(title !is String && title !is Int) throw InvalidParameterException()
   val dialogObj = AlertDialog.Builder(context)
   dialogObj.setTitle(title)
   dialogObj.setMessage(message)
}

Upvotes: 3

lam
lam

Reputation: 565

Thank you guys, I was writing codes

interface DialogOption {
    val title: Any
    val message: Any
    val positiveBtnTxt: Any
    val negativeBtnTxt: Any
    fun confirm(d: DialogInterface, n: Int) {
        d.dismiss()
    }
    fun cancel(d: DialogInterface, n: Int) {
        d.dismiss()
    }

}

object UIBehavior {
    fun dialog(context: Context, opt: DialogOption) {
        val dialogObj = AlertDialog.Builder(context)
        val title = opt.title
        val message = opt.message
        val poTxt = opt.positiveBtnTxt
        val negTxt = opt.negativeBtnTxt
        fun positiveCallback(d: DialogInterface, n: Int) {
            opt.confirm(d, n)
        }

        fun negativeCallback(d: DialogInterface, n: Int) {
            opt.cancel(d, n)
        }

        if (title is String) {
            dialogObj.setTitle(title)
        } else if (title is Int) {
            dialogObj.setTitle(title)
        }
        if (message is String) {
            dialogObj.setMessage(message)
        } else if (message is Int) {
            dialogObj.setMessage(message)
        }
        if (poTxt is String) {
            dialogObj.setPositiveButton(poTxt, ::positiveCallback)
        } else if (poTxt is Int) {
            dialogObj.setPositiveButton(poTxt, ::positiveCallback)
        }
        if ( negTxt is String) {
            dialogObj.setNegativeButton(negTxt, ::negativeCallback)
        } else if (negTxt is Int) {
            dialogObj.setNegativeButton(negTxt, ::negativeCallback)
        }

        dialogObj.show()
    }
}

It works but not sure if it's reasonable

Upvotes: 0

Sjolfr
Sjolfr

Reputation: 80

You could use Any, but I don't think it is a very nice solution at all.

It would be much better to make both the title and message a String or CharSequence in line with the types setTitle and setMessage take as parameters.

Upvotes: 0

zapl
zapl

Reputation: 63955

You can't do that in Kotlin.

But you can have multiple versions of a function, e.g.

object UIBehavior {
    fun dialog(context: Context, titleId: Int, messageId: Int){
        val titleString = context.getString(titleId)
        val messageString = context.getString(messageId)
        dialog(context, titleString, messageString)
    }

    fun dialog(context: Context, title: String, message: String) {
        val dialogObj = AlertDialog.Builder(context)
        dialogObj.setTitle(title)
        dialogObj.setMessage(message)
    }
}

That way you can simply call the function with either ids or strings and it looks like you are using the same function

UIBehavior.dialog(this, R.string.title, R.string.message)
UIBehavior.dialog(this, "title", "message")

You could also use a common supertype of Int and String but that would allow a lot more and I wouldn't recommend that.

fun dialog(context: Context, title: Any, messageId: Any){
    val titleString = when (title) {
        is String -> title
        is Int -> context.getString(title)
        else -> throw IllegalArgumentException("Unsupported type")
    }
    val messageString = when ...
       ...
    dialog(context, titleString, messageString)
}

Generics don't work here either because you can't call dialogObj.setTitle(title) dynamically. It must be known at compile time whether you want to call the Int or the String overload of that function. It's also not really different from using Any.

Upvotes: 16

Durdu
Durdu

Reputation: 4849

You could use Generics to have the same method, and check the type in the method body.

Or have two different methods.

Upvotes: 0

Related Questions