What are the use cases for `Delegates.observable` when we have property setters? - kotlin

What are the use cases for Delegates.observable when we can just use property setters?
var foo by Delegates.observable("hell0") { prop, old, new ->
// react to changes in foo
}
var bar = "hello"
set(value) {
field = value
// react to changes in bar
// we can also do validation, set something like `value*2` to field, etc.
}

Property setters require much more code duplication if you want multiple properties to react to modification in the same way:
var foo: Foo = Foo()
set(value) {
println("foo = $value")
field = value
}
var bar: Bar = Bar()
set(value) {
println("bar = $value")
field = value
}
Delegates, in turn, are aimed to allow for reuse of the property accessors logic, like this:
fun <T> printDelegate(init: T) =
Delegates.observable(init) { prop, _, new ->
println("${prop.name} = $new")
}
val foo: Foo by printDelegate(Foo())
val bar: Bar by printDelegate(Bar())

Delegates.observable are commonly used in Android. One such such case is adding text change listener.
Example
interface TextChangedListener {
fun onTextChanged(newText: String)
}
class PrintingTextChangedListener : TextChangedListener {
override fun onTextChanged(newText: String) = println("Text is changed to: $newText")
}
class TextView {
var listener: TextChangedListener? = null
var text: String by Delegates.observable("") { prop, old, new ->
listener?.onTextChanged(new)
}
}
Usage
val textView = TextView()
textView.listener = PrintingTextChangedListener()
textView.text = "Lorem ipsum"
textView.text = "dolor sit amet"
Output
Text is changed to: Lorem ipsum
Text is changed to: dolor sit amet
You can read more patterns here:
https://github.com/dbacinski/Design-Patterns-In-Kotlin

Related

Get annotation value via reflection

