Search code examples
scalascalafx

Removing listeners from property in ScalaFX


I'm struggling with removing an event listener from Property in ScalaFX.

Simplified example

import scalafx.Includes._

object ListenerApp {

  val prop = DoubleProperty(0)

  val listener = (source, oldValue, newVal) => {
    println("Listener working, and the value is " + newVal)
  }

  def main(args: Array[String]) = {
    prop.addListener(listener)
    prop.value = 1
    prop.removeListener(listener)
    prop.value = 2
  }

}

The result is not as expected:

Listener working, and the value is 1.0
Listener working, and the value is 2.0

I've seen similar code work in JavaFX however my adaptation might be wrong.

Addinational information

I'm puzzled whether there's an error in my methodology, as similar thing is happening with unbindBidirectional(), or perhaps it is a bug since this feature might not be utilized much and nobody noticed.

Tried using a debugger to access listeners in the delegate but it does not display any fields.

Why do I even need this

I have a view displaying some insideProp: Property, which is a member of an object content: T inside a different outsideProp: ObjectProperty[T].

However I don't want to display a particular content's insideProp or rather whatever is inside the outsideProp. For this I need a removable binding, or an removable event listener as the view should only be modified by the current content of outsideProp.

I would gladly create a new "immutable" view for every content however speaking from experience JavaFX isn't really build for this behaviour, and there is also a problem of memory leaks.

I would much appreciate somebody pointing what I'm doing wrong.


Solution

  • Adding a Listener

    The idiomatic way of adding property listeners in ScalaFX is to use the onChange method:

    val prop = DoubleProperty(0)
    
    prop.onChange { (source, oldValue, newValue) =>
      println(s"Property $source changed value from $oldValue to $newValue")
    }
    

    If you just want the new value you can ignore the first two parameters:

    prop.onChange { (_, _, newValue) =>
      println(s"Property changed value to $newValue")
    }
    

    Removing a Listener

    A subscription handle lets you remove a listener. A subscription is created for every listener added to a property. When you no longer need to the listen, you "cancel" the subscription:

    val prop = DoubleProperty(0)
    
    val subscription = prop.onChange { (_, _, newValue) =>
                 println(s"Property changed value to $newValue")
               }
    
    prop.value = 1
    subscription.cancel()
    
    // Listener will not be notified about this change
    prop.value = 2