My data on firebase uses many fields which have string type, but really are enum values (which I check in my validation rules). To download the data into my Android app, following the guide, the field must be a basic String. I know I can work around this with a second (excluded) field which is an enum, and set this basing on the string value. A short example:
class UserData : BaseModel() {
val email: String? = null
val id: String = ""
val created: Long = 0
// ... more fields omitted for clarity
#Exclude
var weightUnitEnum: WeightUnit = WeightUnit.KG
var weightUnit: String
get() = weightUnitEnum.toString()
set(value) { weightUnitEnum = WeightUnit.fromString(value) }
}
enum class WeightUnit(val str: String) {
KG("kg"), LB("lb");
override fun toString(): String = str
companion object {
#JvmStatic
fun fromString(s: String): WeightUnit = WeightUnit.valueOf(s.toUpperCase())
}
}
Now, while this works, it's not really clean:
The enum class itself is (1) kinda long for an
enum, (2) the insides are repeated for every enum. And I have more of them.
It's not only enums, the created field above is really a timestamp,
not a Long.
Each model uses these enum fields a lot of times, which bloats the model classes with repeatable code...
The helper field/functions are getting much worse/longer for fields with types such as Map<SomeEnum, Timestamp>...
So, is there any way to do this properly? Some library maybe? Or some way to write a magic "field wrapper" that would automatically convert strings to enums, or numbers to timestamps, and so on, but is still compatible with Firebase library for getting/setting data?
(Java solutions are welcome too :) )
If the conversion between a property with your enum value and another property of String type is enough, this can be easily done in a flexible way using Kotlin delegated properties.
To say it short, you can implement a delegate for String properties which performs the conversion and actually gets/sets the value of another property storing the enum values, and then delegate the String property to it.
One possible implementation would look like this:
class EnumStringDelegate<T : Enum<T>>(
private val enumClass: Class<T>,
private val otherProperty: KMutableProperty<T>,
private val enumNameToString: (String) -> String,
private val stringToEnumName: (String) -> String) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return enumNameToString(otherProperty.call(thisRef).toString())
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
val enumValue = java.lang.Enum.valueOf(enumClass, stringToEnumName(value))
otherProperty.setter.call(thisRef, enumValue)
}
}
Note: This code requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. With Gradle, use compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version".
This will be explained below, but first let me add a convenience method to avoid creating the instances directly:
inline fun <reified T : Enum<T>> enumStringLowerCase(
property: KMutableProperty<T>) = EnumStringDelegate(
T::class.java,
property,
String::toLowerCase,
String::toUpperCase)
And a usage example for your class:
// if you don't need the `str` anywhere else, the enum class can be shortened to this:
enum class WeightUnit { KG, LB }
class UserData : BaseModel() {
// ... more fields omitted for clarity
#Exclude
var weightUnitEnum: WeightUnit = WeightUnit.KG
var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum)
}
Now, the explanation:
When you write var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum), you delegate the String property to the constructed delegate object. This means that when the property is accessed, the delegate methods are called instead. And the delegate object, in turn, works with the weightUnitEnum property under the hood.
The convenience function I added saves you from the necessity of writing UserData::class.java at the property declaration site (using a reified type parameter) and provides the conversion functions to EnumStringDelegate (you can create other functions with different conversions at any time, or even make a function that receives the conversion functions as lambdas).
Basically, this solution saves you from the boilerplate code that represents a property of enum type as a String property, given the conversion logic, and also allows you to get rid of the redundant code in your enum, if you don't use it anywhere else.
Using this technique, you can implement any other conversion between properties, like the number to timestamp you mentioned.
I am in similar situation & thus found your question, plus whole lot of other similar questions/answers.
Cant answer your question directly but this is what I ended up doing: I decided to change my app & not use enum data types at all - mainly because of the advice from Google dev portal which shows how bad the enum's are on app's performance. See the video below https://www.youtube.com/watch?v=Hzs6OBcvNQE
Related
I hava a Java interface class that has been converted to Kotlin. On Conversion it creates a lot of errors on the other files as the getters and setters are used.
The Java interface class:
public interface ValueFormElement extends BaseElement {
Pair<Boolean, Hashtable<String,String>> getValue();
String getName();
void setName(String name);
void setValue(String value);
}
On conversion it removes the getter and am using the getter in other classes. This is the generated Kotlin class
interface ValueFormElement : BaseElement {
val value: Pair<Boolean, Hashtable<String, String>>
var name: String
fun setValue(value: String)
}
If anyone has a way of implementing or doing it cleanly with get and set without affecting the rest of the code ,please show me or direct me.
Using var, both a get/set method will be generated for use in Java. For val however, only a get method is provided (since val implies read-only). You can just consolidate to two vars to match the Java implementation:
interface ValueFormElement : BaseElement {
var value: Pair<Boolean, Hashtable<String, String>>
var name: String
}
Java code using this Kotlin interface will still see getters and setters, same as before, so they won't be affected. Only the Kotlin code will be. And if you aren't prepared to fix them at the moment, I wouldn't bother converting the interface to Kotlin at all.
While it's possible to write
fun getName(): String
etc. in a Kotlin interface, it'll make it less usable than the Java version is. Namely, Kotlin won't let you write x.value or x.name = ....
I have a simple TYPE_USE annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public #interface Cool {
}
And the following sample Kotlin class:
class Item(
id: Long? = null,
var names: List<#Cool String> = emptyList())
Is there any way to extract the annotation using Java reflection?
Item.class.getMethod("getName").getAnnotatedReturnType() loses the annotations, same with getting the field.
Can I even get the annotation from Kotlin?
Item::class.memberProperties.elementAt(0).returnType return a KType which has the annotation, but I don't see a way to extract it. Nor to obtain an AnnotatedType from KType, even though I have JDK8 extensions.
All I see is KType#javaType but this returns Type, not AnnotatedType... so it looses the annotations again.
Edit: this is a bug and has been reported. There is not yet a target version, but its priority has been set to Major. This was fixed in Kotlin 1.3.
TL; DR: no...?
The item annotated with #Cool is the first type argument, so you need to retrieve it:
val type = Item::class.memberProperties.elementAt(0).returnType
val arg = type.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
Unfortunately there doesn't seem to be a way to retrieve the annotations on a KType (as you have mentioned).
Strangely, this is a very internal process. Looking into the source for KTypeImpl shows that toString is implemented via ReflectionObjectRenderer.renderType(type), (where type is a KotlinType) which is delegated to DescriptorRenderer.FQ_NAMES_IN_TYPES, which we can see is a DescriptorRenderer with modifiers ALL.
The renderer checks if the type is a subclass of kotlin.reflect.jvm.internal.impl.descriptors.annotations.Annotated, and then accesses its annotations property.
I tried this:
val retType = Item::class.memberProperties.elementAt(0).returnType
val arg = retType.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
val type = arg.type!!
println(type)
val field = type::class.memberProperties.first { it.name == "type" }
val kotlinType = field.call(type) as Annotated
println(kotlinType)
println(kotlinType.annotations)
Unfortunately, I get a ClassNotFoundException for org.jetbrains.kotlin.types.KotlinType, so that option is gone.
Equally strangely, KType is not a subtype of KAnnotatedElement (which is why it has no annotations property).
I suppose this may have been an oversight, since KTypeImpl wraps a KotlinType, which does contain annotations.
I have a generic getter trait
trait Getter[A] {
def get: A
}
and I would like to parse JSON into a List of objects implementing this trait. Two such implementations:
case class CoalesceGetter[A](getters: List[Getter[String]]) extends Getter[A] {
override def get: A = getters.map(_.get).find(_ != null).orNull
}
case class AsnGetter(ipGetter: Getter[String]) extends Getter[Long] {
override def get: Long = 99L // dummy function
}
I would like to parse JSON into the correct Getter class based upon a property called function which corresponds to the class and type which corresponds to the generic type in the case of getters which need a generic (both properties are strings in the json blob I'm parsing). I've looked at custom serializers for json4s but don't see how to work with generics. Any help is appreciated!
First of all, I don't think it is a good idea to jsonify classes with type argument. I think it is a better design to define non-typed (case) classes that are direct equivalent of your json object, and use standard read/write json as provided by many libraries.
But then, to answer your question, I'd like to return another question: how would you do it "manually"?
I.e. how would you write and read different CoalesceGetter[A] with different A?
Here is a proposition: put the type arg in a json field:
"ofInt": {"type-arg":"Int", "getters":[ ... list of getters in json ...]},
"ofDouble":{"type-arg":"Double", "getters":[ ... list of getters in json ...]}
Now, if you'd write the reader, how would you instantiate the 2 ofInt and ofDouble, knowing the type-arg "Int" and "Double" (which are string!).
I see 2 solutions:
1) Either you have a hard-coded map of arg-type string => actual scala type
argType match{
case "Int" => new CoalesceGetter[Int](...)
case "Double" => new CoalesceGetter[Double](...)
}
2) Or you store and read a generalized type as string value in the arg-type string, such as the java Class.forName (see [https://stackoverflow.com/a/7495850/1206998] for example). But this is a really really bad idea IMHO.
(note: if you want to serialize any object just to reload it later or on another computer, don't use json but dedicated serialization such as the Java Serialization or kryo that is used by spark)
I am working on a JAVA web service with mongo, In order to implement mongo java driver POJO api (like Morphia), I establish my POJO like the following:
public class User {
public static final String USER_NAME = "userName";
private String userName;
public User() {
}
// getter && setter //
}
USER_NAME = "userName" is a reference for future use as a filed name. What I'm trying to achieve is that I could use a simple if to check if received data equals to the field name. For example :
User user = new User();
String receivedData = httpRequest.getParameter(User.USER_NAME);
if (receivedData == null) {
return null;
} else {
user.setUserName(receivedData);
userCollection.insertOne(user);
// userCollection is MongoCollection<User>
return Gson().toJson(user);
}
I am looking for a eclipse function or plug in that could auto generate one of the declaration (USER_NAME = "userName" and private String userName) by the other and make sure the consistency of the code.
Of course, that would be appreciated if there's any suggestion for a better practice.
EDIT
Stephan's reflection approach gives a great flexibility for the code. However, compare to my original simplified example, the real situation might be more complex. For example, one httpRequest has many different parameters which are stored in different(n) POJO and each POJO has many different(n) fields. In that case, we will do n*n loop for just getting the field value.
If you want to do the same for all fields of the POJO, consider iterating the result of User.class.getFields(). For each Field get the name (f.getName()), use the name to retrieve the value and set it to the field (f.set(object,value)). With this approach there's no need for a constant like USER_NAME.
EDIT 1: If performance is an issue you may of course collect all those fields into some collection up-front once and for all, but then the bottleneck will remain at having to try httpRequest.getParameter() for each possible field. Nothing specific to the reflective approach.
EDIT 2: In a "nicer" language there could be type checked syntax for retrieving a Field instance, e.g., imagine (not Java):
Field f = User::userName;
This answer basically demonstrates that generating redundant constants is not strictly necessary, since the thing that consistently connects the name to the field already exists, it's a Field.
I am trying to introduce Scala into my Android project, which uses Guice for DI. For Guide to work, I need to add the #Inject annotation to the constructor I would like Guice to use. In my case I created a Scala class and I need to use it in my Java code.
scala:
class scalaClass1(a: String) {
var myA = a
#Inject
def this() = { this("test") }
}
This looks alright, correct? But in another case the constructor does not have any parameters, so I tried
scala:
class scalaClass2() {
var myA: String = null
#Inject
def this() = { this() }
}
And I got an syntax error. Something like recursive definition. Then I tried this:
scala:
class scalaClass2() {
var myA: String = null
#Inject
def scalaClass2() = { this }
}
The code compiled and the app works well on my phone. I have no idea why. I browsed in google, but I could not find any definition/explanation about having a method that has the same name as the class. Why this works? Is there any better solution to my problem?
If you need to to apply #Inject to a constructor without parameters you can use this:
class scalaClass2 #Inject () {
// whatever
}
Note the mandatory empty parentheses. You need them to apply an annotation on the primary constructor. But in this particular case you don't even need #Inject; see below.
In your second example (when you define def this() = { this() }) you are getting an error because you can't define multiple constructors with the same signature, and that's exactly what you are doing - you define primary constructor without parameters and immediately you define secondary constructor, again without parameters.
And in the third example you're really defining a method named scalaClass2 which returns this. It is perfectly valid, but it is not a constructor. As far as I remember, Guice does not need #Inject annotation on parameterless constructor when it is the only constructor in the class, so you can inject scalaClass2 or ask it from Guice, who will create it for you. But you don't really need scalaClass2 method; Guice may call it as a part of method injection procedure but it won't do anything.
Firstly, according to convention class names should start with upper case and methods with lower. But if we would not follow them I would say it is not safe.
Consider having a companion object to the class with apply method defined.
class Person(val name: String, val age: Int)
object Person {
def apply(name: String, age: Int) = new Person(name, age) }
and then create a method with same name and list parameters:
def Person(lastName: String, score: Int): String = s"${lastName} got ${score} points in last game"
Now if you want to make use of object apply method you cannot do it in regular way:
Person("McKenzie", 1000)
will yield McKenzie got 1000 points in last game