I'm switching to Kotlin for Android, but I'm struggling to understand the behavior of generics and Bound Class References.
In java I can serialize an object using Moshi's lib with the following lines:
Moshi moshi = new Moshi.Builder().build();
String string = moshi.adapter(CredentialsResponse.class).toJson(body);
And in Kotlin:
val moshi = Moshi.Builder().build()
var string = moshi.adapter(CredentialsResponse::class.java).toJson(body)
If I want to get the class from an instance, I found two options, but one is not working, and I can't understand why:
This code works:
fun testStack(body: CredentialsResponse) {
val moshi = Moshi.Builder().build()
var string = moshi.adapter(body.javaClass).toJson(body)
}
but this code shows a type mismatch error
fun testStack(body: CredentialsResponse) {
val moshi = Moshi.Builder().build()
var string = moshi.adapter(body::class.java).toJson(body)
}
AFAIK, this call is allowed since 1.1 (https://kotlinlang.org/docs/reference/reflection.html#bound-class-references-since-11), so what am I missing?
There's a subtle difference between the two:
class K
val javaClass: JsonAdapter<K> = moshi.adapter(body.javaClass)
val classJava: JsonAdapter<out K> = moshi.adapter(body::class.java)
Note that body::class.java is marked with out
By calling moshi.adapter(body::class.java).toJson(body) you're try to pass body as in parameter
The difference is, as #AlexeySoshin noted, that the unbound class reference Foo::class is typed with the exact type of the referenced class KClass<Foo>, and the bound one is typed with an out-projection: KClass<out Foo>.
There is a strong reason for this difference. When you reference a class by its name, you can be sure that the class token the reference evaluates to designates exactly the referenced type.
But, when you get a bound class reference for an expression typed as Foo, the expression may evaluate to an instance of Foo's subtype, and the correct type for the type token is KClass<out Foo>, meaning exactly that the actual type argument may be Foo or its subtype.
See this answer for another detailed explanation of the difference between bound and unbound class references: (link)
Related
Getting this error : java.lang.RuntimeException: org.simpleframework.xml.core.MethodException: Annotation #org.simpleframework.xml.Element(data=false, name=, required=false, type=void) must mark a set or get method
This is the same problem as: kotlin data class + bean validation jsr 303
You need to use Annotation use-site targets since the default for an annotation on a property is prioritized as:
parameter (if declared in constructor)
property (if the target site allows, but only Kotlin created annotations can do this)
field (likely what happened here, which isn't what you wanted).
Use get or set target to place the annotation on the getter or setter. Here it is for the getter:
#Root(name = "response")
public class User() {
#get:Element public var result: String? = null
#get:Element public var token: String? = null
#get:Element public var uid: String? = null
}
See the linked answer for the details.
See the question response: Here
I want to create custom request parser, i want to do this by annotating fields and getting value by reflection but I can get annotation only from the class field, the code below doesn't work for data class or constructor in a class, any idea what is wrong?
open class RequestParser {
fun getRequestWithTag(): String {
var requestString = "<RequestData>"
val declaredMemberProperties = this::class.declaredMemberProperties
declaredMemberProperties.filter {
it.findAnnotation<RequestField>() != null
}.forEach { filteredMemberProperties ->
requestString += "<${filteredMemberProperties.name.firstLetterToUpperCase()}>${filteredMemberProperties.getter.call(this)}</${filteredMemberProperties.name.firstLetterToUpperCase()}>"
}
requestString += "</RequestData>"
return requestString
}
}
#Retention(AnnotationRetention.RUNTIME)
#Target(
FIELD,
PROPERTY,
PROPERTY_GETTER,
VALUE_PARAMETER,
PROPERTY_SETTER,
CONSTRUCTOR,
FUNCTION)
public annotation class RequestField
//model example
data class RequestTest(
#RequestField val name: String
) : RequestParser()
//using example
RequestTest("name").getRequestWithTag()
An attribute in a data class constructor is many things, a constructor parameter, a getter, a setter and a field. So you need to set use-site targets to express what you actually mean.
class Example(#field:Ann val foo, // annotate Java field
#get:Ann val bar, // annotate Java getter
#param:Ann val quux) // annotate Java constructor parameter
See also https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets
So in your case I would try the following:
data class RequestTest(
#property:RequestField val name: String
) : RequestParser()
With property I am able to get the annotation from this::class.declaredMemberProperties
If you put field you would be able to get it via this::class.java.declaredFields
I am making a list of observable LiveData objects, that should contain Resource object (https://developer.android.com/topic/libraries/architecture/guide.html#addendum). I don't care what type of data that Resource object is containing.
abstract class LiveResources : LiveData<Resource<Any>>() {
private val mediatorLiveData = MediatorLiveData<Resource<Any>>()
protected val resources = HashSet<LiveData<Resource<Any>>>()
fun addResource(source: LiveData<Resource<Any>>) {
resources.add(source)
mediatorLiveData.addSource(source, resourceObserver)
}
fun removeResource(source: LiveData<Resource<Any>>) {
resources.remove(source)
mediatorLiveData.removeSource(source)
}
private val resourceObserver = Observer<Resource<Any>> {
onSourceChange()
}
abstract fun onSourceChange()
}
Unfortunately when I try to use LiveResources.addResource() with LiveData<Resource<List<String>>> I get TypeMismatch error in my IDE, saying that LiveData<Resource<Any>> was expected.
Your Resource (and/or LiveData) class should be defined with generic covariance in order to make it work. Like so:
class Resource<out T> // <- out marks generic type as covariant
Haven't tried it, but I think this would work
fun <T:Any> addResource(source: LiveData<Resource<T>>)
You should generify the classes to accept Resource<T> i.e LiveData<Resource<T>>. Any is the covariance of any object passed, but I think you are not trying to achieve that.
Another friendly advice is that you don't need to add another abstraction on top of MediatorLiveData that solely does the same you have implemented.
I have a data class in Kotlin that inherits from a Java class, which defines a constructor with 1 argument,
public BaseClass(String userSessionId) {
this.userSessionId = userSessionId;
}
My Kotlin class is defined as this
class DerivedClass(
userSessionId: String,
var other: Other? = null
) : BaseClass(userSessionId) {
I can't define it as a data class because of userSessionId, which Kotlin requires to be a val or var in data classes. However, if I do so, then Retrofit throws an exception because there are 2 members named userSessionId. Is there a way to have a data class inherit from a Java class with a constructor taking arguments? Note that I cannot change the base class.
A possible solution is to define a dummy val to avoid the name clash, but this is less than ideal
data class DerivedClass(
val dummy: String,
var other: Other? = null
) : BaseClass(dummy) {
You can use the transient keyword in Java to ignore a field during serialization, this can be done in Kotlin by using the #Transient annotation on the property instead.
Given an interface method like this (Android Retrofit), how do I read the URL path specified in the annotation argument from Kotlin code at runtime?
ApiDefinition interface:
#GET("/api/somepath/objects/")
fun getObjects(...)
Read the annotation value:
val method = ApiDefinition::getObjects.javaMethod
val verb = method!!.annotations[0].annotationClass.simpleName ?: ""
// verb contains "GET" as expected
// But how to get the path specified in the annotation?
val path = method!!.annotations[0].????????
UPDATE 1
Thanks for answers. I'm still struggling as I can't see what type to use to do the following:
val apiMethod = ApiDefinition::getObjects
.... then to pass that function reference into a method like this (it's reused)
private fun getHttpPathFromAnnotation(method: Method?) : String {
val a = method!!.annotations[0].message
}
IntelliJ IDE is suggesting I use KFunction5<> as a function parameter type (it doesn't exist as far as I can see) and seems to be requiring I specify all the parameter types for the method too, which makes a generic call to get the annotation attribute impossible. Isn't there a Kotlin equivalent of "Method"?, a type that will accept any method? I tried KFunction, without success.
UPDATE 2
Thanks for clarifying things. I've got to this point:
ApiDefinition (Retrofit interface)
#GET(API_ENDPOINT_LOCATIONS)
fun getLocations(#Header(API_HEADER_TIMESTAMP) timestamp: String,
#Header(API_HEADER_SIGNATURE) encryptedSignature: String,
#Header(API_HEADER_TOKEN) token: String,
#Header(API_HEADER_USERNAME) username: String
): Call<List<Location>>
Method to retrieve annotation argument:
private fun <T> getHttpPathFromAnnotation(method: KFunction<T>) : String {
return method.annotations.filterIsInstance<GET>().get(0).value
}
Call to get the path argument for a specific method:
val path = getHttpPathFromAnnotation<ApiDefinition>(ApiDefinition::getLocations as KFunction<ApiDefinition>)
The implicit cast seems to be necessary or the type parameter demands I provide a KFunction5 type.
This code works, but it has the GET annotation hard-coded, is there a way to make it more generic? I suspect I might need to look for GET, POST and PUT and return the first match.
Use the Kotlin KFunction directly instead of javaMethod (you're using Kotlin anyway!), and findAnnotation for concise, idiomatic code.
This will also work if the annotation happens to not be the first, where annotations[0] may break.
val method = ApiDefinition::getObjects
val annotation = method.findAnnotation<GET>() // Will be null if it doesn't exist
val path = annotation?.path
Basically all findAnnotation does is return
annotations.filterIsInstance<T>().firstOrNull()