More fun with Kotlin delegates - delegates

If you know Google's experimental Android Architecture Components, you probably know MutableLiveData. Trying to make it a bit more fun to use I came with:
class KotlinLiveData<T>(val default: T) {
val data = MutableLiveData<T>()
operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
return data.value ?: default
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
if (Looper.myLooper() == Looper.getMainLooper()) {
data.value = value
} else {
data.postValue(value)
}
}
}
And then I can:
var name : String by KotlinLiveData("not given")
name = "Chrzęszczybrzęczykiewicz"
But alas - that makes data which is needed i.e. to register Observer inaccessible:
name.data.observe(this, nameObserver) // won't work :(
Any idea if I can get it somehow?

You can access the delegate object of the property and get the MutableLiveData<T> from it:
inline fun <reified R> KProperty<*>.delegateAs<R>(): R? {
isAccessible = true
return getDelegate() as? R
}
Then the usage is:
::name.delegateAs<KotlinLiveData<String>>?.data?.observe(this, nameObserver)
To reference a member property, use this::name or someInstance::name.
This solution requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. Also, due to the type erasure, the .delegateAs<KotlinLiveData<String>> call is not type-safe: it can only check that the delegate is KotlinLiveData<*> but not that its type argument is String.

Thanks to hotkey's solution, here's some better code:
class KotlinLiveData<T>(val default: T, val liveData : MutableLiveData<T>? = null) {
val data = liveData ?: MutableLiveData<T>()
operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
return data.value ?: default
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
if (Looper.myLooper() == Looper.getMainLooper()) {
data.value = value
} else {
data.postValue(value)
}
}
}
inline fun <reified R> KMutableProperty0<*>.getLiveData(): MutableLiveData<R> {
isAccessible = true
return (getDelegate() as KotlinLiveData<R>).data
}
inline fun <reified R> KMutableProperty0<*>.observe(owner: LifecycleOwner, obs : Observer<R>) {
isAccessible = true
(getDelegate() as KotlinLiveData<R>).data.observe(owner,obs)
}
Now I can:
someViewModel::name.observe(myActivity, Observer<String>{...})
with
someViewModel.name = "Kowalski, Leon"
working as expected
This class enables using LiveData with Android Data Binding out of the box.

the simplest way you can achieve is make the delegator to a field, for example:
#JvmField val dataOfName = KotlinLiveData("not given")
var name : String by dataOfName
then you can using live data in the class, for example:
dataOfName.data.observe(this, nameObserver)
name = "Chrzęszczybrzęczykiewicz"
OR you can write some syntax suglar, for example:
var name : String by live("not given").observe(this, nameObserver)
Note you can make nameObserver lazily too, for example:
val observers by lazy{mutableListOf<Observer>()}
var name : String by live("not given").observe(this){data->
observers.forEach{it.dataChanged(data)}
}
then you can do something like as below:
observers+= nameObserver;
name = "Chrzęszczybrzęczykiewicz"
observers-= nameObserver;

Related

How to import a helper class in buildSrc/build.gradle.kts, settings.gradle.kts, and build.gradle.kts?

