Using ListenersManager

This is the more low-level way of listening fields: you pass callback which gets added to a list and is called whenever the field changes. You can also remove them when they're not necessary anymore. All of this is handled by the ListenersManager class: each field has one associated instance that is exposed through the listeners property.

Note: the lifecycle method for listening field changes should always be your first choice. You should read this page to understand how listeners work internally, but you should always use the lifecycles when possible.

Weak vs strong

link

When we set a listener we must do it in one of two ways:

  • Strongly: the ListenersManager holds a reference of the callback until it is manually removed. This means that if the callback implicitly holds some references, they'll be kept as well. When using this mode you must make sure to cancel the callback as soon as it's not needed anymore to prevent memory leaks.
  • Weakly: in this mode the ListenersManager hold a WeakReference to the callback. This means that when all other references are lost it internally becomes null and the callback is then removed when the manager notices. While this eases the programmer from the duty of removing the callback, it also gives them the responsibility to store a reference until it's needed: usually this is done by keeping it inside an object with a lifecycle, for example an Android™ Activity.

Usually it's preferable to use the weak mode, while the strong mode should be used only when you know the exact moment you need to remove the listener.

Registering a listener

link

The ListenersManager in fields accepts an instance of FieldListener. It has a single function called onFieldChanged() that passes you the field that changed and its new data. The callback is called on the same thread that triggered it (i.e. the thread that changed the value).

Strong listeners

link

Adding a strong listener is pretty straightforward. See this example:

import com.femastudios.dataflow.listen.* import com.femastudios.dataflow.util.* fun main() { val f = mutableFieldOf("hello") f.listeners.addStrongly { newData -> //Can also have a `source` parameter println(newData) } f.value = "how" f.value = "are" f.value = "you" }
f.listeners().addStrongly((source, newData) -> { System.out.println(newData); });

Since kotlin's support of SAM conversions works only on Java™ interfaces, the two addStrongly functions you see used in kotlin are extension functions.

Weak listeners

link

The process to add a weak listener is a bit more complicated as we must keep the reference to the FieldListener instance somewhere.

import com.femastudios.dataflow.listen.* import com.femastudios.dataflow.util.* fun main() { class ObjectWithLifecycle { //`fieldListener()` requires also have the source as first paramter val listener = simpleFieldListener<String> { println(it) } } val f = mutableFieldOf("hello") val obj = ObjectWithLifecycle() f.listeners.addWeakly(obj.listener) f.value = "how" f.value = "are" f.value = "you" }
class ObjectWithLifecycle { public final FieldListener<Integer> listener = (source, newData) -> { System.out.println(newData); }; } ObjectWithLifecycle obj = new ObjectWithLifecycle(); f.listeners().addWeakly(obj.listener);

Doing this the only strong reference is held by the ObjectWithLifecycle instance, so when it gets garbage collected, the listener dies with him.

A more compact way of doing it is directly implementing the FieldListener interface in the object:

import com.femastudios.dataflow.* import com.femastudios.dataflow.listen.* import com.femastudios.dataflow.util.* fun main() { class ObjectWithLifecycle : FieldListener<String> { override fun onFieldChanged(source: Field<String>, newData: String) { println(newData) } } val f = mutableFieldOf("hello") val obj = ObjectWithLifecycle() f.listeners.addWeakly(obj) f.value = "how" f.value = "are" f.value = "you" }
class ObjectWithLifecycle implements FieldListener<Integer> { public void onFieldChanged(Field<Int> source, Integer newData) { System.out.println(newData); } } ObjectWithLifecycle obj = new ObjectWithLifecycle(); f.listeners().addWeakly(obj);

With this technique we can avoid the creation of one object (the listener instance) by implementing it directly on an already existing object. Obviously we can do this only for one field since we can implement only on time the same interface; this leaves us with two possibilities: either we listen only one field or we register multiple fields and check with the source object which one triggered the callback before performing the appropriate action. If both seem unacceptable, we can instantiate one listener for each field and keep it in the object, like in the first example.

Canceling a listener

link

If we want to manually cancel a listener we can do so by calling the remove() function passing the listener instance as its only parameter, like so:

f.listeners.remove(listener)
f.listeners().remove(listener);

This works for both strongly and weakly set listeners.