Room, Boolean and kotlin interface lead to ambiguous getter error - kotlin

Having following interface and class:
interface IUserCreated {
val userCreated: Boolean
}
#Entity
data class Test(
#PrimaryKey #ColumnInfo(name = "test_id") var id: Long,
#ColumnInfo(name = "user_created") override val userCreated: Boolean
) : IUserCreated
This leads to following error:
error: Ambiguous getter for Field(element=userCreated, name=userCreated, type=boolean, affinity=INTEGER, collate=null, columnName=user_created, defaultValue=null, parent=null, indexed=false, nonNull=true).
All of the following match: getUserCreated, isUserCreated. You can #Ignore the ones that you don't want to match.
Question
How can I use the #Ignore annotation in this example? The userCreated boolean field is part of the entity, I just want to ignore the unused getUserCreated and tell room that it should use the default kotlin getter function isUserCreated
Note
I know I can solve this by doing following:
interface IUserCreated {
val internalUserCreated: Boolean
fun getUserCreated(): Boolean = internalUserCreated
}
#Entity
data class Test(
#PrimaryKey #ColumnInfo(name = "test_id") var id: Long,
#ColumnInfo(name = "user_created") override val internalUserCreated: Boolean
) : IUserCreated
I wonder if I can somehow solve this without the workaround, I don't want to do this for every boolean field in any entity I use, but I don't know how I can use the #Ignore annotation to fix the problem.

Related

How to get two data from Hashmap in room database?

I took the data from a money exchange API and saved it to the room. I want to extract 2 values ​​from the data I saved, for example, the user wants to convert 120 dollars to euros. I will take the conversion rate of the dollar and the conversion rate of the euro from my room database and convert it with a mathematical operation accordingly. However, I did not know how to get these two values ​​from my data that I keep as Hashmap.
I wrote a code like this,
dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues WHERE conversion_rates=:currency")
suspend fun getConversionRateByCurrency(currency : String) : Double
}
entity
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: HashMap<String,Double>,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
repositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao,
private val api: ExchangeApi
) : ExchangeRepository{
override suspend fun getConversionRateByCurrency(currency: String): Double {
return dao.getConversionRateByCurrency(currency)
}
}
repository
interface ExchangeRepository {
suspend fun getConversionRateByCurrency(currency : String) : Double
}
use case
class GetConversionRateByCurrencyUseCase #Inject constructor(
private val repository: ExchangeRepository
) {
suspend fun getConversionRateByCurrency(currency:String) : Double {
return repository.getConversionRateByCurrency(currency)
}
}
but it gave an error like this
error: Not sure how to convert a Cursor to this method's return type (java.lang.Double).
public abstract java.lang.Object getConversionRateByCurrency(#org.jetbrains.annotations.NotNull()
error: The columns returned by the query does not have the fields [value] in java.lang.Double even though they are annotated as non-null or primitive. Columns returned by the query: [base_code,conversion_rates,result,uid]
public abstract java.lang.Object getConversionRateByCurrency(#org.jetbrains.annotations.NotNull()

Kotlin pass through constructor parameters to parent without declaring in child

My use case:
I have a large number of POJO models that are different types of requests for a third-party API. All of them have several common fields and a couple unique ones.
I was hoping to build something that conceptually looks like this
class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
commonField1 = "1",
commonField2 = "2",
...
uniqueFieldA = "A"
)
}
I can of course override the common fields in every child request and then pass them to the parent constructor, but this ends up producing a lot of boilerplate code and bloats the model. Are there any options I can explore here?
Notice that what you are doing in the parentheses that follow a class declaration is not "declaring what properties this class has", but "declaring the parameters of this class' primary constructor". The former is just something you can do "along the way", by adding var or val.
Each class can have its own primary constructor that take any number and types of parameters that it likes, regardless of what class its superclass is. Therefore, it is not unreasonable to have to specify all the parameters of the constructor:
open class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
// notice that the parameters for the inherited properties don't have the
// "val" prefix, because you are not declaring them in the subclass again.
// These are just constructor parameters.
commonField1: String,
commonField2: String,
...
commonFieldX: String,
val uniqueFieldA: String,
): RequestBase(
commonField1,
commonField2,
...
commonFieldX,
)
If you find this unpleasant, there are a bunch of ways to work around this.
One way is to use composition and delegation - create an interface having the common properties. The specific requests' primary constructors will take a RequestBase and their unique properties, and implement the interface by delegating to the RequestBase:
interface Request {
val commonField1: String
val commonField2: String
val commonFieldX: String
}
open class RequestBase(
override val commonField1: String,
override val commonField2: String,
override val commonFieldX: String
): Request
class RequestA(
val requestBase: RequestBase,
val uniqueField: String
): Request by requestBase
This allows you to access someRequestA.commonFieldX directly, without doing someRequestA.requestBase.commonFieldX, but to create a RequestA, you need to create a RequestBase first:
RequestA(
RequestBase(...),
uniqueField = ...
)
Another way is to change your properties to vars, give them default values, and move them out of the constructor parameters:
open class RequestBase {
var commonField1: String = ""
var commonField2: String = ""
var commonFieldX: String = ""
}
class RequestA: RequestBase() {
var uniqueField: String = ""
}
Then to create an instance of RequestA, you would just call its parameterless constructor, and do an apply { ... } block:
RequestA().apply {
commonField1 = "foo"
commonField2 = "bar"
commonFieldX = "baz"
uniqueField = "boo"
}
The downside of this is of course that the properties are all mutable, and you have to think of a default value for every property. You might have to change some properties to nullable because of this, which might not be desirable.
You can't do it with constructors of base class. Without constructors it's possible:
open class RequestBase {
lateinit var commonField1: String
lateinit var commonField2: String
...
lateinit var commonFieldX: String
}
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
uniqueFieldA = "A"
).apply {
commonField1 = "1"
commonField2 = "2"
...
commonFieldX = "X"
}
}