I'd like to create a class to help me loading different types of properties (local.properties, gradle.properties, $GRADLE_HOME/gradle.properties, environment variables, system properties, and custom properties files (maybe in other formats like yml, xml, etc.).
Also, I'd like to use this in my buildSrc/build.gradle.kts, settings.gradle.kts, and build.gradle.kts.
Please consider that we are using Gradle 6.+.
A simple implementation of this class would be (the full implementation would be a lot of more powerful):
plugins/properties/build.gradle.kts:
package com.example
object Properties {
val environmentVariables = System.getenv()
}
How can we successfully import this Properties class in all of those files (buildSrc/build.gradle.kts, settings.gradle.kts, build.gradle.kts) and use it from there? Something like:
println(com.example.Properties.environmentVariables["my.property"])
Can we do that creating this class inside of a plugin and applying it from there? Without pre-compiling and releasing the plugin? Maybe something like:
apply("plugins/properties/build.gradle.kts")
How would it be a minimal implementation for this?
I tried different approaches but I'm not being able to find a way that work with those 3 files altogether.
I'm not completely satisfied with this approach but maybe it can help others.
I wasn't able to reuse a class but a function in all places, like this:
settings.gradle.kts
apply("plugin/properties/build.gradle.kts")
#Suppress("unchecked_cast", "nothing_to_inline")
inline fun <T> uncheckedCast(target: Any?): T = target as T
val getProperty = uncheckedCast<(key: String) -> String>(extra["getProperty"])
println(getProperty("group"))
buildSrc/build.gradle.kts
apply("../plugin/properties/build.gradle.kts")
#Suppress("unchecked_cast", "nothing_to_inline")
inline fun <T> uncheckedCast(target: Any?): T = target as T
val getProperty = uncheckedCast<(key: String) -> String>(extra["getProperty"])
println(getProperty("group"))
build.gradle.kts
// Can be used inside of the file
apply("plugin/properties/build.gradle.kts")
#Suppress("unchecked_cast", "nothing_to_inline")
inline fun <T> uncheckedCast(target: Any?): T = target as T
val getProperty = uncheckedCast<(key: String) -> String>(extra["getProperty"])
println(getProperty("group"))
buildScript {
// Since the other getProperty is not visible here we need to do this again.
apply("plugin/properties/build.gradle.kts")
#Suppress("unchecked_cast", "nothing_to_inline")
inline fun <T> uncheckedCast(target: Any?): T = target as T
val getProperty = uncheckedCast<(key: String) -> String>(extra["getProperty"])
println(getProperty("group"))
}
plugin/properties/build.gradle.kts
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import java.util.Properties as JavaProperties
import org.gradle.api.initialization.ProjectDescriptor
object Properties {
lateinit var rootProjectAbsolutePath : String
val local: JavaProperties by lazy {
loadProperties(JavaProperties(), Paths.get(rootProjectAbsolutePath, "local.properties").toFile())
}
val environment by lazy {
System.getenv()
}
val system: JavaProperties = JavaProperties()
val gradle: JavaProperties by lazy {
loadProperties(JavaProperties(), Paths.get(rootProjectAbsolutePath, "gradle.properties").toFile())
}
val globalGradle: JavaProperties by lazy {
loadProperties(JavaProperties(), Paths.get(System.getProperty("user.home"), ".gradle", "gradle.properties").toFile())
}
fun containsKey(vararg keys: String): Boolean {
if (keys.isNullOrEmpty()) return false
keys.forEach {
when {
local.containsKey(it) -> return true
environment.containsKey(it) -> return true
system.containsKey(it) -> return true
gradle.containsKey(it) -> return true
globalGradle.containsKey(it) -> return true
}
}
return false
}
fun get(vararg keys: String): String {
return this.getAndCast<String>(*keys) ?: throw IllegalArgumentException("Property key(s) ${keys} not found.")
}
fun getOrNull(vararg keys: String): String? {
return getAndCast<String>(*keys)
}
inline fun <reified R> getOrDefault(vararg keys: String, default: R?): R? {
return getAndCast<R>(*keys) ?: default
}
inline fun <reified R> getAndCast(vararg keys: String): R? {
if (keys.isNullOrEmpty()) return null
keys.forEach {
val value = when {
local.containsKey(it) -> local[it]
environment.containsKey(it) -> environment[it]
system.containsKey(it) -> system[it]
gradle.containsKey(it) -> gradle[it]
globalGradle.containsKey(it) -> globalGradle[it]
else -> null
}
// TODO Improve the casting using Jackson
if (value != null) return value as R
}
return null
}
private fun loadProperties(target: JavaProperties, file: File): JavaProperties {
if (file.canRead()) {
file.inputStream().use { target.load(it) }
}
return target
}
}
if (rootProject.name == "buildSrc") {
Properties.rootProjectAbsolutePath = rootDir.parent
} else {
Properties.rootProjectAbsolutePath = rootDir.absolutePath
}
extra["getProperty"] = {key: String -> Properties.get(key)}

kotlin safe conversion from string to enum

