There are many examples how to use init during class construction, but they are mostly without using actual class properties.
What I need to do is init a class property from database on object init. So far I have this:
class MyObj constructor(val id: Long){
var data: MutableMap? = null
init {
data = db.find(id) // more like pseudocode, db fetch is done and result assigned to data property
}
}
But it seems a little overcomplicated to me. Is there any better, more elegant way to do this ?
You can initialize the property directly in the class body:
class MyObj(val id: Long) {
val data: MutableMap = db.find(id)
}
Then you won't need to declare it as a nullable type, and may be able to use val instead of var. (I've also removed the constructor keyword which is redundant.).
You can just write:
class MyObj constructor(val id: Long){
val data: Map<String, String> = mapOf()
}
It's equivalent to:
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = mapOf()
}
}
Init block is useful if you need introduce some logic for your object initialization, like error checking etc. Ofc you can do it with or without init block. But remember what is more readable:
class MyObj constructor(val id: Long){
val data: Map<String, String> = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
} // without init
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
}
} // with init
You can also init this value using lazy (what means property will be initialized as function, but value will be assigned when you first ask for it, not when object is created). I guess taking value from db can be quite long, so it may be useful:
val data: Map<String, String> by lazy { mapOf() }
Related
I try to put a key-value to ConcurrentHashMap in Kotlin buf failed. The compiler tells me: No set method providing array access.
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: Map<Long, Any> = ConcurrentHashMap()
fun init() {
unionMaps[1] = NULL // No set method providing array access
}
}
I don't know what does it mean. Is ConcurrentHashMap in Kotlin unmutable?
As Sweeper says you have the wrong type on unionMap.
Is ConcurrentHashMap in Kotlin unmutable
No, but unlike Java Maps/Lists/Sets have mutable and immutable interfaces and the interfaces Map, List, Set are the immutable variation.
You want this:
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: MutableMap<Long, Any> = ConcurrentHashMap()
init {
unionMaps[1] = NULL
}
}
And the other thing is that your original function init() does not get executed as the instance is constructed, you probably want init {... as I show above
but the problem with that approach is that now you have made the map mutable outside your proxy class which might not be your intention, in which case you could do this:
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: Map<Long, Any> = ConcurrentHashMap()
init {
(unionMaps as MutableMap)[1] = NULL
}
}
I have used the memberExtensionProperties() method, but result collection of the extension properties is empty. The test code is attached. What is the right procedure?
class ExtensionPropertyTest {
class DummyClass{}
val DummyClass.id get() = 99
val DummyClass.name get() = "Joe"
#Test
fun testExtensionProperties() {
val dummyClass = DummyClass()
expect(dummyClass.id).toEqual(99) // OK
val properties = DummyClass::class.memberExtensionProperties
.stream()
.toList()
expect(properties).toHaveSize(2) // Fails due a zero size
}
}
memberExtensionProperties does not return extensions over a class, but its members that are at the same time extensions:
fun main() {
println(DummyClass::class.memberExtensionProperties)
}
class DummyClass {
val String.foo: Int
get() = toInt()
}
It is not that easy if at all possible to find all extensions over a class, because extensions are detached from their receivers and they can be located anywhere in the classpath.
In Kotlin, accessing an abstract val in an init block causes a NullPointerException since the field is overridden by an extending class after the super class's init block executes.
The ideal solution would be a way to declare some code/function to execute after all stages of object instantiation are complete. I can only think of creating an initialize() function and manually calling it, which is bad because it's not automatic. Sticking it in init block doesn't work as shown in the below example.
As a comment pointed out below, instead of overriding fields, they can be passed in as parameters, but that doesn't work for my actual use-case. It adds a lot of clutter for object construction and is a nightmare when other classes try to extend it.
Below example shows a solution using coroutines. Waiting for a field to != null works in this case, but doesn't not when map is an open val with a default value that may or may not get overridden.
The problem is somewhat solved, but the solution is far from optimal. Any suggestions and alternative solutions would be greatly appreciated.
#Test #Suppress("ControlFlowWithEmptyBody", "SENSELESS_COMPARISON")
fun abstractValAccessInInitNPE() {
val key = "Key"
val value = "Value"
abstract class Mapper {
abstract val map: HashMap<String, String>
fun initialize() { map[key] = value }
}
// Test coroutine solution on abstract mapper
println("CoroutineMapper")
abstract class CoroutineMapper: Mapper() {
init {
GlobalScope.launch {
while (map == null) {}
initialize()
}
}
}
val coroutineMapper = object : CoroutineMapper() {
override val map = HashMap<String, String>()
}
val start = System.nanoTime()
while (coroutineMapper.map.isEmpty()) {} // For some reason map == null doesn't work
println("Overhead: ${(System.nanoTime() - start) / 1000000.0} MS")
println("Mapped: ${coroutineMapper.map[key].equals(value)}")
// Test coroutine solution on open mapper
println("\nDefaultMapper")
open class DefaultMapper: Mapper() {
override val map = HashMap<String, String>()
}
val newMap = HashMap<String, String>()
val proof = "Proof"
newMap[proof] = proof
val defaultMapper = object: DefaultMapper() {
override val map = newMap
}
Thread.sleep(1000) // Definitely finished by the end of this
println("Mapped: ${defaultMapper.map[proof].equals(proof) && defaultMapper.map[key].equals(value)}")
// Basic solution (doesn't work)
println("\nBrokenMapper")
abstract class BrokenMapper: Mapper() {
init { initialize() } // Throws NPE because map gets overridden after this
}
val brokenMapper = object: BrokenMapper() {
override val map = HashMap<String, String>()
}
println("Mapped: ${brokenMapper.map[key].equals(value)}")
}
An open (as all abstract functions are) function should never be called from a constructor because then the class's initial state cannot be guaranteed in the superclass. It can lead to all kinds of very tricky bugs.
Usually there's a good way to design around this problem if you take a step back. For instance, instead of making the map an abstract property, make it a constructor parameter in the superclass. Then you know it's already initialized before subclass constructors can try to use it.
abstract class Mapper(key: String, value: String, val map: HashMap<String, String>)
abstract class DecentMapper(key: String, value: String, map: HashMap<String, String>) : Mapper(key, value, map) {
init {
map[key] = value
}
}
val key = "Key"
val value = "Value"
val decentMapper = object : DecentMapper(key, value, HashMap()){
//...
}
I'm trying to build a class where certain values are Observable but also Serializable.
This obviously works and the serialization works, but it's very boilerplate-heavy having to add a setter for every single field and manually having to call change(...) inside each setter:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
#Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
correctly outputs
changing value2
{"value2":"test2"}
I've tried introducing Delegates:
interface Observable {
fun change(message: String) {
println("changing $message")
}
#Suppress("ClassName")
class default<T>(defaultValue: T) {
private var value: T = defaultValue
operator fun getValue(observable: Observable, property: KProperty<*>): T {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
#Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" }) correctly triggers change detection, but it doesn't serialize:
changing value1
{}
If I go from Observable to ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
#Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
the result is the same:
changing blah!
{}
Similarly for Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
outputs:
changing value4
{}
Delegates just doesn't seem to work with Kotlin Serialization
What other options are there to observe a property's changes without breaking its serialization that will also work on other platforms (KotlinJS, KotlinJVM, Android, ...)?
Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now.
There is an open issue #1578 on GitHub regarding this feature.
According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original object. Also you could write a custom serializer to support the serialization of Kotlin Delegates, which seems to be even more boilerplate, then writing custom getters and setters, as proposed in the question.
Data Transfer Object
By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.
This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as #Serializable.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
#Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
This yields the following result:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
Custom data type
If changing the data type is an option, you could write a wrapping class which gets (de)serialized transparently. Something along the lines of the following might work.
#Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
Which yields the following result:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
You however lose information about which property changed, as you don't have easy access to the field name. Also you have to change your data structures, as mentioned above and might not be desirable or even possible. In addition, this work only for Strings for now, even though one might make it more generic though.
Also, this requires a lot of boilerplate to start with. On the call site however, you just have to wrap the actual value in an call to obs.
I used the following boilerplate to get it to work.
typealias OnChange = (before: String, after: String) -> Unit
#Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}
I played about with Kotlin's unsupported JavaScript backend in 1.0.x and am now trying to migrate my toy project to 1.1.x. It's the barest bones of a single-page web app interfacing with PouchDB. To add data to PouchDB you need JavaScript objects with specific properties _id and _rev. They also need to not have any other properties beginning with _ because they're reserved by PouchDB.
Now, if I create a class like this, I can send instances to PouchDB.
class PouchDoc(
var _id: String
) {
var _rev: String? = null
}
However, if I do anything to make the properties virtual -- have them override an interface, or make the class open and create a subclass which overrides them -- the _id field name becomes mangled to something like _id_mmz446$_0 and so PouchDB rejects the object. If I apply #JsName("_id") to the property, that only affects the generated getter and setter -- it still leaves the backing field with a mangled name.
Also, for any virtual properties whose names don't begin with _, PouchDB will accept the object but it only stores the backing fields with their mangled names, not the nicely-named properties.
For now I can work around things by making them not virtual, I think. But I was thinking of sharing interfaces between PouchDoc and non-PouchDoc classes in Kotlin, and it seems I can't do that.
Any idea how I could make this work, or does it need a Kotlin language change?
I think your problem should be covered by https://youtrack.jetbrains.com/issue/KT-8127
Also, I've created some other related issues:
https://youtrack.jetbrains.com/issue/KT-17682
https://youtrack.jetbrains.com/issue/KT-17683
And right now You can use one of next solutions, IMO third is most lightweight.
interface PouchDoc1 {
var id: String
var _id: String
get() = id
set(v) { id = v}
var rev: String?
var _rev: String?
get() = rev
set(v) { rev = v}
}
class Impl1 : PouchDoc1 {
override var id = "id0"
override var rev: String? = "rev0"
}
interface PouchDoc2 {
var id: String
get() = this.asDynamic()["_id"]
set(v) { this.asDynamic()["_id"] = v}
var rev: String?
get() = this.asDynamic()["_rev"]
set(v) { this.asDynamic()["_rev"] = v}
}
class Impl2 : PouchDoc2 {
init {
id = "id1"
rev = "rev1"
}
}
external interface PouchDoc3 { // marker interface
}
var PouchDoc3.id: String
get() = this.asDynamic()["_id"]
set(v) { this.asDynamic()["_id"] = v}
var PouchDoc3.rev: String?
get() = this.asDynamic()["_rev"]
set(v) { this.asDynamic()["_rev"] = v}
class Impl3 : PouchDoc3 {
init {
id = "id1"
rev = "rev1"
}
}
fun keys(a: Any) = js("Object").getOwnPropertyNames(a)
fun printKeys(a: Any) {
println(a::class.simpleName)
println(" instance keys: " + keys(a).toString())
println("__proto__ keys: " + keys(a.asDynamic().__proto__).toString())
println()
}
fun main(args: Array<String>) {
printKeys(Impl1())
printKeys(Impl2())
printKeys(Impl3())
}
I got a good answer from one of the JetBrains guys, Alexey Andreev, over on the JetBrains forum at https://discuss.kotlinlang.org/t/controlling-the-jsname-of-fields-for-pouchdb-interop/2531/. Before I describe that, I'll mention a further failed attempt at refining #bashor's answer.
Property delegates
I thought that #bashor's answer was crying out to use property delegates but I couldn't get that to work without infinite recursion.
class JSMapDelegate<T>(
val jsobject: dynamic
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return jsobject[property.name]
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
jsobject[property.name] = value
}
}
external interface PouchDoc4 {
var _id: String
var _rev: String
}
class Impl4() : PouchDoc4 {
override var _id: String by JSMapDelegate<String>(this)
override var _rev: String by JSMapDelegate<String>(this)
constructor(_id: String) : this() {
this._id = _id
}
}
The call within the delegate to jsobject[property.name] = value calls the set function for the property, which calls the delegate again ...
(Also, it turns out you can't put a delegate on a property in an interface, even though you can define a getter/setter pair which work just like a delegate, as #bashor's PouchDoc2 example shows.)
Using an external class
Alexey's answer on the Kotlin forums basically says, "You're mixing the business (with behaviour) and persistence (data only) layers: the right answer would be to explicitly serialise to/from JS but we don't provide that yet; as a workaround, use an external class." The point, I think, is that external classes don't turn into JavaScript which defines property getters/setters, because Kotlin doesn't let you define behaviour for external classes. Given that steer, I got the following to work, which does what I want.
external interface PouchDoc5 {
var _id: String
var _rev: String
}
external class Impl5 : PouchDoc5 {
override var _id: String
override var _rev: String
}
fun <T> create(): T = js("{ return {}; }")
fun Impl5(_id: String): Impl5 {
return create<Impl5>().apply {
this._id = _id
}
}
The output of keys for this is
null
instance keys: _id
__proto__ keys: toSource,toString,toLocaleString,valueOf,watch,unwatch,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__,__proto__,constructor
Creating external classes
Three notes about creating instances of external classes. First, Alexey said to write
fun <T> create(): T = js("{}")
but for me (with Kotlin 1.1) that turns into
function jsobject() {
}
whose return value is undefined. I think this might be a bug, because the official doc recommends the shorter form, too.
Second, you can't do this
fun Impl5(_id: String): Impl5 {
return (js("{}") as Impl5).apply {
this._id = _id
}
}
because that explicitly inserts a type-check for Impl5, which throws ReferenceError: Impl5 is not defined (in Firefox, at least). The generic function approach skips the type-check. I'm guessing that's not a bug, since Alexey recommended it, but it seems odd, so I'll ask him.
Lastly, you can mark create as inline, though you'll need to suppress a warning :-)