List setter is not being called when appending a new value - kotlin

I had a workaround solution for this problem which involves reassigning the variable to itself; however, this doesn't seem efficient.
fun main() {
val x = Example1()
x.listOfInt = mutableListOf(3,4)
x.listOfInt.add(455)
}
class Example1 {
var listOfInt: MutableList<Int> = mutableListOf()
set(value) {
println("setting it here to value => $value")
field = value
}
}
// It only prints:
// setting it here to value => [3, 4]
As you can see, the setter doesn't get triggered when the value is appended which effectively changing the value of the collection. Is there a better way to do this?
Here's a link to the code on Kotlin's playground
The reason this is odd to me because of the below similar code in Swift which does the expected when the Array is appended:
import Foundation
class Example1 {
var fieldProperty: Int = 0
var listOfInt: [Int] = [] {
didSet {
print("setting it here to value => \(listOfInt) vs oldValue: \(oldValue)")
}
}
}
let x = Example1()
x.listOfInt = [3,4]
x.listOfInt.append(455)
// this prints:
// setting it here to value => [3, 4] vs oldValue: []
// setting it here to value => [3, 4, 455] vs oldValue: [3, 4]
I know I might be comparing apples to oranges, but I'm just curious if there's a better solution.

The use of set(value) { } is the equivalent of implementing a setter on that variable.
The code you provided could be interpreted as:
class Example1 {
var listOfInt: MutableList<Int> = mutableListOf()
fun set(value: MutableList<Int>) {
println("setting it here to value => $value")
this.listOfInt = value
}
}
When you call x.listOfInt.add(455) you are calling the add method on the list defined in the x instance of Example1.
Try calling x.listOfInt = mutableListOf(1,2,3) and you'll see your setter will correctly get called.
To implement what you want to do, there is several ways:
Implement a new version of MutableList<> which overrides the method add (this might be overkill).
Create a add method in your Example1 class which will look like this:
fun add(value: Int) {
println("adding value to list => $value")
listOfInt.add(value)
}
You main will now look like this:
fun main() {
val x = Example1()
x.listOfInt = mutableListOf(3,4)
x.add(455)
}

Related

Kotlin property delegation not working as expected

I'm confused about the different behaviour depending whether I use getters or delegated properties. Consider the following:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH GETTERS:
val partA get() = parts.a
val partB get() = parts.b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
This code splits the string raw into two parts the first time parts is accessed. All later calls to parts will return the cached parts, even if raw changes. Output:
#1
#2
#2
a=one, b=two
The value of raw is empty when Test is created, but the accessors aren't called until we've set raw to some string. When partA and partB are finally accessed, they contain the correct value.
If I use property delegation instead, the code no longer works:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH DELEGATION:
val partA by parts::a
val partB by parts::b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
All I've changed here is that partA is now delegated to parts::a, and the same for partB. For some strange reason, partA and partB are now accessed before the value of raw is set, so cachedParts is initilized with two empty parts. Output:
#2
#2
#1
a=, b=
Can someone explain what is going on here?
See what your delegated properties translate to in the documentation here. For example, partA translates to:
private val partADelegate = parts::a
val partA: String
get() = partADelegate.getValue(this, this::partA)
Notice that the callable reference expression part::a is used to initialise partADelegate. This expression is evaluated when the instance of Test is created, before println("#1").
To evaluate parts::a, parts must be first evaluated. After all, this is a reference to the a property of parts, not a reference to parts.
Therefore, parts ends up being evaluated before raw gets its value.

Creating an object builder with error handling using Arrow - Pattern match multiple Eithers

