19

Is there any way to cast a when argument to an enum?

 enum class PaymentStatus(val value: Int) {
     PAID(1),
     UNPAID(2) 
 }

fun f(x: Int) {
   val foo = when (x) {
     PaymentStatus.PAID -> "PAID"
     PaymentStatus.UNPAID -> "UNPAID"
   }
}

The above example will not work, as x is int and the values provided are the enum, if I go by PaymentStatus.PAID.value it would work but then I don't get the benefit of when (full coverage), and

when (x as PaymentStatus)

does not work.

Any one have any ideas to make this work?

Sergio
  • 27,326
  • 8
  • 128
  • 149
serhan
  • 213
  • 1
  • 2
  • 5
  • You should pass the enum instead of passing Int as a param to make the code cleaner `fun f(status: PaymentStatus) { val foo = when (status) { PaymentStatus.PAID -> "PAID" PaymentStatus.UNPAID -> "UNPAID" } }` – crack_head Sep 11 '20 at 12:54

5 Answers5

17

If you need to check a value you can do something like this:

fun f(x: Int) {
    val foo = when (x) {
        PaymentStatus.PAID.value -> "PAID"
        PaymentStatus.UNPAID.value -> "UNPAID"

        else -> throw IllegalStateException()
    }
}

Or you can create factory method create in the companion object of enum class:

enum class PaymentStatus(val value: Int) {
    PAID(1),
    UNPAID(2);

    companion object {
        fun create(x: Int): PaymentStatus {
            return when (x) {
                1 -> PAID
                2 -> UNPAID
                else -> throw IllegalStateException()
            }
        }
    }
}

fun f(x: Int) {
    val foo = when (PaymentStatus.create(x)) {
        PaymentStatus.PAID -> "PAID"
        PaymentStatus.UNPAID -> "UNPAID"
    }
}
Sergio
  • 27,326
  • 8
  • 128
  • 149
4

You don't need when in this particular use-case.

Since your goal is to get the name of the enum element having a specific value x, you can iterate over the elements of PaymentStatus like that and pick the matching element using firstOrNull:

fun getStatusWithValue(x: Int) = PaymentStatus.values().firstOrNull {
     it.value == x
}?.toString()

println(getStatusWithValue(2)) // "UNPAID"

Calling toString() on an enum element will return its name.

Edit: Since you don't want the code to compile when a new PaymentStatus is added, you can just use an exhaustive when:

fun paymentStatusNumToString(x: Int): String {
  val status = PaymentStatus.values().first { it.value == x }

  // when must be exhaustive here, because we don't use an else branch
  return when(status) {
    PaymentStatus.PAID -> "PAID" // you could use status.toString() here too
    PaymentStatus.UNPAID -> "UNPAID"
  }
}
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
  • if you read the question carefully i am interested in "the benefit of _when (full coverage)_ ." not sure how your suggestion accomplishes that. but thanks anyways. – serhan Jan 24 '19 at 22:18
  • @serhan what do you mean by "benefit of when (full coverage)"? do you mean that you want to run specific code if x is passed to f and when with its branches gives you that opportunity? – Willi Mentzel Jan 24 '19 at 22:40
  • 1
    sorry it took a while for me to see your comment, what I meant with _"benefit of when (full coverage)"_, was the built in ability for kotlin compiler to flag when a new value is added to an enumeration. Its a long term maintenance issue, where an enum is referenced in 20 places, if a new value is added, you want to be able to make sure all those locations reference that enum have ability to handle the new value, `when` will produce that error for you in compile time, if you forget one of those places. In contrast `switch / if..else..` you would not get that. – serhan Jun 18 '20 at 15:41
  • 1
    @serhan Yep, I get what you mean! See my edit pls. What you mean by "full coverage" is a when which is exhaustive (so without else). – Willi Mentzel Jun 19 '20 at 12:19
3

It basically depends on how you want to solve the identification of the appropriate enum value. The rest is probably easy enough.

Here are some variants to solve that:

  1. extension function to PaymentStatus.Companion (or integrate the function into the PaymentStatus.Companion):

    fun PaymentStatus.Companion.fromValue(i : Int) = PaymentStatus.values().single { it.value = i } // or if you want another fallback, just use singleOrNull and add ?: with an appropriate default value
    

    Usage of it in a when:

    fun f(x : Int) = when (PaymentStatus.fromValue(x)) {
      PAID -> "PAID" // or PAID.name()
      UNPAID -> "unpaid" //...
    }
    
  2. using a generic function for all your enums

    inline fun <reified T : Enum<T>> identifyFrom(identifier : (T) -> Boolean) = T::class.java.enumConstants.single(identifier) // or again: singleOrNull ?: throw IllegalArgumentException maybe?
    

    with the following usage then:

    fun f(x : Int) = when (identifyFrom<PaymentStatus> { it.value = x }) {
      PAID -> "PAID"
      UNPAID -> "UNPAID"
    }
    

    this variant clearly has the benefit that it can be reused for basically any enum where you want to get a value based on some property or properties

  3. using when to identify the appropriate enum:

    fun PaymentStatus.Companion.fromValue(i : Int) = when (i) {
      1 -> PAID
      2 -> UNPAID
      else -> IllegalArgumentException("$i is not a valid value for PaymentStatus")
    }
    

    same usage as with the first example. However: I wouldn't use this approach except you have a really good reason for it. The reason I wouldn't use it: it requires you to always remember to adapt both, the enum value and its corresponding counterpart in the fromValue-function. So you always have to update the values (at least) twice ;-)

Roland
  • 22,259
  • 4
  • 57
  • 84
2

A possible workaround to use when with an enum is the following (maybe it will not target the question entirely but I think is a good idea to have it here as a reference):

package com.company.my_package

import com.company.my_package.MyEnum.*

enum class MyEnum {
    ENUM_ITEM_1,
    ENUM_ITEM_2,
    ENUM_ITEM_3
}

val myCommand1 = { input: Any? -> input.toString() }
val myCommand2 = { input: Any? -> input.toString() }
val myCommand3 = { input: Any? -> input.toString() }
val myElseCommand = { input: Any? -> input.toString() }

fun main() {
    val myValue = null

    when {
        ENUM_ITEM_1 == myValue -> myCommand1(myValue)
        ENUM_ITEM_2 == myValue -> myCommand2(myValue)
        ENUM_ITEM_3 == myValue -> myCommand3(myValue)
        else -> myElseCommand(myValue)
    }
}
GarouDan
  • 3,743
  • 9
  • 49
  • 75
1

define your enum

 enum class Images {
        ID_PHOTO, PROFILE_IMAGE, IBAN_IMAGE, LICENSE_IMAGE
    }

define object from your enum

lateinit var selectedImage: Images

use selectedImage object from your enum with when

when (selectedImage) {
  Images.ID_PHOTO -> binding.idPhoto.setImageURI(uri)
  Images.PROFILE_IMAGE -> binding.profileImage.setImageURI(uri)
  Images.LICENSE_IMAGE -> binding.licenseImage.setImageURI(uri) 
  Images.IBAN_IMAGE -> binding.ibanImage.setImageURI(uri)
                    
}

set value for object

private fun pickImageFromGallery(image: Images) {
    selectedImage = image

}
Hamdy Abd El Fattah
  • 1,405
  • 1
  • 16
  • 16