Save a class created with the Builder pattern in Room - kotlin

I need to create a table in my db where I can save Filters so I created this class like this:
#Entity
class Filter private constructor(
val name: String?,
#TypeConverters(Converters::class)
val sortBy: Sort?,
val withGenres: List<Int>?,
val withCast: List<Int>?,
) : Serializable {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
data class Builder(
var name: String? = null,
var sortBy: Sort? = null,
var withGenres: List<Int> = listOf(),
var withCast: List<Int> = listOf(),
) {
fun name(name: String) = apply { this.name = name }
fun sortBy(sort: Sort?) = apply { this.sortBy = sort }
fun withGenres(genresId: List<Int>) = apply { this.withGenres = genresId }
fun withCast(castIds: List<Int>) = apply { this.withCast = castIds }
fun build() = Filter(name, sortBy, withGenres, withCast)
}
/**
* Sorting options, default order is Descendant (desc)
*/
enum class Sort {
POPULARITY, RELEASE_DATE, REVENUE, VOTE_AVERAGE;
private var order: Order = Order.DESC
override fun toString(): String {
return this.name.lowercase() + "." + this.order.name.lowercase()
}
fun setOrder(order: Order) {
this.order = order
}
enum class Order {
ASC, DESC
}
}
override fun toString(): String {
return "sortBy: ${sortBy.toString()}, withGenres: $withGenres"
}
}
But upon compilation I get the following errors:
Cannot configure how to save field withGenres into database
Cannot configure how to save field withCast into database
Entities and POJOs must have public constructor
Cannot find setter for field (all the fields)
How can I modify the code so that it works, what are the problems?

Related

Kotlin - Ktor - How to handle Optional API resource fields in PATCH calls?