I have class A:
class A (private var z: String, private var y: String, private var x: Int)
I want to create a failsafe builder for it. The builder should return Either the list of Exceptions (e.g. when values are missing) or the created values. What is the recommended way to create something like this? Or is there a conceptually better approach?
My own approach to it:
sealed class ABuilderException {
object MissingXValue : ABuilderException()
object MissingYValue : ABuilderException()
object MissingZValue : ABuilderException()
}
import arrow.core.Either
import arrow.core.Option
import arrow.core.none
import arrow.core.some
class ABuilder {
private var x : Option<Int> = none()
private var y : Option<String> = none()
private var z : Option<String> = none()
fun withX(x : Int) : ABuilder {
this.x = x.some();
return this;
}
fun withY(y : String) : ABuilder {
this.y = y.some();
return this;
}
fun withZ(z : String) : ABuilder {
this.z = z.some();
return this;
}
fun build() : Either<A, List<ABuilderException>> {
var xEither = x.toEither { ABuilderException.MissingXValue }
var yEither = y.toEither { ABuilderException.MissingYValue }
var zEither = z.toEither { ABuilderException.MissingZValue }
// If all values are not an exception, create A
// otherwise: Return the list of exceptions
}
}
How could I best complete the build code?
I favor a solution that avoids deep nesting (e.g. orElse or similar methods) and avoids repeating values (e.g. by recreating Tuples), because this may lead to typos and makes it harder to add/remove properties later.
First you need to change the signature of build to:
fun build() : Either<List<ABuilderException>, A>
The reason for doing that is because Either is right biased - functions like map, flatMap etc operate on the Right value and are no-op in case the value is Left.
For combining Either values you can use zip:
val e1 = 2.right()
val e2 = 3.right()
// By default it gives you a `Pair` of the two
val c1 = e1.zip(e2) // Either.Right((2, 3))
// Or you can pass a custom combine function
val c2 = e1.zip(e2) { two, three -> two + three } // Either.Right(5)
However there is an issue here, in case of an error (one of them is Left) it will fail fast and give you only the first one.
To accumulate the errors we can use Validated:
val x = none<Int>()
val y = none<String>()
val z = none<String>()
// Validated<String, Int>
val xa = Validated.fromOption(x) { "X is missing" }
// Validated<String, String>
val ya = Validated.fromOption(y) { "Y is missing" }
// Validated<String, String>
val za = Validated.fromOption(z) { "Z is missing" }
xa.toValidatedNel().zip(
ya.toValidatedNel(),
za.toValidatedNel()
) { x, y, z -> TODO() }
Validated, like Either has a zip function for combining values. The difference is that Validated will accumulate the errors. In the lambda you have access to the valid values (Int, String, String) and you can create your valid object.
toValidatedNel() here converts from Validated<String, String> to Validated<Nel<String>, String> where Nel is a list that can NOT be empty. Accumulating errors as a List is common so it's built in.
For more you can check the Error Handling tutorial in the docs.

private set(value) requires initilization before init{ }

