Using lifecycles
This way of listening fields has a very similar concept to the weak way of the previous page: we still register weak listeners inside the ListenersManager
, but our reference are automatically kept inside the object's lifecycle. This is possible by implementing the LifecycleOwner
interface. The callback is still called by the thread that triggered it.
This interface has two main functions to implement:
attachToLifecycle()
: accepts aListenerHolder
instance that we must attach in some way to the objectdetachFromLifecycle()
: accepts aListenerHolder
instance that we must remove from the object
ListenerHolder
is a particular class that holds the callback and some associated info; you cannot directly instantiate it.
This interface also has multiple versions of a listen()
function: given its parameters it creates a ListenerHolder
and calls attachToLifecycle()
. After that it returns the created ListenerHolder
.
Whenever you want to listen to a field and attach the callback to a LifecycleOwner
all you have to do is call listen()
on it passing the callback: everything else will automatically be taken care of.
This is the basic implementation of LifecycleOwner
that already provides all the needed functions to work.
We can now see a brief example on an object extending BaseLifecycleOwner
:
import com.femastudios.dataflow.listen.*
import com.femastudios.dataflow.util.*
fun main() {
class ObjectWithLifecycle : BaseLifecycleOwner() {
}
val f = mutableFieldOf(10)
val obj = ObjectWithLifecycle()
obj.listen(f) {
println(it)
}
f.value = 42
}
class ObjectWithLifecycle extends BaseLifecycleOwner {
}
MutableField<Integer> f = MutableField.of(10)
ObjectWithLifecycle obj = new ObjectWithLifecycle();
obj.listen(f, newData -> {
System.out.println(newData);
});
f.setValue(42);
A thing we should keep in mind is that calling listen()
will also execute the callback the first time. If you do not want this behavior you can set the parameter callImmediately
to false
.
Please note that the callbacks listeners are registered and listening to changes until the garbage collector destroys the BaseLifecycleOwner
. If you don't like this behavior, you can manually call stop()
and destroy()
methods.
If you need to register a listener for the whole duration of the program you can use a special lazily-instantiated LifecycleOwner
instance, called global lifecycle:
import com.femastudios.dataflow.listen.*
import com.femastudios.dataflow.util.*
fun main() {
val f = mutableFieldOf("hello")
globalLifecycle.listen(f) {
println(it)
}
println("world")
}
MutableField<String> f = MutableField.of("hello");
LifecycleOwner.globalLifecycle.listen(f, (v) -> {
System.out.println(v);
});
f.setValue("world");
You could technically remove listeners from the global lifecycle, but doing so is discouraged since the listener's actual lifecycle wouldn't actually be global.
Last but not least, we can call the lifecycle()
function providing a lambda that is immediately evaluated and that accepts as its receiver the newly created LifecycleOwner
; as soon as it terminates the LifecycleOwner
and all its listeners are stopped.
This method is meant for short-lived listeners whose life-span isn't necessarily attached to another already existing object.
Example:
import com.femastudios.dataflow.listen.*
import com.femastudios.dataflow.util.*
fun main() {
val f = mutableFieldOf(1)
f.value = 2
f.value = 3
lifecycle {
listen(f) {
print("$it ")
}
f.value = 4
f.value = 5
}
f.value = 6
f.value = 7
//Outputs 3 4 5
}
MutableField<Integer> f = MutableField.of(1);
f.setValue(2);
f.setValue(3);
LifecycleOwner.lifecycle(lc -> {
lc.listen(f, v -> {
System.out.print(v + " ");
});
f.setValue(4);
f.setValue(5);
});
f.setValue(6);
f.setValue(7);
//Outputs 3 4 5
As soon as the lambda exits, all listeners are canceled.
You can pass an Executor
to this function and obtain a new LifecycleOwner
whose listeners will be called on the given executor.