I feel like the simplest solution to this is with Kotlin Flows.
class A {
private val _event = MutableSharedFlow<Unit>()
val event get() = _event.asSharedFlow()
}
class B {
private var subscription: Job? = null
suspend fun subscribeToEvents(a: A) {
subscription?.cancel()
subscription = launch {
a.event.onEach {
// Event has been fired...
}.collect()
}
}
fun cancelSubscriptions() {
subscription?.cancel()
}
}
class C {
private var subscription: Job? = null
suspend fun subscribeToEvents(a: A) {
subscription?.cancel()
subscription = launch {
a.event.onEach(::handleEvent).collect()
}
}
private suspend fun handleEvent(unit: Unit) {
// Event has been fired...
}
fun cancelSubscriptions() {
subscription?.cancel()
}
}
This is thread safe and can be easily cleaned up at any time by either cancelling the Job, or cancelling the CoroutineScope the job was created in.
You could also simplify the process with an extension function that only works on Flow<Unit>.
suspend inline fun Flow<Unit>.subscribe(noinline handler: suspend (Unit) -> Unit): Job = coroutineScope {
launch { this@subscribe.onEach(handler).collect() }
}
class D {
private var subscription: Job? = null
suspend fun subscribeToEvents(a: A) {
subscription?.cancel()
subscription = a.event.subscribe(::handleEvent)
}
private suspend fun handleEvent(unit: Unit) {
// Event has been fired...
}
fun cancelSubscriptions() {
subscription?.cancel()
}
}
I also have a class I use all the time for dealing with job cancellations when subscribing to flows:
class SafeJob(initialValue: Job? = null) : ReadWriteProperty<Any?, Job?> {
var value: Job? = initialValue; private set
override fun getValue(thisRef: Any?, property: KProperty<*>): Job? = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Job?) = synchronized(this) {
this.value?.apply { if (!isCancelled) cancel() }
this.value = value
}
}
class E {
private var subscription: Job? by SafeJob()
suspend fun subscribeToEvents(a: A) {
subscription = a.event.subscribe(::handleEvent)
}
private suspend fun handleEvent(unit: Unit) {
// Event has been fired...
}
fun cancelSubscriptions() {
subscription = null
}
}
The SafeJob will automatically cancel the previous job when ever the property's value is changed, and it can be simply set to null if you want to just clear it.