I am trying to add a private set(value) to a var, but this requires to init the field before init { } has run. Why?
fun main(args: Array<String>) {
println("Hello fellow Stackoverflowers!")
val data = Data(listOf<Point>(Point(1, 1.0, 1.0), Point(2, 2.0, 2.0), Point(3, 3.0, 3.0)))
val testInit = TestInit(data)
testInit.magicMethod()
println("Relevant Point Id: ${testInit.relevantPoint.id}")
}
class TestInit(val someData: Data) {
var relevantPoint: Point // = Point(0,0.0,0.0) // or do this, but why? It is set in init{}, lateinit also not allowed
// private set // this works
private set(value) { // Why can't I do this? -> "Property must be initialized - Error"
if (value.id < 100)
field = value
else
field = someData.points.first()
}
init {
if (someData.points.size < 3) // doing validation before setting the point
throw IllegalArgumentException("Need at least three points!")
relevantPoint = someData.points.first() // here the point gets initialized
}
fun magicMethod() {
// do other calculations
relevantPoint = someData.points[someData.points.size / 2] // just assign some point
}
}
data class Data(var points: List<Point> = mutableListOf())
data class Point(val id: Int, val x: Double, val y: Double)
When you call relevantPoint = someData.points.first(), the field will only get initialised if it's id is less than 100 (due to your setter logic). So there is a chance that you won't have initialised the field.
You can either use a backing field if you need to keep the initialisation in init, or simply initialise the value inline, which won't go through the setter:
var relevantPoint = someData.points.first()
private set(value) {
...
}
Edit
It's interesting that this problem occurs even after you adding an else branch to your setter and I am not sure why that is. However for a solution (if you can't initialise it as above) you can easily use a backing field:
private var _relevantPoint: Point
var relevantPoint: Point
private set(value) {
if (value.id < 100) _relevantPoint = value
}
get() = _relevantPoint
init {
_relevantPoint = someData.points.first()
}
In Kotlin, variables must be either initialized on declaration or (only in case of var) get marked with lateinit.
According to the Documentation:
The modifier (lateinit) can be used on var properties declared inside the body of
a class (not in the primary constructor, and only when the property
does not have a custom getter or setter) as well as for top-level
properties and local variables.
So You can't do The latter
Your best bet is what Henry suggested:
var relevantPoint = someData.points.first()
private set(value) {
if (value.id < 100)
field = value
}
The other option is that you don't use custom setter and check your criteria elsewhere.
I think the best option is what was presented earlier.
But I thought of an alternative, this way.
data class Data(var points: List<Point> = mutableListOf())
data class Point(var id: Int, val x: Double, val y: Double)
class RelevantPoint(_point: Point) {
var point: Point = _point
set(value) {
if (value.id < 100)
field = value
}
}
fun main(args: Array<String>) {
println("Hello fellow Stackoverflowers!")
val data = Data(listOf<Point>(Point(1, 1.0, 1.0), Point(2, 2.0, 2.0), Point(3, 3.0, 3.0)))
val testInit = TestInit(data)
testInit.magicMethod()
println("Relevant Point Id: ${testInit.relevantPoint.point.id}")
}
class TestInit(val someData: Data) {
var relevantPoint: RelevantPoint
init {
if (someData.points.size < 3) // doing validation before setting the point
throw IllegalArgumentException("Need at least three points!")
relevantPoint = RelevantPoint(someData.points.first())
}
fun magicMethod() {
// do other calculations
relevantPoint.point = someData.points[someData.points.size / 2] // just assign some point
}
}

Kotlin Data class copy extension

I am trying to find a solution for a nice kotlin data class solution. I have already this:
data class Object(
var classMember: Boolean,
var otherClassMember: Boolean,
var example: Int = 0) {
fun set(block: Object.() -> kotlin.Unit): Object {
val copiedObject = this.copy()
copiedObject.apply {
block()
}
return copiedObject
}
fun touch(block: Object.() -> kotlin.Unit): Object {
return this.set {
classMember = true
otherClassMember = false
block() }
}
}
val test = Object(true,true,1)
val changedTest = test.touch { example = 2 }
the result of this method is that the changedTest object has classMember = true, otherClassMember = false and example = 2
The problem with this solution is, the class properties are not immutable with var declaration. Does somebody have an idea how to optimize my methods to change var to val?
val says that a variable can't change it's value after initialization at the definition point. Kotlin's generated copy method does not modify an existing copy after construction: this method actually uses retrieved values from an object, replaces these values with ones that provided in copy method (if any), and after that just constructs a new object using these values.
So, it is not possible to perform such an optimization if you are going to change object's state after construction.
If I understood what you want correctly, you can do
data class Object(
val classMember: Boolean,
val otherClassMember: Boolean,
val example: Int = 0) {
fun touch(example: Int = this.example): Object {
return copy(
classMember = true,
otherClassMember = false,
example = example)
}
}
val test = Object(true,true,1)
val changedTest = test.touch(example = 2)
Though you need to repeat parameters other than classMember and otherClassMember but without reflection you can't do better.

Can I monitor a variable in Kotlin?

