fix(notif): throttle relay-count updates to dodge Android's rate limit

A device log showed the persistent notification stuck on a stale count
(e.g. "44 inbox relays") while the pool had actually settled lower
(flowConnected=8). Cause: the count collector posted the notification on
every connectedRelaysFlow delta — ~90 updates during feed load, then ~22
in ~250ms during background teardown. Android rate-limits notification
updates (~10/s) and silently drops the excess, so the last value the
framework rendered (a mid-cascade 44) stuck instead of the final 8.

Sample connectedRelaysFlow at 1s before updating the notification. That
caps updates to ~1/s — comfortably under the limit — and the settled
count always lands. Also drops the now-confirmed notif-collector/
notif-popup debug logging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
This commit is contained in:
Claude
2026-06-18 17:53:39 +00:00
parent bbd84664d9
commit dbfd2c4c43
@@ -43,10 +43,12 @@ import com.vitorpamplona.amethyst.ui.pluralStringRes
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
/**
@@ -80,6 +82,10 @@ class NotificationRelayService : Service() {
private const val ACTION_START = "com.vitorpamplona.amethyst.START_NOTIFICATION_SERVICE"
// Throttle interval for refreshing the persistent notification's relay count.
// Keeps notification updates well under Android's rate limit (~10/s).
private const val NOTIFICATION_REFRESH_MS = 1000L
const val ACTION_AUTO_RESTART = "com.vitorpamplona.amethyst.AUTO_RESTART_NOTIFICATION_SERVICE"
fun start(context: Context) {
@@ -243,6 +249,7 @@ class NotificationRelayService : Service() {
* drafts, and relay list changes. Since the service keeps the client connected,
* those subscriptions remain active on the relays.
*/
@OptIn(FlowPreview::class)
private fun startRelayConnection() {
relayServiceCollectorJob?.cancel()
relayServiceCollectorJob =
@@ -254,15 +261,22 @@ class NotificationRelayService : Service() {
}
launch {
Amethyst.instance.client.connectedRelaysFlow().collectLatest { relays ->
val count = relays.size
Log.d("BgRelayTrace") { "notif-collector received connectedRelays=$count (lastPosted=$connectedRelayCount)" }
if (count != connectedRelayCount) {
connectedRelayCount = count
updateNotification(count)
Log.d("BgRelayTrace") { "notif-popup posted count=$count" }
// sample() caps how often we touch the notification. During feed
// load/teardown connectedRelaysFlow churns dozens of times per second;
// posting on every delta blows past Android's notification rate limit
// (~10/s), which silently drops updates and leaves the visible count
// stuck on a stale intermediate value. One refresh per second stays
// well under the limit and always lands the settled count.
Amethyst.instance.client
.connectedRelaysFlow()
.sample(NOTIFICATION_REFRESH_MS)
.collectLatest { relays ->
val count = relays.size
if (count != connectedRelayCount) {
connectedRelayCount = count
updateNotification(count)
}
}
}
}
}
}