I need to convert strings to Enum values, but want a function which returns null if the string is not an enum.
enum class Colors{
Red, Green, Blue
}
I can used Colors.valueOf(testString) provided testString is value, but there will be an exception if it is not valid, and I want a null in that case.
Because I want to this often, an extension function would be ideal. But the extension needs to operate on the class Colors, and not an object of type Colors.
Anyone know how to write such an extension? Ideally one that is generic for any enum class.
It is simple to write a top level function, but I am seeking one that acts as the standard 'method' does
// instead of
val willGetAnException = Colors.valueOf("Yellow") // standard existing fun
val willGetNull = Colors.valueOrNullOf("Orange") // new fun i seek
And ideally one that is generic and works for any enum
You don't want an extension since they must be invoked on an existing object. You want a top-level function. There is a built in one You can use:
/**
* Returns an enum entry with specified name.
*/
#SinceKotlin("1.1")
public inline fun <reified T : Enum<T>> enumValueOf(name: String): T
You can call it by inferring the type, or explicitly:
val a : MyEnumClass = enumValueOf("A")
val b = enumValueOf<MyEnumClass>("B")
However this method is not nullable: it throws java.lang.IllegalArgumentException on unknown values.
But it's easy to mimick it's behavior and have it work for nullable enums with a top level function:
inline fun <reified T : Enum<*>> enumValueOrNull(name: String): T? =
T::class.java.enumConstants.firstOrNull { it.name == name }
Colors.values().find { it.name == "Yellow" }
You can use something like this :
inline fun <reified T : Enum<T>> String.asEnumOrDefault(defaultValue: T? = null): T? =
enumValues<T>().firstOrNull { it.name.equals(this, ignoreCase = true) } ?: defaultValue
Then: "Yellow".asEnumOrDefault(Colors.Green)
Or, if you it can't be infered: "Yellow".asEnumOrDefault<Colors>()
enum class Colors {
BLACK, WHITE, UNKNOWN;
companion object {
// Verbose for illustrative purposes
fun fromOrdinal(ordinal: Int): Colors = values()[ordinal]
fun fromOrdinalOrNull(ordinal: Int): Colors? = values().getOrNull(ordinal)
fun fromOrdinalOrDefault(ordinal: Int): Colors = values().getOrElse(ordinal) { UNKNOWN }
fun fromName(name: String): Colors = valueOf(name.uppercase())
fun fromNameOrNull(name: String): Colors? = values().find { it.name == name.uppercase() }
fun fromNameOrDefault(name: String): Colors = values().find { it.name == name.uppercase() } ?: UNKNOWN
}
}
Given the fact it's not easy to access the Enum value safely in Kotlin, I published a library enum-or-null-kt to provide a collection of shorthand functions which makes you can write code like below:
class Example {
enum class Direction(val az: Int) {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(240)
}
fun printAz01(name: String = "EAST") {
val direction = enumValueOrNull<Direction>(name) ?: Direction.EAST
println("az01=${direction.az}")
}
fun printAz02(name: String = "EAST") {
val direction = name.toEnumOrNull<Direction>() ?: Direction.EAST
println("az02=${direction.az}")
}
fun printName01(az: Int = 0) {
val direction = enumValueOrNull<Direction> {
it.az == az
} ?: Direction.NORTH
println("name03=${direction.name}")
}
fun printName02(ordinal: Int = 0) {
val direction = enumValueOrNull<Direction> {
it.ordinal == ordinal
} ?: Direction.NORTH
println("name03=${direction.name}")
}
}
With it, not only can you access the Enum value with names, but also you can pass an arbitrary higher-order function as a predicate clause. That is convenient when you need to deal with a custom conversion such as JPA attribute converters.
The Kotlin API does not work by simply using <reified T: Enum<T>>. It throws an exception of the type InvocationTargetException. So I pass directly to type: Class<T> by parameter.
private fun <T> enumValueOf(type: Class<T>, enum: String) : T {
return type.enumConstants.first { it.toString() == enum }
}
Using
if (type.isEnum) enumValueOf(#Field.type, value as String)

how to convert to NotNull by using enum in Kotlin

How can I convert the following code to accepted NotNull inside enum class by using Kotlin?
Note: i'm using this enum between two activity and one activity has 2 adapters.
Here is enum class
enum class Adapterx {
ADAPTER_1,
ADAPTER_2;
companion object {
fun fromOrdinal(ordinal: Int): Adapterx? {
return Adapterx.values().firstOrNull { it.ordinal == ordinal }
}
}
}
Since you cannot restrict the ordinal: Int parameter as you've define it, you have two choices if you receive an ordinal which is not part of the enum, or is out of bounds:
Return a default value
Throw an exception
IMHO both cases are plausible if you document properly the method.
Here's a case where you return just a default value if you ask for an ordinal that does not exist:
class KotlinEnumTest {
enum class Adapterx {
ADAPTER_1,
ADAPTER_2;
companion object {
val defaultValue = ADAPTER_1
fun fromOrdinal(ordinal: Int): Adapterx =
Adapterx.values().getOrElse(ordinal, { _ -> defaultValue })
}
}
#Test fun testEnumOrdinals() {
val resultAdapter1 = Adapterx.fromOrdinal(0)
Assert.assertEquals(Adapterx.ADAPTER_1, resultAdapter1)
val resultAdapter2 = Adapterx.fromOrdinal(1)
Assert.assertEquals(Adapterx.ADAPTER_2, resultAdapter2)
// The following returns the default value ADAPTER_1
val resultOrdinalIndexOutOfBounds = Adapterx.fromOrdinal(2)
Assert.assertEquals(Adapterx.ADAPTER_1, resultOrdinalIndexOutOfBounds)
}
}

