Identifying relationships in Kotlin - kotlin

Are nested classes a good way to model identifying relationships in Kotlin?
Requirements:
an issue can not be without a serno
a revocation can not be without an issue
an affirmation can not be without a revocation
Looks quite verbose:
class Have {
inner class Serno(val value: String) {
override fun toString(): String = "serno: $value"
fun nothing () = this#Have
inner class Issue(val value: String) {
override fun toString(): String = "issue: $value (${serno()})"
fun serno () = this#Serno
inner class Revocation(val value: String) {
override fun toString(): String = "revocation: $value (${issue()})"
fun issue () = this#Issue
inner class Affirmation(val value: String) {
override fun toString(): String = "affirmation: $value (${revocation()})"
fun revocation () = this#Revocation
}
}
}
}
}
val serno: Have.Serno = Have().Serno("123")
val issue: Have.Serno.Issue = serno.Issue("SUP-1")
val revocation: Have.Serno.Issue.Revocation = issue.Revocation("2020")
val affirmation: Have.Serno.Issue.Revocation.Affirmation = revocation.Affirmation("2022")
println(serno)
println(issue)
println(revocation)
println(affirmation)
println(serno == affirmation.revocation().issue().serno())
Is there a simpler way to achieve the same?

This would usually be achieved with simple non-null properties:
class Serno(val value: String) {
override fun toString(): String = "serno: $value"
}
class Issue(val value: String, val serno: Serno) {
override fun toString(): String = "issue: $value ($serno)"
}
class Revocation(val value: String, val issue: Issue) {
override fun toString(): String = "revocation: $value ($issue)"
}
class Affirmation(val value: String, val revocation: Revocation) {
override fun toString(): String = "affirmation: $value ($revocation)"
}
val serno = Serno("123")
val issue = Issue("SUP-1", serno)
val revocation = Revocation("2020", issue)
val affirmation = Affirmation("2022", revocation)
And if you're not strict on the toString format, you could even use the built-in toString of data classes, and simplify further:
data class Serno(val value: String)
data class Issue(val value: String, val serno: Serno)
data class Revocation(val value: String, val issue: Issue)
data class Affirmation(val value: String, val revocation: Revocation)

Related

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

typealias for global kotlinx.serialization not work

First I'll post the code
#OptIn(ExperimentalSerializationApi::class)
#Serializer(forClass = UUID::class)
object UUIDserializer : KSerializer<UUID> {
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.toString())
}
}
typealias SID = #Serializable(with = UUIDserializer::class) UUID
fun randomSid() = UUID.randomUUID() as SID
#Serializable
data class Example(val id:SID = randomSid())
class SerializeId {
#Test
fun nestedTypeUsage() {
val example = Example()
val string = Json.encodeToString(example)
println(string)
}
#Test
fun directTypeUsage () {
val hi = randomSid()
val string = Json.encodeToString(hi)
println(string)
}
}
nestedTypeUsage run and passes, but directTypeUsage fails.
Serializer for class 'UUID' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
kotlinx.serialization.SerializationException: Serializer for class 'UUID' is not found
I can't apply the #Serializable annotation directly to a val or a function parameter.
almost immediately after posting this. I realized I can
#Test
fun directTypeUsage () {
val hi = randomSid()
val string = hi.toString()
println(string)
}

TypeConverter not working for android Room DB

So I am trying to save a list of string pairs into my database but having some issues with the TypeConverter, I tried following guides ans other SO posts but can't figure out whats wrong...
My Entity:
#Entity
data class Credential(
#PrimaryKey()
val id: String,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "url")
val url: String?,
#TypeConverters(ListPairTypeConverter::class)
#ColumnInfo(name = "fields")
val fields: List<Pair<String, String>>
)
My Type Converter:
class ListPairTypeConverter {
#TypeConverter
fun storedStringToPairList(value: String): List<Pair<String, String>> {
return value.split("~!!!!!~").map {
val vals = it.split("!~~~~~!")
Pair(vals[0], vals[1])
}
}
#TypeConverter
fun pairListToStoredString(pl: List<Pair<String, String>>): String {
return pl.joinToString(separator = "~!!!!!~") { it.first + "!~~~~~!" + it.second }
}
}
My Error:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.util.List<kotlin.Pair<java.lang.String, java.lang.String>> fields = null;
^
You are adding type converter at wrong place. instead of
#TypeConverters(ListPairTypeConverter::class)
#ColumnInfo(name = "fields")
val fields: List<Pair<String, String>>
You need to add at here
#Database(entities = [User::class], version = 1)
#TypeConverters(ListPairTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}

