Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect values changed in Data class Kotlin?

Tags:

android

kotlin

I want to detect any values changed of a property of my class so then I could do another operation after that. In other word, if one of a specific data of a property is changed, then a specific event will fire by then. Actually, if it is a normal class in another programming languages such as Java, then I think I could use setter to do this job after data have modified or use delegate in C#. But since Kotlin is very new, I could not find any solution out there at all. I have tried to overload property but was not successful by any chance. I wanted to use interface to to this too, but again since it is data class, I got no idea how should it be done.

Below is the sample class. In such situation, how to detect when Age or name is changed?

data class Person(var Name: String, var Age: Int) 

So please if anyone have any ideas about this, please help.

Note: in my situation, data class must be used.

like image 739
jujuzi Avatar asked Jun 10 '17 05:06

jujuzi


2 Answers

Data classes are really not made for this. Since their properties have to be declared in the primary constructor, you can't really add custom behaviour to them.

That said, if you must, you can achieve this by duplicating the properties, and then either using custom setters, or Delegates.observable.

Here's a way to do it with custom setters, here you're gonna be accessing the publicly visible name and age properties, which keep the ones declared in the constructor up to date as well:

data class Person(private var _name: String, private var _age: Int) {

    var name = _name
        set(value) {
            println("Name changed from $name to $value")
            field = value // sets the backing field for `name`
            _name = value // sets the `_name` property declared in the primary ctor
        }

    var age = _age
        set(value) {
            println("Age changed from $age to $value")
            field = value
            _age = value
        }

}

Same idea, with Delegates.observable, which does some of the work for you, here your only overhead is setting the properties declared in the constructor to the new values:

data class Person(private var _name: String, private var _age: Int) {

    var name: String by Delegates.observable(_name) { prop, old, new ->
        println("Name changed from $old to $new")
        _name = new
    }

    var age: Int by Delegates.observable(_age) { prop, old, new ->
        println("Age changed from $old to $new")
        _age = new
    }

}

Usage of either of these looks like this (the toString will look a bit ugly with the underscores):

val sally = Person("Sally", 50)
println(sally)      // Person(_name=Sally, _age=50)
sally.age = 51      // Age changed from 50 to 51
println(sally)      // Person(_name=Sally, _age=51)
println(sally.name) // Sally
println(sally.age)  // 51

Edit to answer the question below:

If you didn't need your class to be a data class, the following would probably be the simplest solution:

class Person(name: String, age: Int) {

    var name: String by Delegates.observable(name) { _, old, new ->
        println("Name changed from $old to $new")
    }

    var age: Int by Delegates.observable(age) { _, old, new ->
        println("Age changed from $old to $new")
    }

}

This way you still have a constructor that takes the name and age as parameters, but they get assigned to the properties that are inside the body of the class. This is not possible with a data class, because every constructor parameter of a data class has to be a property as well (marked val or var). For more, you can see the documentation about constructors, properties, and data classes.

like image 139
zsmb13 Avatar answered Sep 21 '22 20:09

zsmb13


Delegated Properties

There are certain common kinds of properties, that, though we can implement them manually every time we need them, would be very nice to implement once and for all, and put into a library. Examples include

lazy properties: the value gets computed only upon first access, observable properties: listeners get notified about changes to this property, storing properties in a map, instead of a separate field for each property. To cover these (and other) cases, Kotlin supports delegated properties:

class Example {
    var p: String by Delegate()
}

The syntax is: val/var : by . The expression after by is the delegate, because get() (and set()) corresponding to the property will be delegated to its getValue() and setValue() methods. Property delegates don’t have to implement any interface, but they have to provide a getValue() function (and setValue() — for var's). For example:

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}
like image 36
Glory Avatar answered Sep 21 '22 20:09

Glory