Add new custom properties to retrofit2.Response - kotlin

In my android app:
Here my interface method:
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
#GET("traders/json")
suspend fun getTraidersList(): Response<List<Trader>>
Nice.
but I need to add 2 my custom properties to retrofit2.Response
e.g.
val isCorrect : boolean
val myCustom : MyCustomClass
I want to set/get this properties. Smt like this:
val response: Response<List<Trader>> = TransportService.getTraidersList()
if (response.isCorrect) {
// do some logic
}
val myCustom = response.getMyCustom()
Is is possible in Kotlin?

Only you can do in Kotlin is to add some extension members, which are in fact just a usual Java's static methods. All the stuff around extension getters and setters is also emulated using static methods.
Based on mentioned above we cannot add new state (fields) using static methods.
But what can we do (I'm not familiar with Retrofit, it should be possible), is to use extension getter isCorrect, which can read response status, and if it is 4xx or 5xx it returns false

Related

Null property provided by Gradle when using custom plugin

I'm trying to follow the Gradle custom plugin documentation to create a plugin that can be configured.
My plugin code:
interface MyExtension {
var myValue: Property<String>
}
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyExtension>("myExt")
}
}
in build.gradle.kts:
plugins {
`java-library`
}
apply<MyPlugin>()
the<MyExtension>().myValue.set("some-value")
Running this will give
Build file '<snip>/build.gradle.kts' line: 6
java.lang.NullPointerException (no error message)
Turns out the the<MyExtension>().myValue is null, so the set call fails. How do I do this correctly? Did I miss something in the documentation, or is it just wrong?
The documentation is not wrong. Properties can be managed by either you or by Gradle. For the latter, certain conditions have to be met.
Without managed properties
If you want to be completely in charge, you can instantiate any variables you declare yourself. For example, to declare a property on an extension that is an interface, it could look like this:
override fun apply(project: Project) {
val extension = project.extensions.create("myExt", MyExtension::class.java)
extension.myValue = project.objects.property(String::class.java)
}
Or you could instantiate it directly in the extension by making it a class instead:
open class MessageExtension(objects: ObjectFactory) {
val myValue: Property<String> = objects.property(String::class.java)
}
However, a property field is not really supposed to have a setter as the property itself has both a setter and a getter. So you should generally avoid the first approach and remove the setter on the second.
See here for more examples on managing the properties yourself.
With managed properties
To help you reduce boilerplate code, Gradle can instantiate the properties for you with what is called managed properties. To do use these, the property must not have a setter, and the getter should be abstract (which it implicitly is on an interface). So you could go back to your first example and fix it by changing var to val:
interface MyExtension {
val myValue: Property<String> // val (getter only)
}
Now Gradle will instantiate the field for you. The same thing works for abstract classes.
Read more about managed properties in the documentation here.

Scoping Java static methods in Kotlin

There's have a Java library's class which has aVeryLongNameThatIDontWantToTypeEveryTime. This class has a few static methods with generic names: get(), abs() etc.
Now I need to construct complicated calls with them in my kotlin code like this one:
aVeryLongNameThatIDontWantToTypeEveryTime.get(aVeryLongNameThatIDontWantToTypeEveryTime.abs(aVeryLongNameThatIDontWantToTypeEveryTime.get(...), aVeryLongNameThatIDontWantToTypeEveryTime.get(...)))
Now, I would like to use a local scoping function in order to not repeat myself so often. However, simply using
with(aVeryLongNameThatIDontWantToTypeEveryTime) {
get(abs(get(...), get(...)))
}
does not work: It complains that aVeryLongNameThatIDontWantToTypeEveryTime does not have a companion object. (Of course it doesn't, it's a Java class.)
The only "solution" is to globally import aVeryLongNameThatIDontWantToTypeEveryTime.* in the file which isn't great since the method names are so generic and could collide.
You can use import alias to give it locally a more convenient name:
import com.example.aVeryLongNameThatIDontWantToTypeEveryTime as MyShortName
If you prefer your scoped solution then I think the only way right now is to specify your own "scope":
object aVeryLongNameThatIDontWantToTypeEveryTimeScope {
inline fun get() = aVeryLongNameThatIDontWantToTypeEveryTime.get()
inline fun set() = aVeryLongNameThatIDontWantToTypeEveryTime.set()
inline fun abs() = aVeryLongNameThatIDontWantToTypeEveryTime.abs()
}
with (aVeryLongNameThatIDontWantToTypeEveryTimeScope) {
get()
set()
abs()
}
Unfortunately, that requires rewriting all functions, including their parameters.
In the future it could be possible to use Java static members similarly to companion objects. There are similar tasks in Kotlin's YouTrack.

Why use #singleton instead using simply object