When implementing a REST API with Ktor (and Kotlin), it supports the optional field handling of Kotlin. Which works for POST and GET, but what about PATCH (update)?
For example, you have the following resource:
#Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
So all MyAddress fields are optional (with a default value).
When you create an address with POST:
"line1" : "line1",
"country" : "XX"
and you than want to remove the country with a PATCH:
"country" : null
the end result of the resource should be:
"line1" : "line1"
But how can you determine this by parsing the json of the PATCH request? Because there is no way, as far as I know, to determine if it was null by default, or submitted.
Furthermore, the default null value for the MyAddress is required, because else the parsing will not work.
Code example:
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
#kotlinx.serialization.Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddress>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringPATCH = "{\"country\":null}"
println("JSON string is: $jsonStringPATCH")
val myAddressPatch = Json.decodeFromString<MyAddress>(jsonStringPATCH)
println("MyAddress object: $myAddressPatch")
}
I tried to add Optional<String>? as well, but it complains about missing serialization of Optional, and preferably I do not want to make all my data var's Optionals.
Note: I am looking for a more structured solution, that also works with all other resources in the api (10+ classes).
A second solution, based on Aleksei's example:
#Serializable
data class Address2(val line1: OptionalValue<String> = Undefined, val country: OptionalValue<String> = Undefined)
#Serializable(with = OptionalValueSerializer::class)
sealed interface OptionalValue<out T>
object Undefined: OptionalValue<Nothing> {
override fun toString(): String = "Undefined"
}
object Absent: OptionalValue<Nothing> {
override fun toString(): String = "Absent"
}
class WithValue<T>(val value: T): OptionalValue<T> {
override fun toString(): String = value.toString()
}
open class OptionalValueSerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalValue<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalValue<T> {
return try {
WithValue(valueSerializer.deserialize(decoder))
} catch (cause: SerializationException) {
Absent
}
}
override fun serialize(encoder: Encoder, value: OptionalValue<T>) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> valueSerializer.serialize(encoder, value.value)
}
}
}
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<Address2>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<Address2>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country is Absent || myAddressUpdate.country is WithValue) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
Output is:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: Address2(line1=street, country=GB)
JSON string is: {"country":null}
MyAddress object: Address2(line1=Undefined, country=Absent)
Update country: Absent
You can use a sealed interface for a part of an address to represent undefined value, absence of value, and actual value. For this interface, you need to write a serializer that will encode and decode values accordingly to your logic. I'm not good at the kotlinx.serialization framework so I wrote an example as simple as possible.
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
fun main() {
embeddedServer(Netty, port = 4444) {
install(ContentNegotiation) {
json()
}
routing {
post {
val address = call.receive<Address>()
println(address)
}
}
}.start()
}
#Serializable
data class Address(val line1: MyValue = Undefined, val country: MyValue = Undefined)
#Serializable(with = AddressValueSerializer::class)
sealed interface MyValue
object Undefined: MyValue {
override fun toString(): String = "Undefined"
}
object Absent: MyValue {
override fun toString(): String = "Absent"
}
class WithValue(val value: String): MyValue {
override fun toString(): String = value
}
object AddressValueSerializer: KSerializer<MyValue> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("AddressValue", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MyValue {
return try {
WithValue(decoder.decodeString())
} catch (cause: SerializationException) {
Absent
}
}
#OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: MyValue) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> { encoder.encodeString(value.value) }
}
}
}
With some help from medium.com, I came to the following solution:
#Serializable(with = OptionalPropertySerializer::class)
open class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>() {
override fun toString(): String {
return value.toString()
}
}
fun isPresent() : Boolean {
return this is Present
}
fun isNotPresent(): Boolean {
return this is NotPresent
}
fun isEmpty(): Boolean {
return (this is Present) && this.value == null
}
fun hasValue(): Boolean {
return (this is Present) && this.value != null
}
override fun toString(): String {
if(this is NotPresent) {
return "<NotPresent>"
}
return super.toString()
}
}
open class OptionalPropertySerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalProperty<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalProperty<T> =
OptionalProperty.Present(valueSerializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: OptionalProperty<T>) {
when (value) {
is OptionalProperty.NotPresent -> {}
is OptionalProperty.Present -> valueSerializer.serialize(encoder, value.value)
}
}
}
#Serializable
private data class MyAddressNew(
var line1: OptionalProperty<String?> = OptionalProperty.NotPresent,
var line2: OptionalProperty<String?> = OptionalProperty.NotPresent,
var city: OptionalProperty<String?> = OptionalProperty.NotPresent,
var postal_code: OptionalProperty<String?> = OptionalProperty.NotPresent,
var state: OptionalProperty<String?> = OptionalProperty.NotPresent,
var country: OptionalProperty<String?> = OptionalProperty.NotPresent,
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddressNew>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<MyAddressNew>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country.isPresent()) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
This prints:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: MyAddressNew(line1=street, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=GB)
JSON string is: {"country":null}
MyAddress object: MyAddressNew(line1=<NotPresent>, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=null)
Update country: null

ObjectBox returning all elements ignoring pageSize while using paging3

I want to get 100 items per page from ObjectBox database using Paging3 library. But I'm getting all the elements from database at once. The official document of ObjectBox have information about paging2.
Here is my Implementation:
LocalDatabaseImpl.kt
LocalDatabaseImpl(
// dependencies...
private val trxBox: Box<Trx>
) {
override fun getPagingDataSource(): ObjectBoxDataSource.Factory<Trx> {
val query = trxBox.query().build()
return ObjectBoxDataSource.Factory(query)
}
}
ViewModel.kt
#HiltViewModel
class HomeViewModel #Inject constructor(
private val db: LocalDatabase
): ViewModel() {
private val pager: Pager<Int, Trx>
get() = Pager(
config = PagingConfig(pageSize = 100),
pagingSourceFactory = db.getPagingDataSource()
.asPagingSourceFactory(Dispatchers.IO)
)
private var _trxFlow = pager.flow.cachedIn(viewModelScope)
val trxFlow: Flow<PagingData<Trx>> get() = _trxFlow
}
Inside Compose
#Composable
fun TrxContent() {
// ...
val trxItems = viewModel.trxFlow.collectAsLazyPagingItems()
// latest trx
LazyColumn () {
items(items = trxItems) { trx ->
if (trx == null) return#items
ItemTrxCompose(trx)
}
}
}