Proper way to serialize a sealed class with kotlinx-serialization

I am not sure if it is possible yet but i would like to serialize the following class.
#Serializable
sealed class RestResponseDTO<out T : Any>{
#Serializable
#SerialName("Success")
class Success<out T : Any>(val value: T) : RestResponseDTO<T>()
#Serializable
#SerialName("Failure")
class Error(val message: String) : RestResponseDTO<String>()
}
when i try and use it
route(buildRoute(BookDTO.restStub)) {
get {
call.respond(RestResponseDTO.Success(BookRepo.getAll()))
}
}
I get this error:
kotlinx.serialization.SerializationException: Serializer for class
'Success' is not found. Mark the class as #Serializable or provide the
serializer explicitly.
The repo mentioned in the get portion of the route returns a list of BookDTO
#Serializable
data class BookDTO(
override val id: Int,
override val dateCreated: Long,
override val dateUpdated: Long,
val title: String,
val isbn: String,
val description: String,
val publisher:DTOMin,
val authors:List<DTOMin>
):DTO {
override fun getDisplay() = title
companion object {
val restStub = "/books"
}
}
This problem is not a deal breaker but it would be great to use an exhaustive when on my ktor-client.
Serializing sealed classes works just fine. What is blocking you are the generic type parameters.
You probably want to remove those, and simply use value: DTO. Next, make sure to have all subtypes of DTO registered for polymorphic serialization in the SerializersModule.

org.springframework.data.mapping.MappingException with Kotlin and sealed classes

