I am using Firestore's Java-based annotation for marking fields and methods for mapping document fields to Java class elements:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface PropertyName {
String value();
}
I am using it on a field in a Kotlin data class, which compiles fine:
data class MyDataClass(
@PropertyName("z") val x: Int
)
In IntelliJ and Android Studio, I can see it show up in the decompiled class dump:
public final data class MyDataClass public constructor(x: kotlin.Int) {
@field:com.google.cloud.firestore.annotation.PropertyName public final val x: kotlin.Int /* compiled code */
public final operator fun component1(): kotlin.Int { /* compiled code */ }
}
My impression at this point is that this annotation should be discoverable somehow via Kotlin reflection. As far as I can tell, it is not. I've tried iterating the annotations on:
- Each Kotlin data class constructor fields
- Each Kotlin field
- Each Kotlin function
- Each Java constructor
- Each Java field
- Each Java method
It just does not show up anywhere.
The moment I change the usage of the annotation like this (note the target specifier "get" now):
data class MyDataClass(
@get:PropertyName("z") val x: Int
)
The annotation now shows up in the generated getter of the Java class object. This is at least workable in practice, but I'm curious why Kotlin lets me compile the annotation in as a field-targeted annotation, but doesn't allow me to get it back out at runtime (unless I'm missing something in the kotlin-reflect APIs?).
If I use this Kotlin-based annotation instead:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class PropertyName(val value: String)
With this, the annotation shows up at runtime on the Kotlin field. This is curious because Java's ElementType.FIELD simply does not seem to map perfectly to Kotlin's AnnotationTarget.FIELD.
(Incidentally, if I change this to AnnotationTarget.VALUE_PARAMETER, I can also discover this annotation in the data class constructor parameter.)
This feels like a bug to me, but I'm open to seeing if I just did something wrong here. Or maybe this is just not supported. I'm using Kotlin 1.3.11. Same behavior on JVM and Android.
Code that looks for the annotation:
Log.d("@@@@@", "\n\nDump of $kclass")
val ctor = kclass.constructors.first()
Log.d("@@@@@", "Constructor parameters")
ctor.parameters.forEach { p ->
Log.d("@@@@@", p.toString())
Log.d("@@@@@", p.annotations.size.toString())
p.annotations.forEach { a ->
Log.d("@@@@@", " " + a.annotationClass)
}
}
Log.d("@@@@@", "kotlin functions")
kclass.functions.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "kotlin members")
kclass.members.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "kotlin declared functions")
kclass.declaredFunctions.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
val t = kclass.java
Log.d("@@@@@", "java constructors")
t.constructors.forEach { f ->
Log.d("@@@@@", f.toString())
}
Log.d("@@@@@", "java methods")
t.methods.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "java fields")
t.fields.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
kclass
assignment please, and remove everything about the constructor, it is a field and would not appear there unless it was allowed to be on a method parameter. Plus you checked the bytecode and saw it on the field. – Jayson Minardval kclass = MyDataClass::class
? or something else. – Jayson Minard