Kotlin vert.x parsing a JSON String to a Data class using gson always returns null

I am just playing around with vert.x 3.5.3 Kotlin and I am unable to parse a JSON string into a Data class using gson.
Here is the code
class DataVerticle : AbstractVerticle() {
override fun start(startFuture: Future<Void>) {
data class Product(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("productCode") val productCode: String
)
val products: MutableList<Product> = mutableListOf()
val gson = Gson()
val eventBus = vertx.eventBus()
eventBus.consumer<String>("data.verticle") {
when (it.headers().get("ACTION")) {
"ADD_PRODUCT" -> {
val prodJson = it.body()
if (prodJson != null) {
println(prodJson)
val product = gson.fromJson(prodJson, Product::class.java)
println(product)
it.reply("SUCCESS")
}
}
else -> {
print("ERROR")
}
}
}
startFuture.complete()
}
}
The Problem is the parsed value is always null
Here is my sample json ->
{"id":1,"name":"SOAP","productCode":"P101"}
The json string sent over the eventBus is not null.
I am using this package for gson
com.google.code.gson', version: '2.8.5'
Thanks
You declare your class inside the method body, which Gson doesn't like much.
Extracting it to be nested class will work just fine:
class DataVerticle : AbstractVerticle() {
override fun start(startFuture: Future) {
val gson = Gson()
val eventBus = vertx.eventBus()
eventBus.consumer<String>("data.verticle") {
when (it.headers().get("ACTION")) {
"ADD_PRODUCT" -> {
val prodJson = it.body()
if (prodJson != null) {
println(prodJson)
val product = gson.fromJson(prodJson, Product::class.java)
println(product)
it.reply("SUCCESS")
}
}
else -> {
print("ERROR")
}
}
}
startFuture.complete()
}
data class Product(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("productCode") val productCode: String
)
}
Tested with:
val vertx = Vertx.vertx()
vertx.deployVerticle(DataVerticle()) {
val options = DeliveryOptions()
options.addHeader("ACTION", "ADD_PRODUCT")
vertx.eventBus().send("data.verticle", """{"id":1,"name":"SOAP","productCode":"P101"}""", options)
}

Get value from annotation failed