I am trying to get all the "Keys" of annotations for use later, I am initializing the value like this:
private val wgKeys = SpecialRequestContext::class.members.associate { m ->
m.name to run {
val headerAnnotation = m.annotations.find { a -> a is Header } as? Header
headerAnnotation?.key
}
}
Unfortunately, the result is a Map with name for keys (correct), but all the values are null.
While debugging I see that m.annotations has no values.
Are annotations not available at this step?
Update: The minimum code to demonstrate this is here, unfortunately Kotlin playground cannot do reflection though:
#Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Header(val key: String)
data class SpecialRequestContext(
#Header("BK-Correlation-Id") val correlationId: String? = null,
#Header("BK-Origin") val origin: String? = null,
#Header("BK-Origin-City") val originCity: String? = null,
)
fun main() {
println(wgKeys.count())
println(wgKeys["origin"])
}
private val wgKeys = SpecialRequestContext::class.members.associate { m ->
m.name to run {
val headerAnnotation = m.annotations.find { a -> a is Header } as? Header
headerAnnotation?.key
}
}
Notice:
#Target(AnnotationTarget.VALUE_PARAMETER)
This means that the annotation is applied to the constructor parameters, not the properties. You won't find them on the properties.
To find them on the properties, you can change it to:
#Target(AnnotationTarget.PROPERTY)
If you need them applied to the parameters for some reason, you can find them like this:
private val wgKeys = SpecialRequestContext::class.primaryConstructor!!.parameters.associate { p ->
p.name to p.annotations.filterIsInstance<Header>().firstOrNull()?.key
}
I'm sure there's a better way of doing this, but basically you need to:
get all the annotation's properties
for each property, you need to reflectively get the value for that annotation instance (that's because 2 different classes could have the same annotation with different values).
A (rough) example:
annotation class Annotation1(val key1: String, val key2: Int)
annotation class Annotation2(val key3: String, val key4: Int)
#Annotation1(key1 = "value1", key2 = 2)
#Annotation2(key3 = "value3", key4 = 4)
class MyClass
fun main() {
val myClassAnnotations = MyClass::class.annotations
val propertiesByAnnotation =
myClassAnnotations.associateWith { (it.annotationClass as KClass<Annotation>).declaredMemberProperties }
val keyValuePerAnnotation = propertiesByAnnotation.map { (annotation, properties) ->
annotation.annotationClass.simpleName!! to properties.map { property ->
property.name to property.get(annotation)
}
}
println(keyValuePerAnnotation)
}
This prints a list of pairs, where the first item is the annotation name and the second item is a list of key-value pairs for each of the annotation's properties. The output for that example is:
[(Annotation1, [(key1, value1), (key2, 2)]), (Annotation2, [(key3, value3), (key4, 4)])]

find value in arraylist in kotlin

Hey I am working in kotlin. I am working on tree data structure. I added the value in list and now I want to find that value and modified their property. But I am getting the error.
VariantNode, StrengthNode, ProductVariant
StrengthNode.kt
class StrengthNode : VariantNode() {
var pricePerUnit: String? = null
var defaultValue = AtomicBoolean(false)
}
ActivityViewModel.kt
class ActivityViewModel : ViewModel() {
var baseNode: VariantNode = VariantNode()
private val defaultValueId = "12643423243324"
init {
createGraph()
}
private fun createGraph() {
val tempHashMap: MutableMap<String, VariantNode> = mutableMapOf()
val sortedList = getSortedList()
sortedList.forEach { productVariant ->
productVariant.strength?.let { strength ->
if (tempHashMap.containsKey("strength_${strength.value}")) {
baseNode.children.contains(VariantNode(strength.value)) // getting error
return#let
}
val tempNode = StrengthNode().apply {
value = strength
pricePerUnit = productVariant.pricePerUnit?.value
if (productVariant.id == defaultValueId) {
defaultValue.compareAndSet(false, true)
}
}
baseNode.children.add(tempNode)
tempHashMap["strength_${strength.value}"] = tempNode
}
productVariant.quantity?.let { quantity ->
if (tempHashMap.containsKey("strength_${productVariant.strength?.value}_quantity_${quantity.value}")) {
return#let
}
val tempNode = QuantityNode().apply {
value = quantity
}
val parent =
tempHashMap["strength_${productVariant.strength?.value}"] ?: baseNode
parent.children.add(tempNode)
tempHashMap["strength_${productVariant.strength?.value}_quantity_${quantity.value}"] =
tempNode
}
productVariant.subscription?.let { subscription ->
val tempNode = SubscriptionNode().apply {
value = subscription
}
val parent =
tempHashMap["strength_${productVariant.strength?.value}_quantity_${productVariant.quantity?.value}"]
?: baseNode
parent.children.add(tempNode)
}
}
baseNode
}
}
I am getting error on this.
I want to find that node value and modified other property.
Your class VariantNode only has a single no-arg constructor, but you're trying to call it with arguments, hence the error
Too many arguments for public constructor VariantNode() defined in com.example.optionsview.VariantNode
Either you have to provide a constructor, that matches your call, e.g.
open class VariantNode(var value: ProductValue?) {
var children: MutableList<VariantNode> = arrayListOf()
}
or you need to adjust your code to use the no-arg constructor instead.
val node = VariantNode()
node.value = strength.value
baseNode.children.contains(node)
Note however, that your call to contains most likely will not work, because you do not provide a custom implementation for equals. This is provided by default, when using a data class.
If you just want to validate whether baseNode.children has any element, where value has the expected value, you can use any instead, e.g.:
baseNode.children.any { it.value == strength.value }

How to make Kotlin use field instead of setter in non-primary constructors too

If I use a primary constructor to initialize a property, its setter won't be called:
open class Foo(open var bar: Any)
open class Baz(bar: Any) : Foo(bar) {
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
...
Baz(1) // works, no setter is called
This works too:
open class Foo(bar: Any) {
open var bar: Any = bar // no exception in Baz(1)
}
But this doesn't:
open class Foo {
open var bar: Any
constructor(bar: Any) {
this.bar = bar // UnsupportedOperationException in Baz(1)
}
}
This can't even be compiled:
open class Foo(bar: Any) {
open var bar: Any // Property must be initialized or be abstract
init {
this.bar = bar
}
}
I'm asking because I need to make bar lateinit to be able doing this:
Baz(1) // works
Foo(2) // this too
Foo().bar = 3 // should work too
But this doesn't work in Kotlin:
// 'lateinit' modifier is not allowed on primary constructor parameters
open class Foo(open lateinit var bar: Any)
And there's no syntax like this:
open class Foo() {
open lateinit var bar: Any
constructor(bar: Any) {
// instead of this.bar
fields.bar = bar // or whatever to bypass the setter
}
}
Any ideas how to solve this? (besides reflection, adding extra properties and delegates)
To allow existence of Foo() object you need to provide some init value for bar property (or make it lateinit). But open lateinit property can't be initialized in constructor/init block, because it leads to leaking 'this' in constructor.
So there are 2 options:
Use lateinit modifier and provide factory function instead of secondary constructor for Foo (also requires modification of Baz class):
open class Foo {
open lateinit var bar: Any
}
fun Foo(bar: Any): Foo = Foo().also { it.bar = bar }
open class Baz(bar: Any) : Foo() {
init {
super.bar = bar
}
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
Use some dummy default value for bar property and add custom getter to emulate lateinit property behavior:
open class Foo(bar: Any = NONE) {
companion object {
private object NONE
}
open var bar = bar
get() = field.takeIf { it != NONE } ?: throw UninitializedPropertyAccessException()
}
However note, that making possible to set property in the base class, but disallowing it in the derived is a poor class design, because it breaks Liskov substitution principle.

How to specify particular setter for property of a class?

I'm following a course in kotlin and they speak about how to make a special setter but I don't know how to make it working here is my code :
class Course (val id:Int, title:String, var duree:Int, var state:Boolean){
var title:String = title
get(){return field}
set(value){field = "my awesome" + value}
}
fun main(args: Array<String>) {
var myCourse:Course = Course (0, "stuff", 50, true)
println(myCourse.title)
}
but it keep output 'stuff' instead of 'my awesome stuff'
var myCourse:Course = Course(0, "stuff", 50, true)
With the above line of code, the Course object is initialized with the constructor. Here the setter is not called, hence it prints stuff and not my awesome stuff. The setter would get called only if you use
myCourse.title = "stuff"
If you want the setter to be called on initialization, you need to set the value of title in an init block as below
class Course(val id: Int, title: String, var duree: Int, var state: Boolean) {
var title: String = title
set(value) {
field = "my awesome $value"
}
init {
this.title = title
}
}
Or, you can drop the custom setter and set the value of title with your custom value in the init block itself
class Course(val id: Int, var title: String, var duree: Int, var state: Boolean) {
init {
this.title = "my awesome ${this.title}"
}
}
The custom setter is only used when you explicitly set the value. The custom setter is not used when you initialize the backing field at the declaration site using = title.
If you want the custom setter to be applied using the initial value, you can add an init block to your class:
init {
this.title = title
}
I was able to get you example working applying a little change in your code:
class Course (val id:Int, __title:String, var duree:Int, var state:Boolean){
var title:String = ""
get() { return field}
set(value){field = "my awesome" + value}
init {
title = __title
}
}
The difference seems to be the explicit assignment title = __title, that forces the usage of the custom setter...

tornadofx listview is creating one additional null listcellfragment than items in the list

I have a ViewModel for a ListView with 3 players in it:
object PlayerListViewModel : ViewModel() {
lateinit var players : ObservableList<Player>
init{
}
fun loadPlayers(){
players = Engine.selectedGame.players.asObservable()
}
}
class PlayerListView : View() {
private val vm = PlayerListViewModel
override val root = VBox()
init {
vm.loadPlayers()
root.replaceChildren {
style {
spacing = 25.px
alignment = Pos.CENTER
padding = box(0.px, 15.px)
}
listview(vm.players){
style{
background = Background.EMPTY
prefWidth = 300.px
}
isFocusTraversable = false
isMouseTransparent = true
cellFragment(PlayerCardFragment::class)
}
}
}
}
For some reason the listview is creating 4 PlayerCardFragments, with the first having a null item property and the last 3 having the correct Player item reference. This is the PlayerCardFragment definition:
class PlayerCardFragment : ListCellFragment<Player>() {
private val logger = KotlinLogging.logger { }
private val vm = PlayerViewModel().bindTo(this)
private lateinit var nameLabel : Label
private lateinit var scoreLabel : Label
override val root = hbox {
addClass(UIAppStyle.playerCard)
nameLabel = label(vm.name) { addClass(UIAppStyle.nameLabel) }
scoreLabel = label(vm.score) { addClass(UIAppStyle.scoreLabel) }
}
init {
logger.debug { "Initializing fragment for ${this.item} and ${vm.name.value}" }
EventBus.channel(EngineEvent.PlayerChanged::class)
.observeOnFx()
.subscribe() {
vm.rollback() //force viewmodel (PlayerViewModel) refresh since model (Player) does not implement observable properties
logger.debug { "${vm.name.value}'s turn is ${vm.myTurn.value}" }
root.toggleClass(UIAppStyle.selected, vm.myTurn)
}
}
When running the application, the PlayerCardFragment initializations print out "Initializing fragment for null and null" four times, but the list appears perfectly correctly with the 3 Player items. Later during execution, wnen there is an Engine.PlayerChanged event received, the Oberver function prints:
"null's turn is false"
"Adam's turn is false"
"Chad's turn is true"
"Kyle's turn is false"
These are the correct players, with the correct turn statuses. The listview appears perfectly well with the styling changes. I'm just not sure where that first null ListCellFragment is coming from.
It seems like you're trying to give ItemViewModel functionality to a view model by having everything around it change. Why not change the PlayerViewModel to have the functionality instead? Easiest way to imagine is to create bindings out of generic properties, then have them all changed and committed with by listening to the itemProperty:
class Player(var foo: String?, var bar: Int?)
class PlayerViewModel() : ItemViewModel<Player>() {
val foo = bind { SimpleStringProperty() }
val bar = bind { SimpleIntegerProperty() }
init {
itemProperty.onChange {
foo.value = it?.foo
bar.value = it?.bar
}
}
override fun onCommit() {
item?.let { player ->
player.foo = foo.value
player.bar = bar.value?.toInt()
}
}
}
Is it pretty? No. Does it keep you from having to implement an event system? Yes.