How to use Either monad and avoid nested flatMap

I'm in a situation where I'm trying to setup some data and then call a service. Each step can fail, so I'm trying to use Arrow's Either to manage this.
But I'm ending up with a lot of nested flatMaps.
The following code snippet illustrates what I'm trying to do:
import arrow.core.Either
import arrow.core.flatMap
typealias ErrorResponse = String
typealias SuccessResponse = String
data class Foo(val userId: Int, val orderId: Int, val otherField: String)
data class User(val userId: Int, val username: String)
data class Order(val orderId: Int, val otherField: String)
interface MyService {
fun doSomething(foo: Foo, user: User, order: Order): Either<ErrorResponse, SuccessResponse> {
return Either.Right("ok")
}
}
fun parseJson(raw: String): Either<ErrorResponse, Foo> = TODO()
fun lookupUser(userId: Int): Either<ErrorResponse, User> = TODO()
fun lookupOrder(orderId: Int): Either<ErrorResponse, Order> = TODO()
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> {
val foo = parseJson(rawData)
val user = foo.flatMap {
lookupUser(it.userId)
}
//I want to lookupOrder only when foo and lookupUser are successful
val order = user.flatMap {
foo.flatMap { lookupOrder(it.orderId) }
}
//Only when all 3 are successful, call the service
return foo.flatMap { f ->
user.flatMap { u ->
order.flatMap { o ->
myService.doSomething(f, u, o)
}
}
}
}
I'm sure there is a better way to do this. Can someone help me with an idiomatic approach?
You can use the either { } DSL, this is available in a suspend manner or in a non-suspend manner through the either.eager { } builder.
That way you can use suspend fun <E, A> Either<E, A>.bind(): A.
Rewriting your code example:
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> =
either.eager {
val foo = parseJson(rawData).bind()
val user = lookupUser(foo.userId).bind()
val order = lookupOrder(foo.orderId).bind()
myService.doSomething(foo, user, order).bind()
}
If you run into an Either.Left, then bind() will short-circuit the either.eager block and return with the encountered Either.Left value.

Property include/exclude on Kotlin data classes