This is annotation definition:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class MyAnno(val desc: String, val comment: String) { }
And below is where the MyAnno used:
class MyAnnoUser {
#MyAnno(desc = "name", comment = "name comment")
lateinit var name: String
#MyAnno(desc = "age", comment = "age comment")
var age: Int = 0
#MyAnno(desc = "money", comment = "money comment")
var money: Double = 0.0
#MyAnno(desc = "gender", comment = "gender comment")
var gender: Boolean = false
override fun toString(): String {
return "(name: $name; age: $age; money: $money; gender: ${if (gender) "men" else "women"})"
}
}
Here's code to read the value in MyAnno:
class MyAnnoExpression(val obj: Any, val context: Context) {
val numTypeSet = setOf("Int", "Double", "Byte")
fun expression() {
val clazz = obj::class
clazz.declaredMemberProperties.forEach { prop ->
val mutableProp = try {
prop as KMutableProperty<*>
} catch (e: Exception) {
null
} ?: return#forEach
val desc = mutableProp.findAnnotation<MyAnno>()
desc?.let {
val propClassName = mutableProp.returnType.toString().removePrefix("kotlin.")
when (propClassName) {
in numTypeSet -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toNum(propClassName))
"String" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String))
"Boolean" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toBoolean())
}
}
}
}
private fun readProp(value: MyAnno, context: Context): Any? {
val prop = Properties()
val input = context.assets.open("app.properties")
prop.load(InputStreamReader(input, "utf-8"))
return prop.get(value.desc)
}
}
Now the Debugger shows me following info of value in readProp(...) function:
#com.demo.basekotlin.MyAnno(comment=age comment, desc=age)
But i got exception when read desc from value:
An exception occurs during Evaluate Expression Action : org.jetbrains.eval4j.VOID_VALUE cannot be cast to org.jetbrains.eval4j.AbstractValue
I can't find any thing wrong in my code, is there another program setting needed?
As I understand you just want to see annotation value for given property.
First, let's declare an annotation.
#Target(PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class PropertyAnnotation(val desc: String)
Container:
class Container {
#PropertyAnnotation("Name")
var name: String? = null
#PropertyAnnotation("Age")
var age: Int = -1
var notAnnotatedProperty: String = "not annotated"
}
And finally, code responsible for get all declared properties, then find a properties annotated as PropertyAnnotation, cast it to it, and get value from it.
fun main() {
val container = Container()
container::class.declaredMemberProperties.forEach { property ->
(property.annotations.find {
it is PropertyAnnotation
} as? PropertyAnnotation)?.let {
println("Property: `$property` is ${it.desc}")
}
}
}
Output:
Property: `var Container.age: kotlin.Int` is Age
Property: `var Container.name: kotlin.String?` is Name
Kind ugly. But, let's use more Kotlin pro-dev-features.
Let's create extension function for any not-null type which returns all member property of given type:
inline fun <reified T : Any> Any.getMemberProperty(): List<T> {
return this::class.declaredMemberProperties.mapNotNull { prop ->
(prop.annotations.find { ann -> ann is T }) as? T
}
}
And now usage:
fun main() {
val container = Container()
container.getMemberProperty<PropertyAnnotation>().forEach {
println(it.desc)
}
}

Instantiating type T in Kotlin

In short, I would like to omit the repeated getT() in the example below. I have read Instantiating a generic type in Kotlin, but what does it mean 'to take () -> T as a parameter'? How can I apply that to below?
interface Food
{
var isHeated:Boolean;
var name:String;
}
abstract class Cooker<T:Food>
{
abstract fun getT():T;
abstract fun enhance(t:T);
fun cook(): T
{
var food = getT();
food.isHeated = true;
food.name = "heated " + food.name;
enhance(food);
return food;
}
}
class PotatoChip:Food
{
override var isHeated = false;
override var name = "potato chip";
}
class PotatoChipCooker:Cooker<PotatoChip>()
{
override fun getT(): PotatoChip {
return PotatoChip();
}
override fun enhance(t:PotatoChip)
{
t.name = "salted " + t.name;
}
}
class Pancake:Food
{
override var isHeated = false;
override var name = "pancake";
}
class PancakeCooker:Cooker<Pancake>()
{
override fun getT(): Pancake {
return Pancake();
}
override fun enhance(t:Pancake)
{
t.name = t.name + " coated with maple syrup";
}
}
fun main(args: Array<String>)
{
val result = PotatoChipCooker().cook();
println(result.name);
val result2 = PancakeCooker().cook();
println(result2.name);
}
You could make the initialization function part of the primary constructor. As a result, implementing classes will have to pass a function that specifies how the corresponding type is being created:
abstract class Cooker<T : Food>(private val initT: () -> T) {
abstract fun enhance(t: T)
fun cook(): T {
val food = initT()
food.isHeated = true
food.name = "heated $name"
enhance(food)
return food
}
}
class PotatoChipCooker : Cooker<PotatoChip>({ PotatoChip() }) {
override fun enhance(t: PotatoChip) {
t.name = "salted ${t.name}"
}
}
class PancakeCooker : Cooker<Pancake>({ Pancake() }) {
override fun enhance(t: Pancake) {
t.name = "${t.name} coated with maple syrup"
}
}
Note that I removed the optional semicolons and used string templates instead of concatenations. Also, the cook method can be simplified to:
fun cook() = initT().apply {
isHeated = true
name = "heated $name"
enhance(this)
}