In Kotlin, a common use for object is using it for singletons, like:
object MyObject {
...
}
However, when using micronaut framework, the official documentation recommends using something like this:
#Singleton
class V8Engine : Engine {
override var cylinders = 8
override fun start(): String {
return "Starting V8"
}
}
Why can't I use simply object instead of using annotation #Singleton with a class?
With a #Singleton, Micronaut can automatically manage dependencies between beans. If you go with the other class in https://docs.micronaut.io/latest/guide/ioc.html#beans, translated to Kotlin:
#Singleton
class Vehicle(private val engine: Engine) {
public fun start() = engine.start()
}
It can't be just an object because takes a parameter.
This parameter is discovered by Micronaut to be the singleton instance of V8Engine, which needs that to be a #Singleton and not an object.
Of course, in this case you could just directly use V8Engine in Vehicle; but it's easier to change e.g. if you want Engine not to be a singleton anymore.
Why can't I use simply object instead of using annotation #Singleton
with a class?
You can use object instead of using #Singleton with a class. Micronaut won't manage instances for you, but that is allowed.

Companion object with extension function in kotlin?

I would like to have extension function and use logger from kotlin-logging and have constants inside companion object.
My function:
fun String.toFoo(): Foo {
logger.debug { "Mapping [$this] to Foo" }
if(MY_CONST.equals(this) {
...
}
Question is where I should put val logger = KotlinLogging.logger {} and MY_CONST since I cannot use companion object with an extension function?
If you just want you logger to be a singleton you can make an object that contains and instance of the logger and reach it from there.
Object LoggerSingleton( val logger = KotlinLogging.logger{})
Then in your extension function
fun String.toFoo(): Foo {
LoggerSingleton.logger.debug { "Mapping [$this] to Foo" }
if(MY_CONST.equals(this) {
}
Since an Object in Kotlin is guaranteed to have only one instance you won't have a different logger for each use of toFoo.
EDIT
To keep the desired class name
Use this signature
Like so:
Object StringLoggerSingleton( val logger = KotlinLogging.logger("String"))
I do not know what you want to accomplish with your logger, but I show you what I did already ;-)
Usually I put extension functions in its own file named similar to what the function is actually extending (e.g. either StringExtensionFunction or if is more related to its purpose and maybe only available if certain dependencies are available, I also did something like, e.g. JsoupExtensionFunctions (where there was a String.toJsoupHtml(), File.toJsoupXml(), etc.)).
If I then need constants I just place them within that file, e.g. by just writing something like:
private const val MY_CONST = "my_const_value"
No surrounding class, no surrounding object.
Regarding the logger... as loggers are usually tied to a certain name/class, I usually put a logger inside every (important) class or associate some logger to specific names... So I am not completely sure what your intent is here... If it's ok for you that the logger is returning the container of your extension function (maybe StringExtensionFunction.kt), then you can also put a logger-val inside that file similar to what I showed with MY_CONST.
If your intention was rather to reuse the callers logger, that might not work so easily... (the easiest would then probably be to pass it to the function, but usually you do not want that)... and other mechanisms may not really be worth it ;-)

Exception when using spring-data-mongodb with Kotlin

I'm new to Kotlin, and experimenting with spring-data-mongodb. Please see example below (also available here as fully runnable Maven project with in-memory MongoDb: https://github.com/danielsindahl/spring-boot-kotlin-example).
Application.kt
package dsitest
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
#SpringBootApplication
open class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
User.kt
package dsitest
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.PersistenceConstructor
import org.springframework.data.mongodb.core.mapping.Document
#Document(collection = "user")
data class User #PersistenceConstructor constructor(#Id val id: String? = null, val userName: String)
UserRepository.kt
package dsitest
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, String>
KotlinIntegrationTest.kt
package dsitest
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
#RunWith(SpringRunner::class)
#SpringBootTest
class KotlinIntegrationTest constructor () {
#Autowired
lateinit var userRepository : UserRepository;
#Test
fun persistenceTest() {
val user : User = User(userName = "Mary")
val savedUser = userRepository.save(user)
val loadedUser = userRepository.findOne(savedUser.id) // Failing code
println("loadedUser = ${loadedUser}")
}
}
When running the test KotlinIntegrationTest.persistenceTest, I get the following error message when trying to retrieve a User object from MongoDb:
org.springframework.data.mapping.model.MappingException: No property null found on entity class dsitest.User to bind constructor parameter to!
If I modify the User data class so that userName is nullable, everything works.
data class User #PersistenceConstructor constructor(#Id val id: String? = null,
val userName: String? = null)
I would like to understand why this is the case, since I don't want userName to be nullable. Is there some alternative, better way of defining my User class in Kotlin?
Many thanks,
Daniel
Yes, it is a known problem. You should check how the bytecode for your User class looks like. Java sees the constructor with all the parameters present and tries to call it with a null value for the 2nd one.
What you could do is to try adding #JvmOverloads to your constructor - this will force Kotlin compiler to generate all versions of the constructor and so the Spring Data Mongo could pick the correct one (get rid of the #PersistenceConstructor) then.
You could also define 1 constructor with no defaults - only for Java-based frameworks and 2nd one with some defaults your you. Or...
When I write things like you are now, I create simple 'persistence' data classes with no default values whatsoever that are mapped to/from my regular domain objects (a sort of abstraction over database). It may generate some overhead at the start - but keeping your domain model not coupled so tightly with the storage model is usually a good idea anyway.