I hope to monitor a variable, I will do sometings when the variable changed, maybe just like Code A.
How can I write these code in Kotlin? Thanks!
Code A
var myList: List<Int>
registerMonitorVar(myList)
fun onVariableChange(){
if (myList.size>=1){
btnDelete.enabled=true
}
}
To ice1000
Thanks! but the following code doesn't work! I don't know how to init allList when I need set property.
private lateinit var allList: MutableList<MSetting> set(value) {
field = value
onVariableChange()
}
private var allList=mutableListOf<MSetting>() set(value) {
field = value
onVariableChange()
}
fun onVariableChange(){
if (allList.size>=1){
}
}
To humazed:
Thanks! why isn't the following code correct?
private var allList: MutableList<MSetting> by Delegates.vetoable(mutableListOf<MSetting>())
{ property, oldValue, newValue ->
{
btnRestore.isEnabled=(newValue.size >= 1)
btnBackup.isEnabled=(newValue.size >= 1)
}
}
To humazed and ice1000
Thanks! The system can't monitor the change of the var allList when I use Code 2
private var allList: MutableList<MSetting> by Delegates.observable(mutableListOf<MSetting>())
{ property, oldValue, newValue ->
btnRestore.isEnabled = newValue.size >= 1
}
Code 1
allList=SettingHandler().getListAllSetting().toMutableList() // Observable Work
Code 2
allList.clear()
allList.addAll(SettingHandler().getListAllSetting().toMutableList()) //Observable Doesn't Work
Kotlin observable and vetoable is perfect for this use case.
vetoable is doing just what you want. from the doc:
vetoable:
Returns a property delegate for a read/write property that
calls a specified callback function when changed, allowing the
callback to veto the modification.
for your example, you can use:
var myList: List<Int> by Delegates.vetoable(listOf()) { property, oldValue, newValue ->
if (newValue.size >= 1)
true // apply the change to myList
else
false // ignore the change. ie. myList stay the same.
}
or simply:
var myList: List<Int> by Delegates.vetoable(listOf()) { property, oldValue, newValue ->
newValue.isNotEmpty()
}
After your edit. I see in your next example observable is more suitable as you seem to want the list to be changed regardless of any condition.
var allList: MutableList<String> by Delegates.observable(mutableListOf<String>()) { property, oldValue, newValue ->
btnRestore.isEnabled = newValue.size >= 1
btnBackup.isEnabled = newValue.size >= 1
}
your code didn't work because you added unnecessary {} and used vetoable without returning neither true nor false.
For the edited answer. it deserves its own question but I'm going to answer it here anyway.
you could use list and when you want to change the list replace it with the new list. this has performance implications since you creating a new list every time you need to add or remove an item.
or you could extend the list class or use extension functions to react to add and delete operations. ex:-
fun main(args: Array<String>) {
val myList = mutableListOf<Int>()
myList.addAllAndNotify(listOf(1, 2, 3, 4))
myList.addAllAndNotify(listOf(1, 2, 88, 9))
}
fun <E> MutableList<E>.addAllAndNotify(elements: Collection<E>) {
addAll(elements)
doStuff(this)
}
fun <E> doStuff(list: List<E>) {
println("list = ${list}")
}
the output:
list = [1, 2, 3, 4]
list = [1, 2, 3, 4, 1, 2, 88, 9]
finally, you could take a look at this nice lib which has ObservableList if this what you need you better of using it instead of writing it yourself.
You can override the and setter.
var myList: List<Int> // maybe here's a missing initialization
set(value) {
field = value
onVariableChange()
}
fun onVariableChange() {
if (myList.size >= 1) {
btnDelete.enabled = true
}
}
By this, if you do myList = blabla, onVariableChange will be called.
To the edit, why doesn't
private var allList = mutableListOf<String>()
set(value) {
field = value
onVariableChange()
}
fun onVariableChange() {
if (allList.size >= 1) {
}
}
This code work?
To the comment, you may use this:
private var allList = listOf<String>()
set(value) {
field = value
onVariableChange()
}