Miraslau Rachkouski
Miraslau Rachkouski

Reputation: 103

Handle cancelation inside Kotlin Coroutines producer

Is it possible to handle producer cancelation inside producer builder itself? It could be useful to unsubscribe from callback:

private fun changes(key: String) = produce<Unit>(UI, CONFLATED) {
        val listener = OnSharedPreferenceChangeListener { _, changedKey ->
             if (key == changedKey) offer(Unit)
        }
        prefs.registerOnSharedPreferenceChangeListener(listener)
        ???.onCancel { 
                 prefs.unregisterOnSharedPreferenceChangeListener(listener)
        }
}

Or may be exists another way to implement this case?

Upvotes: 4

Views: 1309

Answers (2)

Louis CAD
Louis CAD

Reputation: 11529

An upcoming version of the kotlinx.coroutines library should expose a Channel.invokeOnClose { ... } method to satisfy such use cases.

However, there are solutions to have this behavior in the meantime. One solution is to subclass the channel you're looking for, as Roman Elizarov suggested.

Another solution is to use produce the following way:

fun SharedPreferences.changes(key: String) = produce {
    val changesChannel = ConflatedChannel<Unit>()
    val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->
        if (key == changedKey) changesChannel.offer(Unit)
    }
    registerOnSharedPreferenceChangeListener(listener)
    try {
        for (change in changesChannel) {
            send(change)
        }
    } finally {
        unregisterOnSharedPreferenceChangeListener(listener)
    }
}

Upvotes: 2

Roman  Elizarov
Roman Elizarov

Reputation: 28648

First of all, you should not use produce builder to adapt API with listeners in this way, because on the exist from the produce builder body the channel is immediately closed and would cease to serve its function. Instead, you should just create a Channel() and create the corresponding connections.

Unfortunately, channels do not currently provide an out-of-the-box way to install cancellation listeners (see issue #341). The only way to get immediately notified on the channel close is to extend the corresponding channel class, which leads to the following code:

private fun changes(key: String): ReceiveChannel<Unit> = object : ConflatedChannel<Unit>() {
    val listener = OnSharedPreferenceChangeListener { _, changedKey ->
        if (key == changedKey) offer(Unit)
    }

    init {
        prefs.registerOnSharedPreferenceChangeListener(listener)
    }

    override fun afterClose(cause: Throwable?) {
        prefs.unregisterOnSharedPreferenceChangeListener(listener)
    }
}

Upvotes: 3

Related Questions