Internal fileds (columns) in Room's Entity - kotlin

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.

Related

Kotlin modifying dataclass object key from map changes the reference after modifying variable

I have a MutableMap that its keys are objects from a DataClass (User dataclass), and the values are arrays from other Dataclass (Dog dataclass). If i have a variable with a User object, and i put it in the MutableMap and i test if the map contains the User, it says that is true. But after putting the user in the MutableMap if i change one of the attributes of the User object using the variable that holds the User object, the Map says that it doesnt contains the user object.
This is an example
data class User(
var name: String,
var project: String,
)
data class Dog(
var kind: String
)
fun main(args: Array<String>) {
var mapUserDogs: MutableMap<User, MutableList<Dog>> = mutableMapOf()
var userSelected = User("name2", "P2")
mapUserDogs.put(
User("name1", "P1"),
mutableListOf(Dog("R1"), Dog("R2"))
)
mapUserDogs.put(
userSelected,
mutableListOf(Dog("R21"), Dog("R31"))
)
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
println("\n")
userSelected.name = "Name3"
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
}
The prints statements show this:
User(name=name2, project=P2)
[User(name=name1, project=P1), User(name=name2, project=P2)]
true
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
false
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
Process finished with exit code 0
But it doesn't make sense. Why the map says that it doesn't contains the user object if its clear that it still holds the reference to it after being modified?
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
The user in the keys collection was also changed when i modified the userSelected variable, so now the object has it attribute name as "Name3" in both the variable and in the Map keys, but it still says that it doesnt contains it.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?. And doing the same process in reverse shows the same. If i get from the map the user and i modify it, the userVariable is also modified but if i later test if the map contains the userVariable, it says false.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
There is nothing you can do that preserves both your ability to look up the entry in the map and your ability to modify the key.
Make your data class immutable (val instead of var, etc.), and when you need to change a mapping, remove the old key and put in the new key. That's really the only useful thing you can do.
To add to Louis Wasserman's correct answer:
This is simply the way that maps work in Kotlin: their contract requires that keys don't change significantly once stored. The docs for java.util.Map* spell this out:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
The safest approach is to use only immutable objects as keys. (Note that not just the object itself, but any objects it references, and so on, must all be immutable for it to be completely safe.)
You can get away with mutable keys as long as, once the key is stored in the map, you're careful never to change anything that would affect the results of calling equals() on it. (This may be appropriate if the object needs some initial set-up that can't all be done in its constructor, or to avoid having both mutable and immutable variants of a class.) But it's not easy to guarantee, and leaves potential problems for future maintenance, so full immutability is preferable.
The effects of mutating keys can be obvious or subtle. As OP noticed, mappings may appear to vanish, and maybe later reappear. But depending on the exact map implementation, it may cause further problems such as errors when fetching/adding/removing unrelated mappings, memory leaks, or even infinite loops. (“The behaviour… is not specified” means that anything can happen!)
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
What you're trying to do there is to change the mapping. If you store a map from key K1 to value V, and you mutate the key to hold K2, then you're effectively saying “K1 no longer maps to V; instead, K2 now maps to V.”
So the correct way to do that is to remove the old mapping, and then add the new one. If the key is immutable, that's what you have to do — but even if the key is mutable, you must remove the old mapping before changing it, and then add a new mapping after changing it, so that it never changes while it's stored in the map.
(* The Kotlin library docs don't address this, unfortunately — IMHO this is one of many areas in which they're lacking, as compared to the exemplary Java docs…)
That happens because data classes in Kotlin are compared by value, unlike regular classes which are compared by reference. When you use a data class as a key, the map gets searched for a User with the same string values for the name and project fields, not for the object itself in memory.
For example:
data class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // true
works because, even though they are different objects (and thus different references), they have the same name and project values.
However, if I were to do this:
user1.name = "Christian"
println(user1 == user2) // false
the answer would be false because they don't share the same value for all of their fields.
If I made User a standard class:
class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // false
the answer would be false because they are different references, even though they share the same values.
For your code to work the way you want, make User a regular class instead of a data class.
That's the key difference between regular classes and data classes: a class is passed by reference, a data class is passed by value. Data classes are nothing more than collections of values with (optionally) some methods attached to them, classes are individual objects.

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.

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()()