Suppose I only want one or two fields to be included in the generated equals and hashCode implementations (or perhaps exclude one or more fields). For a simple class, e.g.:
data class Person(val id: String, val name: String)
Groovy has this:
#EqualsAndHashCode(includes = 'id')
Lombok has this:
#EqualsAndHashCode(of = "id")
What is the idiomatic way of doing this in Kotlin?
My approach so far
data class Person(val id: String) {
// at least we can guarantee it is present at access time
var name: String by Delegates.notNull()
constructor(id: String, name: String): this(id) {
this.name = name
}
}
Just feels wrong though... I don't really want name to be mutable, and the extra constructor definition is ugly.
I've used this approach.
data class Person(val id: String, val name: String) {
override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
override fun hashCode() = EssentialData(this).hashCode()
override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}
private data class EssentialData(val id: String) {
constructor(person: Person) : this(id = person.id)
}
This approach may be suitable for property exclusion:
class SkipProperty<T>(val property: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 0
}
SkipProperty.equals simply returns true, which causes the embeded property to be skipped in equals of parent object.
data class Person(
val id: String,
val name: SkipProperty<String>
)
I also don't know "the idomatic way" in Kotlin (1.1) to do this...
I ended up overriding equals and hashCode:
data class Person(val id: String,
val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
Isn't there a "better" way?
This builds on #bashor's approach and uses a private primary and a public secondary constructor. Sadly the property to be ignored for equals cannot be a val, but one can hide the setter, so the result is equivalent from an external perspective.
data class ExampleDataClass private constructor(val important: String) {
var notSoImportant: String = ""
private set
constructor(important: String, notSoImportant: String) : this(important) {
this.notSoImportant = notSoImportant
}
}
Here's a somewhat creative approach:
data class IncludedArgs(val args: Array<out Any>)
fun includedArgs(vararg args: Any) = IncludedArgs(args)
abstract class Base {
abstract val included : IncludedArgs
override fun equals(other: Any?) = when {
this identityEquals other -> true
other is Base -> included == other.included
else -> false
}
override fun hashCode() = included.hashCode()
override fun toString() = included.toString()
}
class Foo(val a: String, val b : String) : Base() {
override val included = includedArgs(a)
}
fun main(args : Array<String>) {
val foo1 = Foo("a", "b")
val foo2 = Foo("a", "B")
println(foo1 == foo2) //prints "true"
println(foo1) //prints "IncludedArgs(args=[a])"
}
Reusable solution: to have an easy way to select which fields to include in equals() and hashCode(), I wrote a little helper called "stem" (essential core data, relevant for equality).
Usage is straightforward, and the resulting code very small:
class Person(val id: String, val name: String) {
private val stem = Stem(this, { id })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
It's possible to trade off the backing field stored in the class with extra computation on-the-fly:
private val stem get() = Stem(this, { id })
Since Stem takes any function, you are free to specify how the equality is computed. For more than one field to consider, just add one lambda expression per field (varargs):
private val stem = Stem(this, { id }, { name })
Implementation:
class Stem<T : Any>(
private val thisObj: T,
private vararg val properties: T.() -> Any?
) {
fun eq(other: Any?): Boolean {
if (thisObj === other)
return true
if (thisObj.javaClass != other?.javaClass)
return false
// cast is safe, because this is T and other's class was checked for equality with T
#Suppress("UNCHECKED_CAST")
other as T
return properties.all { thisObj.it() == other.it() }
}
fun hc(): Int {
// Fast implementation without collection copies, based on java.util.Arrays.hashCode()
var result = 1
for (element in properties) {
val value = thisObj.element()
result = 31 * result + (value?.hashCode() ?: 0)
}
return result
}
#Deprecated("Not accessible; use eq()", ReplaceWith("this.eq(other)"), DeprecationLevel.ERROR)
override fun equals(other: Any?): Boolean =
throw UnsupportedOperationException("Stem.equals() not supported; call eq() instead")
#Deprecated("Not accessible; use hc()", ReplaceWith("this.hc(other)"), DeprecationLevel.ERROR)
override fun hashCode(): Int =
throw UnsupportedOperationException("Stem.hashCode() not supported; call hc() instead")
}
In case you're wondering about the last two methods, their presence makes the following erroneous code fail at compile time:
override fun equals(other: Any?) = stem.equals(other)
override fun hashCode() = stem.hashCode()
The exception is merely a fallback if those methods are invoked implicitly or through reflection; can be argued if it's necessary.
Of course, the Stem class could be further extended to include automatic generation of toString() etc.
Simpler, faster, look at there, or into the Kotlin documentation.
https://discuss.kotlinlang.org/t/ignoring-certain-properties-when-generating-equals-hashcode-etc/2715/2
Only fields inside the primary constructor are taken into account to build automatic access methods like equals and so on. Do keep the meaningless ones outside.
Here is another hacky approach if you don't want to touch the data class.
You can reuse the entire equals() from data classes while excluding some fields.
Just copy() the classes with fixed values for excluded fields:
data class Person(val id: String,
val name: String)
fun main() {
val person1 = Person("1", "John")
val person2 = Person("2", "John")
println("Full equals: ${person1 == person2}")
println("equals without id: ${person1.copy(id = "") == person2.copy(id = "")}")
}
Output:
Full equals: false
equals without id: true
Consider the following generic approach for the implementation of equals/hashcode. The code below should have no performance impact because of the use of inlining and kotlin value classes:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
With this in place, you can easily implement/override any equals/hashcode implementation in a uniform way:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
this.combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
// ignore the third field for equals
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes) // ignore the third field for hashcode
}
}
You can create an annotation that represents the exclusion of the property as #ExcludeToString or with #ToString(Type.EXCLUDE) parameters by defining enum.
And then using reflection format the value of the getToString().
#Target(AnnotationTarget.FIELD)
#Retention(AnnotationRetention.RUNTIME)
annotation class ExcludeToString
data class Test(
var a: String = "Test A",
#ExcludeToString var b: String = "Test B"
) {
override fun toString(): String {
return ExcludeToStringUtils.getToString(this)
}
}
object ExcludeToStringUtils {
fun getToString(obj: Any): String {
val toString = LinkedList<String>()
getFieldsNotExludeToString(obj).forEach { prop ->
prop.isAccessible = true
toString += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
}
return "${obj.javaClass.simpleName}=[${toString.joinToString(", ")}]"
}
private fun getFieldsNotExludeToString(obj: Any): List<Field> {
val declaredFields = obj::class.java.declaredFields
return declaredFields.filterNot { field ->
isFieldWithExludeToString(field)
}
}
private fun isFieldWithExludeToString(field: Field): Boolean {
field.annotations.forEach {
if (it.annotationClass == ExcludeToString::class) {
return true
}
}
return false
}
}
GL
Gist