How to determine the initialization order of Kotlin vals? - kotlin

Suppose I have a val that is defined as follows
import a.b.FIRSTNAME;
val FULLNAME = "$FIRSTNAME/aaron"
And this FIRSTNAME is defined in a.b as
val FISTNAME = "LINCON";
I will find that sometimes FULLNAME takes the value of null/aaron in the process of using it.

As I answered here, the order in which Kotlin initialises global properties is unspecified and platform dependent. This is especially annoying when you have multiple files with properties that depend on each other.
See this Kotlin/Native bug report, and this Kotlin/JS bug report about people finding the initialisation behaviour across multiple files confusing.
In any case, you should avoid this kind of "top level properties depending on each other". And in this particular case, you can rewrite FULLNAME using a getter:
val firstName = "LINCON";
val fullName get() = "$firstName/aaron"
Alternatively, I think adding const will also work. The expressions will now be evaluated at compile time by the compiler, and inlined to whenever you use them.
const val FIRST_NAME = "LINCON";
const val FULL_NAME = "$FIRST_NAME/aaron"

Related

Kotlin: get members of a data class by reflection in the order they have been defined

Assume the following simple example data class:
data class SomeDataClass(
var id: String,
var name: String,
var deleted: String
)
With the following code it is possible to get the properties (and set or get their values):
import kotlin.reflect.full.memberProperties
val properties = SomeDataClass::class.memberProperties
print(properties.map { it.name }) // prints: [deleted, id, name]
The map within the print statement will return a List with the name of the properties in alphabetical order. I need the list in the order they have been defined in the source code, in this case: [id, name, deleted].
It doesn't seem achievable purely through reflection. The only solution I could come up with is to use a helper class defining the order:
val SomeDataClass_Order = listOf("id", "name", "deleted")
This wouldn't be a problem for one or two classes, but it is for hundreds of data classes with the largest one having up to almost one hundred properties.
Any idea would be welcome. I do not need detailed code, rather hints (like parsing the source code, annotations, etc).
If all the properties are declared in the primary constructor, you could "cheat":
val propertyNames = SomeDataClass::class.primaryConstructor!!.parameters.map { it.name }
If you want the KPropertys:
val properties = propertyNames.map { name ->
SomeDataClass::class.memberProperties.find { it.name == name }
}
This unfortunately doesn't find the properties that are declared in the class body.
I don't know about other platforms, but on Kotlin/JVM, the order in which the backing fields for the properties are generated in the class file is not specified, and a quick experiment finds that the order (at least for the version of kotlinc that I'm using right now), the order is the same as the declaration order. So in theory, you could read the class file of the data class, and find the fields. See this related answer for getting the methods in order. Alternatively, you can use Java reflection, which also doesn't guarantee any order to the returned fields, but "just so happens" to return them in declaration order:
// not guaranteed, might break in the future
val fields = SomeDataClass::class.java.declaredFields.toList()
If you do want to get the properties declared inside the class body in order too, I would suggest that you don't depend on the order at all.

Kotlin: Prevent mixup of same typed parameters

How can I save the cats from being served for dinner?
data class Animal(
val name: String
)
data class ToDo(
val toEat: List<Animal>,
val toFeed: List<Animal>,
)
val cats = listOf(Animal("fluffy"))
val chickens = listOf(Animal("chic"))
// help the kittens!!!
ToDo(cats, chickens)
Note: This is a simplified example and I cant change the Type of cats/chickens to something like Cat/Chicken.
OOP solution
You could do the following:
Animal should be an interface.
Cat and Chicken should be two different implementation of Animal.
You could define an Interface named Eatable which will have concrete implementations.
Change val toEat: List<Animal> in val toEat: List<Eatable>
Cat will implement Animal and not Eatable
Another solution
Sorry but i read just now that you cannot define 2 different classes for Cat and Chicken.
You should still define this in Animal (pseudocode):
boolean eatable = false;
fun isEatable(): boolean {
return eatable;
}
So you can define Animal.eat(Animal) to check if the animal you are trying to eat could be eaten.
If you can't give them different types, then I don't think there's any way the compiler could spot this problem.  However, you could make it more obvious by simply giving the parameter names:
ToDo(toEat = chickens, toFeed = cats)
That way, it's very clear which list goes with which parameter.  (It also means that the order of the parameters doesn't matter; you can swap them, as long as each is given once.)
It would still compile if you mixed them up:
ToDo(toEat = cats, toFeed = chickens)
…but at least that would be obvious and doesn't need you to check the documentation to see the problem.
A caller could still choose to omit the parameter names, though.  To prevent that, you'd have to restructure the ToDo class.  For example, you could use the builder pattern, and give something like:
ToDo().toEat(chickens).toFeed(cats)
Or you could simply remove the parameters from the constructor, requiring the caller to set properties by name, e.g.:
ToDo().apply{ toEat = chickens; toFeed = cats }
Those are a little more long-winded to call, of course, but force the caller to give the property names explicitly — again, making the problem fairly obvious to human readers (if not to the compiler).