Best way to define model using AndroidAnnotations #Rest and Kotlin?

I am totally new to Android and Kotlin and I was looking into Android Annotations.
I managed to decode a JSON response using the following code:
class ExampleModel {
#JvmField
final var id: Int = 0
lateinit var title: String
var description: String? = null
var author: Author? = null
}
#Rest(
rootUrl = "...",
converters = [MappingJackson2HttpMessageConverter::class]
)
interface ExampleClient {
#Get("/promotions")
fun getModels(): List<ExampleModel>
}
Now it does work but there are a couple of questions I'd like to ask.
Is it possible to use data classes? I tried but I kept getting an error from MappingJackson2HttpMessageConverter saying that there was no constructor available.
Is it somehow possible to just ignore extra keys that might appear in the JSON? Let's say that I am not interested in the author data for now, is there a way to just remove its declaration without having the decoding fail with "unexpected key"?
Consider that I usually work with Swift so if you could point me to the "Codable" equivalent in Kotlin I would really appreciate it.
Cheers
Kotlin Data classes don't have default constructor which is usually required by json deserialization libraries. Any data class require at least one constructor argument, but you can work around it. Define default values, you can use null. For example:
data class Pojo(val name: String? = null, val age: Int? = null)
Such code will allow to use Pojo() constructor. It should work, but it's better to use json deserializer that is more kotlin native or generate data classes with AutoValue.
Jackson that you're using here allows to ignore fields with #JsonIgnoreProperties.
If you're learning Android, don't start from Android Annotations if you don't have to. It's not very popular or modern solution. I used it in few projects back in the day, those were very difficult to maintain or to introduce new developers. Look into android architecture components and jetpack - google made few nice code labs. Also for json pick Moshi or Gson.

Corda State Evolution - nullable vs. default properties

In Corda, adding new properties to states (state evolution) requires that new properties are nullable in order to remain backwards compatible with previous versions of the state.
data class Version1DummyState(
override val participants: List<AbstractParty>
) : ContractState
data class Version2DummyState(
override val participants: List<AbstractParty>,
val myNewProperty: String? = null
) : ContractState
Since Kotlin also supports properties with default values, I'd like to know why state evolution is restricted to properties of nullable types only, but not non-nullable properties, provided that those properties have a default value?
data class Version2DumyState(
override val participants: List<AbstractParty>,
val myNewProperty: String = "Hello, world."
) : ContractState
My rationale for asking this came from looking at the implicit upgrade sample, in which the obligation state is upgraded to allow the obligor to default on their obligation. true and false accurately represent whether the obligor has defaulted, but null does not. The ability to upgrade with a default value of false seems more natural than using a nullable field.
I think you can make that work by marking the constructor with #JvmOverloads like this:
data class DummyState #JvmOverloads constructor(
override val participants: List<AbstractParty>,
val myNewProperty: String = "blah"
) : ContractState
(don't put version numbers into state class names)
The #JvmOverloads makes the "old" constructors visible to the deserialisation engine for matching.
But, it's probably better to be explicit here with a line of code like:
val inDefault: Boolean get() = myNewProperty ?: false
or
fun priceOr(default: Amount<Currency> = 0.USD) get() = price ?: default
if you really want this.
Backwards compatibility and default values need to be treated quite carefully. A common mistake is to use a default value of zero/empty string/false for newly introduced fields, even when those values are semantically meaningful to the app. That is, knowing that the old message didn't specify something is valuable information that shouldn't be lost or replaced with fragile sentinel values. Consider the new field "price". Prices can't be negative, so perhaps a developer sets a default value of zero. But pricing something as free is a meaningful thing to do - maybe not in today's business scenario, but perhaps tomorrow? Now you have a problem.
Kotlin's type system and syntax is very good at dealing with missing values. It's so easy to substitute a default at the use-site using the ?: operator, I'd be worried about establishing a convention of always supplying a default at the construction site that junior devs follow without being aware of the potential consequences. Explicitly exposing the fact that a default may be substituted forces people to think about whether that's really logical.