I am having issues with spring data (elasticsearch) and Kotlin sealed classes. It seems it's detecting two id mappings. The classes:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "#type")
#JsonSubTypes(
JsonSubTypes.Type(value = AbstractSectionV1Dto.HtmlSectionV1Dto::class, name = "HTML"))
sealed class AbstractSectionV1Dto(
open val number: Int?,
open val id: Long?,
open val questionAnswers: List<AbstractQuestionAnswerV1Dto>,
open val rightQuestionAnswers: Int?,
open val questionAnswersSize: Int?) : Dto {
data class HtmlSectionV1Dto(
override val number: Int?,
override val id: Long,
override val questionAnswers: List<AbstractQuestionAnswerV1Dto>,
override val rightQuestionAnswers: Int?,
override val questionAnswersSize: Int?,
val html: String?) : AbstractSectionV1Dto(number, id, questionAnswers, rightQuestionAnswers, questionAnswersSize)
(...)
}
when I save the document to elasticsearch, I get
org.springframework.data.mapping.MappingException: Attempt to add id property private final java.lang.Long com.package.AbstractSectionV1Dto.id but already have property private final long com.package.AbstractSectionV1Dto$TextSectionV1Dto.id registered as id. Check your mapping configuration!
I also tried setting #Id only on the top class and on both. Any clues?
I think I got it working by placing org.springframework.data.annotation.Transient in the sealed class id, but it would be nice if someone could confirm or say something about the best solution to this problem.

What's the correct syntax for overriding an interface member function with a visibility modifier?

Neither of the two pages, the one on Interfaces, nor the one on Visibility Modifiers provides an example of the syntax I am after, so I ask.
I have an interface like so:
package bookyard.contracts;
public interface IAuthenticationManager<T> {
fun authenticateUser(userName: String, password : String,
appId: String, appSecret : String) : OperationResult<T>;
}
And I am writing an implementation for it but neither of the three below listed syntaxes work. Each time, the compiler reports the following error:
'authenticateUser' overrides nothing
Class 'DatabaseAuthenticationManager' must be declared abstract or implement abstract member public abstract fun authenticateUser(userName: String, password: String, appId: String, appSecret: String): OperationResult defined in bookyard.contracts.IAuthenticationManager
package bookyard.server.util
import bookyard.contracts.IAuthenticationManager;
public class DatabaseAuthenticationManager : IAuthenticationManager<User> {
/* override public fun authenticateUser(userName : String?,
password : String?,
appId : String?,
appSecret : String?) : OperationResult<User> {
}
public override fun authenticateUser(userName : String?,
password : String?,
appId : String?,
appSecret : String?) : OperationResult<User> {
}
public fun authenticateUser(userName : String?,
password : String?,
appId : String?,
appSecret : String?) : OperationResult<User> {
} */
}
Your problem is that you're using nullable Strings when attempting to implement the interface.
From Kotlin's documentation about null-safety...
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that can not (non-null references).
String? (nullable String) and String are considered to be different types by Kotlin. Therefore the compiler thinks that you haven't implemented the method.
Your options...
You have 2 options:
Update your interface to use nullable parameters (by adding ? to the end of each type).
Update your implementation to use non-nullable parameters (String).
I think option 2 is slightly cleaner since it won't break any other existing implementations of the interface.
Example (for option 2):
The interface stays unchanged.
Here's the new implementation...
package bookyard.server.util
import bookyard.contracts.IAuthenticationManager
class DatabaseAuthenticationManager : IAuthenticationManager<User> {
override fun authenticateUser(userName : String,
password : String,
appId : String,
appSecret : String) : OperationResult<User> {
// Your implementation...
}
}
You can see that all I've done is change each parameter from String? to String.
Notes:
Classes/fields/interfaces are public by default, the public keyword is unnecessary.
Kotlin has semi-colon inference so you don't need to add semi-colons yourself. There are rare cases where you will actually need to, but the compiler will warn you in advance.
Every overridden method needs to conform to the interface declared parameter nullability. In other words the correct way to implement the interface would be:
class DatabaseAuthenticationManager : IAuthenticationManager<User> {
override fun authenticateUser(userName: String, password: String, appId: String, appSecret: String): OperationResult<User> {
...
}
}