I am working on a library where any change in argument refreshes the view. In refresh() function, I am setting some arguments' values to default values.
var viewAlpha= 255
set(value) {
field = value
refresh()
}
fun refresh() {
viewAlpha = 255
invalidate()
}
This is causing StackOverflowError due to obvious reasons.
Caused by: java.lang.StackOverflowError: stack size 8MB
Is it possible to access variables in kotlin without invoking its setter when we are accessing it in the same class. Similar to what we do in java.
One way would be to provide you good ol' backing field to get out of setter-refres cycle:
private var _viewAlpha = 255
var viewAlpha
get() {
return _viewAlpha
}
set(value) {
_viewAlpha = value
refresh()
}
fun refresh() {
_viewAlpha = 255
invalidate()
}
If you want to simplify your logic for multiple fields you can abstract this implementation into separate class and use callback call with direct setter that will work without refresh invocation. Like this:
class Field(val onSetCb: (Field) -> Unit) {
private var viewAlpha = 255
fun get() {
return viewAlpha
}
fun set(value: Int) {
setDirect(value)
onSetCb(this)
}
fun setDirect(value: Int) {
viewAlpha = value;
}
}
// Elsewhere...
fun refresh(field: Field) {
field.setDirect(255)
invalidate()
}
val f = Field(::refresh)
f.set(255)
Related
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)
}
}
Looking for a natural Kotlin way to let startTime be initialized only in a particular place and exactly once.
The following naive implementation have two problems:
it is not thread safe
it does not express the fact "the variable was or will be assigned exactly once in the lifetime of an Item instance"
class Item {
var startTime: Instant?
fun start(){
if (startTime == null){
startTime = Instant.now()
}
// do stuff
}
}
I believe some kind of a delegate could be applicable here. In other words this code needs something similar to a lazy variable, but without initialization on first read, instead it happens only after explicit call of "touching" method. Maybe the Wrap calls could give an idea of possible implementation.
class Wrap<T>(
supp: () -> T
){
private var value: T? = null
private val lock = ReentrantLock()
fun get(){
return value
}
fun touch(){
lock.lock()
try{
if (value == null){
value = supp()
} else {
throw IllegalStateExecption("Duplicate init")
}
} finally{
lock.unlock()
}
}
}
How about combining AtomicReference.compareAndSet with a custom backing field?
You can use a private setter and make sure that the only place the class sets the value is from the start() method.
class Item(val value: Int) {
private val _startTime = AtomicReference(Instant.EPOCH)
var startTime: Instant?
get() = _startTime.get().takeIf { it != Instant.EPOCH }
private set(value) = check(_startTime.compareAndSet(Instant.EPOCH, value)) { "Duplicate set" }
fun start() {
startTime = Instant.now()
}
override fun toString() = "$value: $startTime"
}
fun main() = runBlocking {
val item1 = Item(1)
val item2 = Item(2)
println(Instant.now())
launch { println(item1); item1.start(); println(item1) }
launch { println(item1) }
delay(1000)
println(item2)
item2.start()
println(item2)
println(item2)
item2.start()
}
Example output:
2021-07-14T08:20:27.546821Z
1: null
1: 2021-07-14T08:20:27.607365Z
1: 2021-07-14T08:20:27.607365Z
2: null
2: 2021-07-14T08:20:28.584114Z
2: 2021-07-14T08:20:28.584114Z
Exception in thread "main" java.lang.IllegalStateException: Duplicate set
I think your Wrap class is a good starting point to implement this. I would definitely make it a property delegate and touch() could be much simplified:
fun touch() {
synchronized(this) {
check(value == null) { "Duplicate init" }
value = supp()
}
}
Then you can remove lock. But generally, this is a good approach.
If you would like to reuse lazy util from stdlib then you can do this by wrapping it with another object which does not read its value until asked:
class ManualLazy<T : Any>(private val lazy: Lazy<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return if (lazy.isInitialized()) lazy.value else null
}
fun touch() {
lazy.value
}
}
class Item {
private val _startTime = ManualLazy(lazy { Instant.now() })
val startTime: Instant? by _startTime
fun start(){
_startTime.touch()
}
}
Of course, depending on your needs you can implement it in a much different way, using a similar technique.
This may be considered exploiting or hacking lazy util. I agree and I think Wrap approach is a better one.
Consider following Kotlin-Code:
class Foo(input: Int) {
private var someField: Int = input
get() = -field
set(value) {
field = -value
}
fun bar() {
println(someField)
}
}
fun main() {
Foo(1).bar()
}
This prints -1 in the console which means that inside method bar() someField references the attribute and not the corresponding getter. Is there a way that allows me to use the get()-method as if I was referencing this field from outside?
Perhaps you could track the "raw" value separately from the negative value? Something like this:
class Foo(input: Int) {
private var _someField: Int = input
var someField: Int
get() = -_someField
set(value) {
_someField = -value
}
fun bar() {
println(someField)
}
}
Now the class internals can reference _someField to deal directly with the raw value, while outside clients can only "see" someField.
With all the well-known single-function listeners we can use a simpler lambda notation
view.setOnClickListener { do() }
instead of the original, longer Java way of
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
do()
}
})
But what exactly makes this work? I tried to do the same with my own listener:
private var listener: OnCopyPasteClickListener? = null
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: OnCopyPasteClickListener) {
listener = onCopyPasteClickListener
}
and while the long approach works just fine:
copypaste.setOnCopyPasteClickListener(object : CopyPasteMenu.OnCopyPasteClickListener {
override fun onPasteClick(text: String) {
do(text)
}
})
I can't make it accept the short one:
copypaste.setOnCopyPasteClickListener {
do(it)
}
The IDE gives a type mismatch error.
Actually, if you have only one function to be invoked, I recommend you use Kotlin Callback.
typealias OnDoWorkListener = ((String) -> Unit)
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
}
And in your function, you just set the callback to it
fun main() {
val work = Work()
work.doWork = {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething();
}
We can also use function to set the listener as well.
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
fun setOnWorkListener(listener: OnDoWorkListener) {
doWork = listener
}
}
fun main() {
val work = Work()
work.setOnWorkListener {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething()
}
Higher order functions make this work:
Kotlin functions are first-class, which means that they can be stored
in variables and data structures, passed as arguments to and returned
from other higher-order functions. You can operate with functions in
any way that is possible for other non-function values.
From the same page:
Passing a lambda to the last parameter
In Kotlin, there is a convention that if the last parameter of a
function accepts a function, a lambda expression that is passed as the
corresponding argument can be placed outside the parentheses:
val product = items.fold(1) { acc, e -> acc * e }
If the lambda is the only argument to that call, the parentheses can
be omitted entirely:
run { println("...") }
Knowing this, a possible update on your class would look like:
class CopyPaste {
private var listener: (String) -> Unit = {}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: (String) -> Unit) {
listener = onCopyPasteClickListener
}
fun doCopyPaste(value: String) {
listener.invoke(value)
}
}
fun main() {
val copyPaste = CopyPaste()
copyPaste.setOnCopyPasteClickListener { println(it) }
copyPaste.doCopyPaste("ClipboardContent!")
}
The class CopyPaste stores the listener, which is a function that takes a String parameter and does not return anything. Its function setOnCopyPasteClickListener accepts a function with the same signature as the listener property and at the end doCopyPaste accepts a String parameter and passes it to the stored function.
Actually, just after I posted, I searched for more thoughts and found this thread: https://youtrack.jetbrains.com/issue/KT-7770 This is indeed a debated limitation as it currently only applies to Java, not Kotlin itself. There is also a suggestion there that gives almost the required simplicity:
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
companion object {
inline operator fun invoke(crossinline op: (text: String) -> Unit) =
object : OnCopyPasteClickListener {
override fun onPasteClick(text: String) = op(text)
}
}
}
and then, thanks to this overloaded operator, it can be called as:
copypaste.setOnCopyPasteClickListener(CopyPasteMenu.OnCopyPasteClickListener { text ->
do(text)
})
But as the suggested answers offer a more idiomatic solution, I'll accept one of those, I only wanted to include this approach here for reference.
Is there a way to tell if a lazy val has been initialised in Kotlin without initialising it in the process?
eg if I have a lazy val, querying if it is null would instantiate it
val messageBroker: MessageBroker by lazy { MessageBroker() }
if (messageBroker == null) {
// oops
}
I could potentially use a second variable, but that seems messy.
private var isMessageBrokerInstantiated: Boolean = false
val messageBroker: MessageBroker by lazy {
isMessageBrokerInstantiated = true
MessageBroker()
}
...
if (!isMessageBrokerInstantiated) {
// use case
}
Is there some sexy way of determining this, like if (Lazy(messageBroker).isInstantiated())?
Related (but not the same): How to check if a "lateinit" variable has been initialized?
There is a way, but you have to access the delegate object which is returned by lazy {}:
val messageBrokerDelegate = lazy { MessageBroker() }
val messageBroker by messageBrokerDelegate
if(messageBrokerDelegate.isInitialized())
...
isInitialized is a public method on interface Lazy<T>, here are the docs.
Since Kotlin 1.1, you can access a property delegate directly using .getDelegate().
You can write an extension property for a property reference that checks that it has a Lazy delegate that has already been initialized:
/**
* Returns true if a lazy property reference has been initialized, or if the property is not lazy.
*/
val KProperty0<*>.isLazyInitialized: Boolean
get() {
if (this !is Lazy<*>) return true
// Prevent IllegalAccessException from JVM access check on private properties.
val originalAccessLevel = isAccessible
isAccessible = true
val isLazyInitialized = (getDelegate() as Lazy<*>).isInitialized()
// Reset access level.
isAccessible = originalAccessLevel
return isLazyInitialized
}
Then at the use site:
val messageBroker: MessageBroker by lazy { MessageBroker() }
if (this::messageBroker.isLazyInitialized) {
// ... do stuff here
}
This solution requires kotlin-reflect to be on the classpath. With Gradle, use compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
The isAccessible = true part is required for the .getDelegate(), because otherwise it cannot access the private field storing the delegate reference.
Testing if the lazy property is easy enough:
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
val KProperty0<*>.isLazyInitialized: Boolean
get() {
// Prevent IllegalAccessException from JVM access check
isAccessible = true
return (getDelegate() as Lazy<*>).isInitialized()
}
…but you can make it even easier to reference a property without initializing it:
/**
* Returns the value of the given lazy property if initialized, null
* otherwise.
*/
val <T> KProperty0<T>.orNull: T?
get() = if (isLazyInitialized) get() else null
Now you can do things like:
private val myList by lazy {
mutableSetOf<String>()
}
fun add(str: String) {
// Create the list if necessary
myList += str
}
fun remove(str: String) {
// Don't create the list
::myList.orNull?.remove(str)
}
fun clear() {
// Don't create the list
::myList.orNull?.clear()
}