Function to return the value of a Map by key in an enum - kotlin

I have an enum class which maps language locale to a list.
How can I fix the function getReservationFrequencies inside the companion object
to return the value of the map based on the locale (key)?
enum class ReservationFrequencies(val frequencies: Map<Language, List<ReservationFrequency>>) {
APPLICATION(mapOf(
Language.en to listOf(
ReservationFrequency(0,"once",0),
ReservationFrequency(1,"Monthly",2),
ReservationFrequency(2,"Quarterly",5),
ReservationFrequency(3,"Semi-annually",5),
ReservationFrequency(4,"Annually",5)
),
Language.ar to listOf(
ReservationFrequency(0,"مرة واحده",0),
ReservationFrequency(1,"شهرياً",2),
ReservationFrequency(2,"ربع سنوياً",5),
ReservationFrequency(3,"نصف سنوياً",5),
ReservationFrequency(4," سنوياً",5)
)
));
}
I tried creating a companion object that includes a function that returns the list
companion object {
fun getReservationFrequencies(locale: String?) : List<ReservationFrequency> {
val reservationFrequencyDtos = mutableListOf<ReservationFrequency>()
reservationFrequencies.values()
.filter { locale!!.contains(it.name)}
.forEach() {
ReservationFrequency.add(ReservationFrequency(code = it.frequencies))
}
}
}
I'm expecting a List<ReservationFrequency> based on the locale

Probably just this
fun getReservationFrequencies(locale: String?) : List<ReservationFrequency> =
ReservationFrequencies.APPLICATION.frequencies.filter {
it.key.name == locale // I don't know the specifics of how to match the String locale arg against Language
}.map {
it.value
}.first()

Related

Kotlin Creating a generic function that accepts any enum that implements an interface

Currently I have multiple enums each with a userFriendly : string, e.g.
enum class TestGroup(val userFriendly: String) {
A("A"),
B("B")
}
For each enum I now have a separate function transforming them to a class SelectField:
class SelectField(
val value: String, /** The value which gets submitted e.g. a UUID or other identifier */
val text: String /** The human-friendly label e.g. the name of an organisation */
)
fun transformTestGroups(testGroups: Array<TestGroup>): List<SelectField> =
testGroups.map {
SelectField(
value = it.name,
text = it.userFriendly
)
}
I found that enums could implement an interface:
interface UserFriendly {
val userFriendly: String
}
enum class TestGroup(override val userFriendly: String) : UserFriendly {
A("A"),
B("B")
}
However, I don't know how to make a genericTransform(values:Array<Enum<UserFriendly>>):List<SelectField>. Is this possible in Kotlin?
Arrays in Kotlin are mutable and hence invariant. That's a fancy way of saying that an Array<Int> is not an Array<Any>. The reason for that is: If we could cast x: Array<Int> up to Array<Any>, then we could write x[0] = "ABC", since "ABC" is a valid Any, and now x would contain a value that is not an integer.
So simply using the supertype won't work here. But generics will. Specifically, we'll use the curiously recurring template pattern to require that our generic argument be an enum and a UserFriendly.
fun<T> transformTestGroups(testGroups: Array<T>): List<SelectField>
where T: UserFriendly,
T: Enum<T> =
// Same implementation as before :)
testGroups.map {
SelectField(
value = it.name,
text = it.userFriendly
)
}
Complete runnable example:
interface UserFriendly {
val userFriendly: String
}
enum class TestGroup(override val userFriendly: String): UserFriendly {
A("A"),
B("B")
}
data class SelectField(
val value: String,
val text: String
)
fun<T> transformTestGroups(testGroups: Array<T>): List<SelectField>
where T: UserFriendly,
T: Enum<T> =
testGroups.map {
SelectField(
value = it.name,
text = it.userFriendly
)
}
fun main(args: Array<String>) {
println(transformTestGroups(arrayOf(TestGroup.A, TestGroup.B)))
}
Try it online!

Kotlin Reflection on Object Expression

