Can I monitor a variable in Kotlin? - 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()
}

Related

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
}
}

List setter is not being called when appending a new value

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

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.

Kotlin general setter function

I am new to kotlin. I wonder if this is possible
I wish to create a function that will change the value of the properties of the object and return the object itself. The main benefit is that I can chain this setter.
class Person {
var name:String? = null
var age:Int? = null
fun setter(propName:String, value:Any): Person{
return this.apply {
try {
// the line below caused error
this[propName] = value
} catch(e:Exception){
println(e.printStackTrace())
}
}
}
}
//usage
var person = Person(null,null)
person
.setter(name, "Baby")
.setter(age, 20)
But I get error "unknown references"
This question is marked as duplicate, however the possible duplicate question specifically want to change the property of "name", but I wish to change anyProperty that is pass from the function to object. Can't seem to connect the dot between two questions. #Moira Kindly provide answer that explain it. thankyou
Why not just simplify your answer to
fun setter(propName: String, value: Any): Person {
val property = this::class.memberProperties.find { it.name == propName }
when (property) {
is KMutableProperty<*> ->
property.setter.call(this, value)
null ->
// no such property
else ->
// immutable property
}
}
Java reflection isn't needed, its only effect is to stop non-trivial properties from being supported.
Also, if you call it operator fun set instead of fun setter, the
this[propName] = value
syntax can be used to call it.
After googling around, I think I can provide an answer, but relying on java instead of kotlin purely. It will be great if someone can provide a better answer in kotlin.
class Person(
var name: String,
val age: Int
){
fun setter(propName: String, value: Any): Person{
var isFieldExistAndNotFinal = false
try{
val field = this.javaClass.getDeclaredField(propName)
val isFieldFinal = (field.getModifiers() and java.lang.reflect.Modifier.FINAL == java.lang.reflect.Modifier.FINAL)
if(!isFieldFinal) {
// not final
isFieldExistAndNotFinal = true
}
// final variable cannot be changed
else throw ( Exception("field '$propName' is constant, in ${this.toString()}"))
} catch (e: Exception) {
// object does not have property
println("$e in ${this.toString()}")
}
if(isFieldExistAndNotFinal){
val property = this::class.memberProperties.find { it.name == propName }
if (property is KMutableProperty<*>) {
property.setter.call(this, value)
}
}
return this;
}
}
usage like this
person
.setter(propName = "age", value = 30.00)
.setter(propName = "asdf", value = "asdf")
.setter(propName = "name", value = "A Vidy")
You have error because when you do this[propName] = value you are trying to use this as a list, but it is not a list, it is a Person and it doesn't overload the [] operator.
What you can do is to add a check for the property that is setted:
class Person {
privavar name:String? = null
var age:Int? = null
fun setter(propName:String, value:Any): Person{
return this.apply {
if (propName == "name" && value is String?) {
it.name = value as String?
} else if (propName == "age" && value is Int?) {
it.age = value as Int?
} else {
// handle unknown property or value has incorrect type
}
}
}
}
Another more dynamic solution without reflection:
class Person {
private var fields: Map<String, Any?> = HashMap()
fun setter(propName:String, value:Any): Person{
return this.apply {
it.fields[propName] = value;
}
}
fun getName() = fields["name"]
}
If you want to get rid of the getters as well then you need to use reflection.

List or array is always empty in Kotlin

I'm trying to write simple Stack on Kotlin, but all data containers are always throws ArrayIndexOutOfBoundsException. I can't find out what can cause this problem:
class StackX(size: Int) {
private var maxSize: Int = size
private var stackArray: Array<Long> = arrayOf(maxSize.toLong())
private var top = -1
fun push(data: Long) {
stackArray[++top] = data
}
fun pop() : Long {
return stackArray[top--]
}
fun peek() : Long {
return stackArray[top]
}
fun isEmpty() : Boolean {
return (top == -1)
}
fun isFull() : Boolean {
return (top == maxSize -1)
}
}
Could you please explain me the right patter of arrays declaration in this case? I want just this:
int a[] = new int[10];
P.S. It's test code, I even doesn't call pop. It throws on push. I'm just trying to understand what's wrong with declaration.
This line is the problem
private var stackArray: ArrayList<Long> = arrayListOf().
It creates an array which length is 0.
Perhaps, you want something like this
val stackArray: LongArray = LongArray(size).
The problem is in your push() method.
private var stackArray: ArrayList<Long> = arrayListOf() // size = 0
private var top = -1
fun push(data: Long) {
stackArray[++top] = data // top = 0
}
What you're trying to do is to get the 0th element of an empty list.
Possible fix:
fun push(data: Long) {
stackArray.add(data)
++top
}
Updated.
Creating an int array of the specified size:
int[] a = new int[10]; // Java
val a = IntArray(10) // Kotlin
All elements are initialized to zero.
Creating a DataItem array:
DataItem[] b = new DataItem[10]; // Java
val b = arrayOfNulls<DataItem>(10) // Kotlin
All elements are initialized to null.