How to write a reusable transform for String to Enum value across a group of Enum classes? (Kotlin)

I have a group >5 of Enum classes that take String parameter in its values, and I want to have simple code for all these Enum classes to convert from a String field in JSON object.
enum class Religiousness(val jsonStr: String, val resID: Int) {
NotAtAll("none", R.string.not_religious),
Somewhat("somewhat", R.string.somewhat_religious),
Very("very", R.string.very_religious),
;
override fun toString() = jsonStr
fun displayString(res: Resources) = res.getString(resID)
}
I want to be able to write code like this
fun JsonConvertStrToEnum(enumClass: Class<Enum<*>>, str: String): Enum<*> {
for (enumval in enumClass.enumConstants) {
if ((enumval as IJsonStringConvertible).jsonStr() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
I am having a hard time figuring out if IJsonStringConvertible can work, and what its definition would be, and how to implement it in the Enum value instances. Any advice?
Update: I have now written the converter as this. Is this the best way? Can I also express that the return value is a subtype of the parameter so don't need to cast return value?
fun JsonConvertStrToEnum(enumClass: Class<out Enum<*>>, str: String): Enum<*> {
for (enumval in enumClass.enumConstants) {
if (enumval.toString() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
Enums as other classes can implement interfaces like so:
interface IJsonStringConvertible {
val jsonStr:String
}
enum class Religiousness(override val jsonStr: String, val resID: Int) : IJsonStringConvertible {
NotAtAll("none", R.string.not_religious),
Somewhat("somewhat", R.string.somewhat_religious),
Very("very", R.string.very_religious),
;
override fun toString() = jsonStr
fun displayString(res: Resources) = res.getString(resID)
}
Which would then be used as:
for (enumval in enumClass.enumConstants) {
if ((enumval as IJsonStringConvertible).jsonStr == str)
return enumval
}
However the above lookup can be expensive (if used millions of times). Take a look at the reverse lookup question to find out how to do it more efficiently.
If it helps anyone, here's the final version in my production app.
fun <EnumT : Enum<EnumT>> ConvertStrToEnum(enumClass: Class<EnumT>, str: String?): EnumT? {
if (str == null)
return null
for (enumval in enumClass.enumConstants) {
if (enumval.toString() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
fun <EnumT : Enum<EnumT> > ConvertStrArrayToEnumSet(enumClass: Class<EnumT>, array: List<String>?) : EnumSet<EnumT> {
val set = EnumSet.noneOf(enumClass)
array?.forEach { value -> set.add(ConvertStrToEnum(enumClass, value)) }
return set
}

Effective Enums in Kotlin with reverse lookup?

I'm trying to find the best way to do a 'reverse lookup' on an enum in Kotlin. One of my takeaways from Effective Java was that you introduce a static map inside the enum to handle the reverse lookup. Porting this over to Kotlin with a simple enum leads me to code that looks like this:
enum class Type(val value: Int) {
A(1),
B(2),
C(3);
companion object {
val map: MutableMap<Int, Type> = HashMap()
init {
for (i in Type.values()) {
map[i.value] = i
}
}
fun fromInt(type: Int?): Type? {
return map[type]
}
}
}
My question is, is this the best way to do this, or is there a better way? What if I have several enums that follow a similar pattern? Is there a way in Kotlin to make this code more re-usable across enums?
First of all, the argument of fromInt() should be an Int, not an Int?. Trying to get a Type using null will obviously lead to null, and a caller shouldn't even try doing that. The Map has also no reason to be mutable. The code can be reduced to:
companion object {
private val map = Type.values().associateBy(Type::value)
fun fromInt(type: Int) = map[type]
}
That code is so short that, frankly, I'm not sure it's worth trying to find a reusable solution.
we can use find which Returns the first element matching the given predicate, or null if no such element was found.
companion object {
fun find(value: Int): Type? = Type.values().find { it.value == value }
}
Another option, that could be considered more "idiomatic", would be the following:
companion object {
private val map = Type.values().associateBy(Type::value)
operator fun get(value: Int) = map[value]
}
Which can then be used like Type[type].
It makes not much sense in this case, but here is a "logic extraction" for #JBNized's solution:
open class EnumCompanion<T, V>(private val valueMap: Map<T, V>) {
fun fromInt(type: T) = valueMap[type]
}
enum class TT(val x: Int) {
A(10),
B(20),
C(30);
companion object : EnumCompanion<Int, TT>(TT.values().associateBy(TT::x))
}
//sorry I had to rename things for sanity
In general that's the thing about companion objects that they can be reused (unlike static members in a Java class)
If you have a lot of enums, this might save a few keystrokes:
inline fun <reified T : Enum<T>, V> ((T) -> V).find(value: V): T? {
return enumValues<T>().firstOrNull { this(it) == value }
}
Use it like this:
enum class Algorithms(val string: String) {
Sha1("SHA-1"),
Sha256("SHA-256"),
}
fun main() = println(
Algorithms::string.find("SHA-256")
?: throw IllegalArgumentException("Bad algorithm string: SHA-256")
)
This will print Sha256
I found myself doing the reverse lookup by custom, hand coded, value couple of times and came of up with following approach.
Make enums implement a shared interface:
interface Codified<out T : Serializable> {
val code: T
}
enum class Alphabet(val value: Int) : Codified<Int> {
A(1),
B(2),
C(3);
override val code = value
}
This interface (however strange the name is :)) marks a certain value as the explicit code. The goal is to be able to write:
val a = Alphabet::class.decode(1) //Alphabet.A
val d = Alphabet::class.tryDecode(4) //null
Which can easily be achieved with the following code:
interface Codified<out T : Serializable> {
val code: T
object Enums {
private val enumCodesByClass = ConcurrentHashMap<Class<*>, Map<Serializable, Enum<*>>>()
inline fun <reified T, TCode : Serializable> decode(code: TCode): T where T : Codified<TCode>, T : Enum<*> {
return decode(T::class.java, code)
}
fun <T, TCode : Serializable> decode(enumClass: Class<T>, code: TCode): T where T : Codified<TCode> {
return tryDecode(enumClass, code) ?: throw IllegalArgumentException("No $enumClass value with code == $code")
}
inline fun <reified T, TCode : Serializable> tryDecode(code: TCode): T? where T : Codified<TCode> {
return tryDecode(T::class.java, code)
}
#Suppress("UNCHECKED_CAST")
fun <T, TCode : Serializable> tryDecode(enumClass: Class<T>, code: TCode): T? where T : Codified<TCode> {
val valuesForEnumClass = enumCodesByClass.getOrPut(enumClass as Class<Enum<*>>, {
enumClass.enumConstants.associateBy { (it as T).code }
})
return valuesForEnumClass[code] as T?
}
}
}
fun <T, TCode> KClass<T>.decode(code: TCode): T
where T : Codified<TCode>, T : Enum<T>, TCode : Serializable
= Codified.Enums.decode(java, code)
fun <T, TCode> KClass<T>.tryDecode(code: TCode): T?
where T : Codified<TCode>, T : Enum<T>, TCode : Serializable
= Codified.Enums.tryDecode(java, code)
Another example implementation. This also sets the default value (here to OPEN) if no the input matches no enum option:
enum class Status(val status: Int) {
OPEN(1),
CLOSED(2);
companion object {
#JvmStatic
fun fromInt(status: Int): Status =
values().find { value -> value.status == status } ?: OPEN
}
}
True Idiomatic Kotlin Way. Without bloated reflection code:
interface Identifiable<T : Number> {
val id: T
}
abstract class GettableById<T, R>(values: Array<R>) where T : Number, R : Enum<R>, R : Identifiable<T> {
private val idToValue: Map<T, R> = values.associateBy { it.id }
operator fun get(id: T): R = getById(id)
fun getById(id: T): R = idToValue.getValue(id)
}
enum class DataType(override val id: Short): Identifiable<Short> {
INT(1), FLOAT(2), STRING(3);
companion object: GettableById<Short, DataType>(values())
}
fun main() {
println(DataType.getById(1))
// or
println(DataType[2])
}
A variant of some previous proposals might be the following, using ordinal field and getValue :
enum class Type {
A, B, C;
companion object {
private val map = values().associateBy(Type::ordinal)
fun fromInt(number: Int): Type {
require(number in 0 until map.size) { "number out of bounds (must be positive or zero & inferior to map.size)." }
return map.getValue(number)
}
}
}
A slightly extended approach of the accepted solution with null check and invoke function
fun main(args: Array<String>) {
val a = Type.A // find by name
val anotherA = Type.valueOf("A") // find by name with Enums default valueOf
val aLikeAClass = Type(3) // find by value using invoke - looks like object creation
val againA = Type.of(3) // find by value
val notPossible = Type.of(6) // can result in null
val notPossibleButThrowsError = Type.ofNullSave(6) // can result in IllegalArgumentException
// prints: A, A, 0, 3
println("$a, ${a.name}, ${a.ordinal}, ${a.value}")
// prints: A, A, A null, java.lang.IllegalArgumentException: No enum constant Type with value 6
println("$anotherA, $againA, $aLikeAClass $notPossible, $notPossibleButThrowsError")
}
enum class Type(val value: Int) {
A(3),
B(4),
C(5);
companion object {
private val map = values().associateBy(Type::value)
operator fun invoke(type: Int) = ofNullSave(type)
fun of(type: Int) = map[type]
fun ofNullSave(type: Int) = map[type] ?: IllegalArgumentException("No enum constant Type with value $type")
}
}
Based on your example, i might suggest removing the associated value and just use the ordinal which is similar to an index.
ordinal - Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).
enum class NavInfoType {
GreenBuoy,
RedBuoy,
OtherBeacon,
Bridge,
Unknown;
companion object {
private val map = values().associateBy(NavInfoType::ordinal)
operator fun get(value: Int) = map[value] ?: Unknown
}
}
In my case i wanted to return Unknown if the map returned null. You could also throw an illegal argument exception by replacing the get with the following:
operator fun get(value: Int) = map[value] ?: throw IllegalArgumentException()
An approach that reuses code:
interface IndexedEnum {
val value: Int
companion object {
inline fun <reified T : IndexedEnum> valueOf(value: Int) =
T::class.java.takeIf { it.isEnum }?.enumConstants?.find { it.value == value }
}
}
Then the enums can be made indexable:
enum class Type(override val value: Int): IndexedEnum {
A(1),
B(2),
C(3)
}
and reverse searched like so:
IndexedEnum.valueOf<Type>(3)
There is a completely generic solution that
Does not use reflection, Java or Kotlin
Is cross-platform, does not need any java
Has minimum hassle
First, let's define our interfaces as value field is not inherent to all enums:
interface WithValue {
val value: Int
}
interface EnumCompanion<E> where E: Enum<E> {
val map: Map<Int, E>
fun fromInt(type: Int): E = map[type] ?: throw IllegalArgumentException()
}
Then, you can do the following trick
inline fun <reified E> EnumCompanion() : EnumCompanion<E>
where E : Enum<E>, E: WithValue = object : EnumCompanion<E> {
override val map: Map<Int, E> = enumValues<E>().associateBy { it.value }
}
Then, for every enum you have the following just works
enum class RGB(override val value: Int): WithValue {
RED(1), GREEN(2), BLUE(3);
companion object: EnumCompanion<RGB> by EnumCompanion()
}
val ccc = RGB.fromInt(1)
enum class Shapes(override val value: Int): WithValue {
SQUARE(22), CIRCLE(33), RECTANGLE(300);
companion object: EnumCompanion<Shapes> by EnumCompanion()
}
val zzz = Shapes.fromInt(33)
As already mentioned, this is not worth it unless you have a lot of enums and you really need to get this generic.
Came up with a more generic solution
inline fun <reified T : Enum<*>> findEnumConstantFromProperty(predicate: (T) -> Boolean): T? =
T::class.java.enumConstants?.find(predicate)
Example usage:
findEnumConstantFromProperty<Type> { it.value == 1 } // Equals Type.A
val t = Type.values()[ordinal]
:)

Resources