Closed lexicalninja closed 4 years ago
Use the @PropertyName
annotation to force the name to be whatever you like if our default naming rules don't work for you.
Firestore maps names as follows:
get
or is
is stripped and then any subsequent run of uppercase is made lowercase.The underlying issue here is that Kotlin properties end up generating both backing fields, getters, and setters (for var) so the getter rule is being applied.
Something like this would do what you're looking for:
@get:PropertyName("UUID") override var UUID: String = ""
I added it as you suggested
data class TrainingPlanProgress(@get:PropertyName("UUID") override var UUID: String = "",
var trainingPlan: String = "",
var startDate: Date = Date(),
var finishDate: Date? = null
) : Model(UUID) {
fun toMap(): Map<String, Any?> {
val map = mutableMapOf<String, Any?>()
map["UUID"] = this.UUID
map["trainingPlan"] = this.trainingPlan
map["startDate"] = this.startDate
map["finishDate"] = this.finishDate
return map
}
}
I receive the following exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.kinetic.fit, PID: 16866
java.lang.RuntimeException: Found setter on com.kinetic.fit.data.firestore.Model with invalid case-sensitive name: setUUID
at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.<init>(com.google.firebase:firebase-firestore@@18.0.1:607)
at com.google.firebase.firestore.util.CustomClassMapper.loadOrCreateBeanMapperForClass(com.google.firebase:firebase-firestore@@18.0.1:348)
at com.google.firebase.firestore.util.CustomClassMapper.convertBean(com.google.firebase:firebase-firestore@@18.0.1:502)
at com.google.firebase.firestore.util.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-firestore@@18.0.1:243)
at com.google.firebase.firestore.util.CustomClassMapper.deserializeToType(com.google.firebase:firebase-firestore@@18.0.1:181)
at com.google.firebase.firestore.util.CustomClassMapper.deserializeToParameterizedType(com.google.firebase:firebase-firestore@@18.0.1:258)
at com.google.firebase.firestore.util.CustomClassMapper.deserializeToType(com.google.firebase:firebase-firestore@@18.0.1:179)
at com.google.firebase.firestore.util.CustomClassMapper.access$200(com.google.firebase:firebase-firestore@@18.0.1:53)
at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-firestore@@18.0.1:701)
at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-firestore@@18.0.1:675)
at com.google.firebase.firestore.util.CustomClassMapper.convertBean(com.google.firebase:firebase-firestore@@18.0.1:504)
at com.google.firebase.firestore.util.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-firestore@@18.0.1:243)
at com.google.firebase.firestore.util.CustomClassMapper.convertToCustomClass(com.google.firebase:firebase-firestore@@18.0.1:97)
at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@18.0.1:203)
at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@18.0.1:183)
at com.kinetic.fit.data.firestore.KineticAccount$syncProfile$1.onComplete(KineticAccount.kt:144)
at com.google.android.gms.tasks.zzj.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Please let me know if I misunderstood the solution you proposed.
Thanks in advance for your time.
P
You didn't misunderstand, this just isn't working as expected. You might try applying the property name to the setter as well or other variations including just the blanket @PropertyName("UUID")
.
Another option is to rename the property in code, now that the external form is explicit:
@get:PropertyName("UUID") override var Uuid: String = ""
I'll have a look at why that failure is happening though.
I've confirmed that annotating the setter with @PropertyName("UUID")
has the intended effect.
@PropertyName("UUID")
on the setter results in the error you saw, which essentially just says that there was a mismatch between the value as seen in Firestore vs the expected case of the setter name.(To be clear: this is working as expected, it's just not obvious that annotating both is required.)
I tried with all the combinations of @PropertyName("UUID") on getters, setters and blanket for both Model and TrainingPlanProgress classes.
It either works, but changes it to uuid or fails on the setter like above.
The test is checking against a plain static class and isn't testing against extended abstract class that Model is or whether it works when there is an array of the AllCapsClass on another class. Could these be part of the problem?
Any ideas or any clue whether this is from the Kotlin data class and the way it generates all of the getters and setters on the classes causing some conflict that overrides the @PropertyName decorator?
Thanks in advance.
Sorry for the delay in response here, but a bunch of internal investigation has led us to this conclusion: there is currently no way to create a Java annotation that will end up on Kotlin property getters/setters so the only available option is to modify our POJO mapper to take private field annotations into account. Unfortunately we haven't come up with any way to do that isn't a breaking change for existing users.
I'll recharacterize this as a feature request for supporting @PropertyName
on Kotlin properties.
Thanks, @wilhuff.
Anyone who reads this, the easiest way around at this time is to change the name to something that is not all caps. Hopefully this isn't too crazy of a refactor for you.
I have a problem with the custom class mapper. I have a Profile class that contains an array of TrainingPlanProgress objects.
When I run the set function on the document with the current profile object, the custom object mapper runs and changes the UUID field on all the array items to lowercase uuid.
Profile class:
Model class:
TrainingPlanProgress class:
I have traced this all the way through the sdk to the CustomClassMapper class to this function
At the point where the array of TrainingPlanProgress is returning results from serialization this is what the debugger shows:
you can see o and list still have UUID in the members, but the result being returned has changed it all to lowercase.
When this gets to firestore it changes the objects then the next sync of the profile loses sight of the TrainingPlanProgress items because it can't map by key to the UUID key because it is no longer all upper case.
Here is what the class decompiles to in Android Studio.
my break point was on the last case return result.
Is there a way around this that doesn't require changing all the UUID to uuid in the database?
Please let me know if you need any more information.