Instantiate Kotlin class from string

I have a list of classes:
val availableClasses = listOf<Whatever>(
classA(),
classB(),
classC()
)
I am randomly selecting an item from this list using:
private var selection: Whatever = availableClasses.random()
Unfortunately, I think this approach is instantiating every class included in the list when the list is loaded.
I am hoping to work around this by replacing the list of classes with a list of strings:
val availableClasses = listOf<String>(
"classA",
"classB",
"classC"
)
Then once I have a selected string, instantiate only that one; something like:
private var selection: String = availableClasses.random()
// pseudo-code
val chosenClass = selection.toClass()
I can reference classes in Python using strings with the getattr function.
Is anything like this possible in Kotlin?
I'm also open to better approaches to this problem.
Instantiating classes by String name is more error-prone than using a constructor, because it relies on using a fully qualified, correctly spelled name, and the class having a specific constructor (either empty, or with specific arguments). So it can be done, but should be avoided when there are safer ways of doing it (ways where the compiler will give you an error if you're doing it wrong, instead of having an error occur only after you run the compiled program).
If I understand correctly, you want a list of classes that will only be instantiated one-at-a-time at random. One way to do this would be to make a list of class constructors.
val classConstructors = listOf<() -> Any>(
::ClassA,
::ClassB,
::ClassC
)
val randomInstantiatedClass = classConstructors.random()()

Using kotlin expression annotations

Kotlin allows to annotate expressions. It is however unclear, how such annotations may be useful and how to use them.
Let's say in following example I would like to check, that string contains number specified in #MyExpr annotation. Can this be achieved and how?
#Target(AnnotationTarget.EXPRESSION)
#Retention(AnnotationRetention.SOURCE)
annotation class MyExpr(val i: Int) {}
fun someFn() {
val a = #MyExpr(1) "value#1";
val b = #MyExpr(2) "value#2";
}
Specifying #Target(AnnotationTarget.EXPRESSION) is just a way of telling the compiler where the user of the annotation can put it.
It does not do anything on it's own rather than that.
So e.g.
#Target(AnnotationTarget.EXPRESSION)
#Retention(AnnotationRetention.SOURCE)
annotation class Something
// compiler will fail here:
#Something class Foo {
// but will succeed here:
val a = #Something "value#1"
}
Unless you're writing an Annotation Processor (so a thing that looks for Annotations and does something with them), your annotations have just informational value. They are just a signal to other devs (or future You) of something.
#Target(AnnotationTarget.EXPRESSION)
#Retention(AnnotationRetention.SOURCE)
annotation class UglyAndOldCode
val a = #UglyAndOldCode "this is something old and requires refactoring"
If you want to implement what you've stated in your question you would have to create an Annotation Processor that checks expressions marked with MyExpr for the condition that you've specified.

Internal fileds (columns) in Room's Entity

I'd like to mark some Room entity's properties as internal. E.g.
#Entity(tableName = "users")
class User {
// ...
#ColumnInfo(name = "admin_id")
internal var adminId: String? = null
}
However, this produce compile errors like:
Error:(10, 1) error: Cannot find getter for field.
The only way how to make this works seems to use lateinit modifier, though, it can't be used for nullable neither primitive fields.
I've tried a "hack": a private field with internal getter/setter, but that doesn't work either.
The compiled generated version obviously adds some suffix to the generated methods (setAdminId$sdk_debug) that doesn't work with room. The "lateinited" field's setters/getters have this suffix too, but the field stay itself public.
Is there any way how to make columns internal?
It seems its getting supported in latest Room 2.5.0-alpha01
Old answer: I didn't solve this and I have to define new set of entities and mapper between them.
The internal names get mangled by Kotlin, so I made it work by just making sure the correct name is used with #JvmName:
#Entity(tableName = "users")
class User {
// ...
#ColumnInfo(name = "admin_id")
#get:JvmName("adminId")
internal var adminId: String? = null
}
Note: This might make it easier to accidentally use this from Java then.