I have a top level kotlin object with various constants declared in it. How can I iterate and reflect on those properties/fields?
object Foobar {
val MEANINGFUL_CONSTANT = SomeClass(...)
#JvmStatic
val getConstants: List<SomeClass>
get() {
val props = Foobar::class.staticProperties
return props.mapNotNull { property ->
val p = property.get()
if (p is SomeClass) {
p
} else {
null
}
}
}
}
No matter what I put in for Foobar::class.staticProperties, I get an empty list back. How do I properly reflect on Foobar?
According to the doc of staticProperties
Only properties representing static fields of Java classes are considered static.
That is why staticProperties on Foobar isn't returning anything.
You can use memberProperties and have a condition to match the KType of SomeClass as below. Maybe that helps.
val getConstants: List<Any>
get() {
val props = Foobar::class.memberProperties
val someClassType = SomeClass::class.createType()
return props.mapNotNull { property ->
if (property.returnType == someClassType) {
property.getter.call(this)
} else {
null
}
}
}

Implementing observable properties that can also serialize in Kotlin

I'm trying to build a class where certain values are Observable but also Serializable.
This obviously works and the serialization works, but it's very boilerplate-heavy having to add a setter for every single field and manually having to call change(...) inside each setter:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
#Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
correctly outputs
changing value2
{"value2":"test2"}
I've tried introducing Delegates:
interface Observable {
fun change(message: String) {
println("changing $message")
}
#Suppress("ClassName")
class default<T>(defaultValue: T) {
private var value: T = defaultValue
operator fun getValue(observable: Observable, property: KProperty<*>): T {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
#Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" }) correctly triggers change detection, but it doesn't serialize:
changing value1
{}
If I go from Observable to ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
#Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
the result is the same:
changing blah!
{}
Similarly for Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
outputs:
changing value4
{}
Delegates just doesn't seem to work with Kotlin Serialization
What other options are there to observe a property's changes without breaking its serialization that will also work on other platforms (KotlinJS, KotlinJVM, Android, ...)?
Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now.
There is an open issue #1578 on GitHub regarding this feature.
According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original object. Also you could write a custom serializer to support the serialization of Kotlin Delegates, which seems to be even more boilerplate, then writing custom getters and setters, as proposed in the question.
Data Transfer Object
By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.
This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as #Serializable.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
#Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
This yields the following result:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
Custom data type
If changing the data type is an option, you could write a wrapping class which gets (de)serialized transparently. Something along the lines of the following might work.
#Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
Which yields the following result:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
You however lose information about which property changed, as you don't have easy access to the field name. Also you have to change your data structures, as mentioned above and might not be desirable or even possible. In addition, this work only for Strings for now, even though one might make it more generic though.
Also, this requires a lot of boilerplate to start with. On the call site however, you just have to wrap the actual value in an call to obs.
I used the following boilerplate to get it to work.
typealias OnChange = (before: String, after: String) -> Unit
#Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}

Map Key Values to Dataclass in Kotlin

how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...

Kotlin Enum with String Room TypeConverter

I'm fetching json that has a 'type' field that contains a string, and I want to store this in my model object as an Enum with a string value.
The API I'm working with doesn't return all the information in one fetch, so once I have all the data I call my setProperties method.
All of my primitives are stored correctly in memory & my room db, but my Type class isn't converted. Any idea whats wrong with my type converter?
// Enum
enum class Type(var value: String) {
ARTICLE("article"),
COMMENT("comment"),
UNKNOWN("unknown")
}
// data class
data class FeedItem(#PrimaryKey override var id: Int) : Item(id) {
#TypeConverters(TypeConverter::class)
var type: Type? = Type.UNKNOWN
// other props...
fun setProperties(itemToCopy: FeedItem) {
this.type = itemToCopy.type
// set other props
}
}
// TypeConverter
class TypeConverter {
#TypeConverter
fun toString(type: Type?): String? {
return type?.value
}
#TypeConverter
fun toType(value: String?): Type? {
return when(value) {
Type.ARTICLE.value ->Type.ARTICLE
Type.COMMENT.value -> Type.COMMENT
else -> Type.UNKNOWN
}
}
}