Home > Blockchain >  How do I get a delegated property class in Kotlin to support multiple property types?
How do I get a delegated property class in Kotlin to support multiple property types?

Time:11-08

I'm trying to use delegated properties in Kotlin to provide some type safety to application preferences that are persisted to a non-typed store. The store is semanitcally a bit like some type of Map<String,Any>, although it doesn't implement that specific class.

The problem i have is that it doesn't appear possible for a single delegate class to implement getValue and setValue for more than one type.

If this is the type-safe SomePreferences class that other applications will call to get/set preferenceOne and preferenceTwo:

class SomePreferences {
    private val myDelegate = MyPreferenceStorageImpl()
    var preferenceOne: Boolean by myDelegate
    var preferenceTwo: String by myDelegate
}

However, if I try and create MyPreferencesStorageImpl, it tries to look something like this:

class MyPreferenceStorageImpl() {
    operator fun getValue(somePreferences: SomePreferences, property: KProperty<*>): Boolean {
        TODO("Read from underlying store, transforming the read value as a boolean")
    }

    operator fun setValue(somePreferences: SomePreferences, property: KProperty<*>, b: Boolean) {
        TODO("Write to underlying store, transforming the value from a Boolean")
    }

    operator fun getValue(somePreferences: SomePreferences, property: KProperty<*>): String {
        TODO("Read from underlying store, transforming value to a String")
    }

    operator fun setValue(somePreferences: SomePreferences, property: KProperty<*>, s: String) {
        TODO("Write to underlying store, transforming given value to a string")
    }
}

However, this doesn't work, because I've got two methods who only differ by return type, which is a conflict.

I've a couple of half-baked ideas on how to solve this, including trying to subclass Map<String, Any> in some way, or maybe provide a type-specific implementation per preference type (ew), or generics (somehow) but these don't feel particularly right.

Am I missing an obviously better approach here?

CodePudding user response:

Assuming that you won't be using the delegate on properties with a type parameter as their type...

You can make it generic:

class MyPreferenceStorageImpl() {
    operator fun <T> getValue(somePreferences: SomePreferences, property: KProperty<*>): T {
        TODO("Read from underlying store, transforming the read value as a T")
    }

    operator fun <T> setValue(somePreferences: SomePreferences, property: KProperty<*>, b: T) {
        TODO("Write to underlying store, transforming the value from a T")
    }
}

Note that when you return from getValue, you would need to do an unchecked cast. This cannot be 100% safe, because T could have its own type parameters. You can at least check something like this though:

// suppose you've got the value from storage and it's now stored in "returnValue"
if ((property.returnType.classifier as? KClass<*>)?.isInstance(returnValue) == true) {
    return returnValue as T
} else {
    // something went wrong, returnValue is not an instance of T
}

If you need to handle each type (String, Int, Boolean) differently, you can do:

when (property.returnType) {
    typeOf<String>() -> TODO()
    typeOf<Int>() -> TODO()
    typeOf<Boolean>() -> TODO()
    else -> throw UnsupportedOperationException()
}

CodePudding user response:

If you want to do it without the reflection library, you can use reified types.

class MyPreferenceStorageImpl {
    inline operator fun <reified T> getValue(somePreferences: SomePreferences, property: KProperty<*>): T {
        when (T::class) {
            Boolean::class -> TODO("Read from underlying store, transforming the read value as a Boolean")
            String::class -> TODO("Read from underlying store, transforming the read value as a String")
            else -> error("${T::class} is an unsupported property type for delegation.")
        }

    }

    inline operator fun <reified T> setValue(somePreferences: SomePreferences, property: KProperty<*>, value: T) {
        when (T::class) {
            Boolean::class -> TODO("Write to underlying store, transforming the value from a Boolean")
            String::class -> TODO("Write to underlying store, transforming the value from a String")
            else -> error("${T::class} is an unsupported property type for delegation.")
        }
    }
}
  • Related