Skip to content

fix(android): avoid resource stalls in connection status#6398

Merged
jamesarich merged 5 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/android-compose-resource-anr
Jul 25, 2026
Merged

fix(android): avoid resource stalls in connection status#6398
jamesarich merged 5 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/android-compose-resource-anr

Conversation

@jeremiah-k

@jeremiah-k jeremiah-k commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Overview

This avoids Compose resource-loading stalls in two connection-status paths that can overlap during normal Android lifecycle transitions.

The observed ANR had the Connections screen waiting in Compose resource loading while foreground-service telemetry formatting was resolving Compose resources at the same time. The UI lookup lived inside frequently re-entered animated content, while the service notification path used blocking non-composable resource helpers during foreground-service startup and telemetry updates.

The Connections UI now resolves stable connected-device display text outside the animated subtree, avoiding repeated entry into the blocking CMP resource state during animation frames. Service notifications return a cached or immediate application-label fallback for startForeground, then render localized status text asynchronously on the existing ServiceScope.

Review Guide

The commits are intended to be reviewed in order:

  1. Leaf labels and UI composition — allows signal and battery components to receive caller-resolved text and hoists stable connected-device display values out of animated content.
  2. Service notification foundation — replaces blocking resource helpers with suspend formatting, an immediate fallback, and deterministic notification coverage.
  3. Structured test and failure handling — contains fallback failures and keeps render work observable through structured test scopes.
  4. Ordered status rendering — replaces generation/job coordination with one replaying SharedFlow collector, closes the last-writer window, and documents the temporary CMP workaround.

Key Changes

Connection UI

  • Allow Rssi and MaterialBatteryInfo to accept caller-provided labels while retaining localized defaults for other callers.
  • Introduce an immutable CurrentlyConnectedText value for localized connected-device text.
  • Resolve RSSI, unknown-battery, disconnect, and firmware-version text outside AnimatedContent.
  • Keep these stable resource lookups out of connection-state animation transitions and deduplicate them across recompositions.
  • Document the caller-provided label surface as a temporary CMP-6615 workaround for Compose Multiplatform 1.11.1, with an explicit removal condition.

Service Notifications

  • Use the Android application label or last rendered notification as an immediate startForeground fallback.
  • Replace blocking resource helpers with suspend-based formatting on the established ServiceScope.
  • Feed captured connection and telemetry snapshots through one replaying, drop-oldest SharedFlow collector.
  • Use collectLatest to cancel obsolete resource work and serialize the complete render-and-notify action, preventing an older Binder post from overwriting newer notification text.
  • Preserve the last telemetry message when newer state has no replacement metrics.
  • Update cached state under serviceNotificationLock, then release the lock before posting through NotificationManager.
  • Preserve cancellation and provide a safe fallback when localized rendering or notification posting fails.
  • Clarify that MeshService.startForeground uses the cached or immediate fallback while localized rendering completes asynchronously.

Testing

Added or updated coverage for:

  • caller-provided RSSI and unknown-battery labels;
  • immediate foreground-notification fallback behavior;
  • deferred localized service-state rendering;
  • latest-state selection when updates arrive before rendering completes;
  • reposting a structurally identical state after notifications are cleared;
  • valid oneof telemetry fixtures;
  • notification-channel cleanup and active-notification behavior;
  • conversation notification integration and structured render-scope cleanup.

Runtime validation repeatedly backgrounded, foregrounded, stopped, and restarted MainActivity while service telemetry continued to update. The original input-timeout and Compose resource-loading stall did not recur, and the capture contains no notification-render exception, ANR, or process restart.

Migration Notes

  • No user data migration is required.
  • Notification channels and IDs are unchanged.
  • Connection-state and telemetry semantics are unchanged.
  • The caller-provided label parameters are temporary and can be removed after adopting a Compose Multiplatform release containing the CMP-6615 Android resource-loading fix.

Summary by CodeRabbit

  • Improvements

    • Foreground service notifications now update asynchronously while promptly displaying a fallback status.
    • Notification updates prioritize the latest connection state and preserve useful status details if rendering is delayed.
    • Connection details now support localized labels, optional firmware version display, and clearer battery and signal descriptions.
    • Signal strength and battery components support customized labels for improved contextual display.
  • Tests

    • Added coverage for notification timing, state updates, reposting, and customizable connection labels.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds asynchronous service-notification rendering with coroutine-scoped tests and introduces caller-provided labels for connected-device UI components.

Changes

Service notification rendering

Layer / File(s) Summary
Asynchronous notification pipeline
core/service/src/androidMain/kotlin/.../MeshNotificationManagerImpl.kt, core/service/src/androidMain/kotlin/.../MeshService.kt
Service notifications use captured snapshots, latest-only rendering, immediate fallbacks, and suspend-safe resource loading.
Coroutine lifecycle and rendering tests
core/testing/src/commonMain/kotlin/.../TestScopes.kt, core/service/src/androidHostTest/kotlin/.../MeshNotificationManagerImpl*Test.kt
Tests use injected render scopes and verify fallback, deferred, cancellation, reposting, and conversation notification behavior.

Connected-device UI labels

Layer / File(s) Summary
Caller-provided component labels
core/ui/src/commonMain/kotlin/.../LoraSignalIndicator.kt, core/ui/src/commonMain/kotlin/.../MaterialBatteryInfo.kt, core/ui/src/commonTest/kotlin/.../LoraSignalIndicatorUiTest.kt
RSSI and battery components accept supplied labels, with Compose coverage for custom values.
Precomputed connected-device text
feature/connections/src/commonMain/kotlin/.../ConnectionsScreen.kt, feature/connections/src/commonMain/kotlin/.../components/CurrentlyConnectedInfo.kt
ConnectionsScreen resolves labels and firmware text outside animated content and passes them through CurrentlyConnectedText.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ServiceState
  participant MeshNotificationManagerImpl
  participant ServiceScope
  participant AndroidNotificationManager
  ServiceState->>MeshNotificationManagerImpl: updateServiceStateNotification()
  MeshNotificationManagerImpl->>ServiceScope: emit service snapshot
  ServiceScope->>MeshNotificationManagerImpl: render latest snapshot
  MeshNotificationManagerImpl->>AndroidNotificationManager: post rendered notification
Loading

Possibly related PRs

Suggested labels: ui

Suggested reviewers: jamesarich

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: reducing Android connection-status resource stalls.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bugfix PR tag label Jul 24, 2026
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 01:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt (1)

75-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a deterministic test dispatcher here.

updateServiceStateNotification() runs async render work through getStringSuspend(...), but these tests assert immediately after calling it under Dispatchers.Unconfined; if resource resolution resumes off the current thread, the assertions become order-dependent. Move this test to StandardTestDispatcher + advanceUntilIdle() or otherwise advance the renderer before asserting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt`
at line 75, Update the notificationScope in
MeshNotificationManagerImplConversationTest to use a deterministic
StandardTestDispatcher-backed test scope instead of Dispatchers.Unconfined, and
advance the dispatcher with advanceUntilIdle() after
updateServiceStateNotification() before assertions so asynchronous
getStringSuspend rendering completes deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt`:
- Around line 139-145: Update the CurrentlyConnectedText preview configuration
to replace the hardcoded “Unknown”, “RSSI”, and “Disconnect” values with
localized core:resources entries resolved through stringResource(Res.string.*),
while leaving the remaining preview setup unchanged.

---

Nitpick comments:
In
`@core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt`:
- Line 75: Update the notificationScope in
MeshNotificationManagerImplConversationTest to use a deterministic
StandardTestDispatcher-backed test scope instead of Dispatchers.Unconfined, and
advance the dispatcher with advanceUntilIdle() after
updateServiceStateNotification() before assertions so asynchronous
getStringSuspend rendering completes deterministically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a2cf68b-ca27-4f0a-94c3-84016e17945a

📥 Commits

Reviewing files that changed from the base of the PR and between a82d0e2 and a3544a4.

📒 Files selected for processing (8)
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MaterialBatteryInfo.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicatorUiTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 01:39
@jeremiah-k
jeremiah-k force-pushed the bugfix/android-compose-resource-anr branch from a3544a4 to ae22f8b Compare July 24, 2026 02:41
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 11:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt`:
- Around line 100-107: Move the duplicated runWithRenderScope helper from
MeshNotificationManagerImplConversationTest and MeshNotificationManagerImplTest
into the core:testing module, preserving its TestScope, CoroutineScope,
dispatcher, SupervisorJob, and cancellation behavior. Expose the shared utility
and import it from both tests, removing their local helper definitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 85355b3f-69d7-4e4e-9997-405967f2a33b

📥 Commits

Reviewing files that changed from the base of the PR and between a3544a4 and ae22f8b.

📒 Files selected for processing (8)
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MaterialBatteryInfo.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicatorUiTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicatorUiTest.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MaterialBatteryInfo.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 11:11
@jeremiah-k
jeremiah-k force-pushed the bugfix/android-compose-resource-anr branch from ae22f8b to 3e939c2 Compare July 24, 2026 13:19
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 15:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/TestScopes.kt`:
- Around line 27-32: Update runWithRenderScope so the renderScope job is
parented to the runTest coroutine context instead of creating an independent
SupervisorJob, or otherwise explicitly await its rendered work before the
finally block cancels it. Ensure render-scope coroutine failures remain
observable and runTest waits for all launched work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 85be81a0-6d01-4ac4-bcc9-90954d664bbb

📥 Commits

Reviewing files that changed from the base of the PR and between ae22f8b and 3e939c2.

📒 Files selected for processing (4)
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/TestScopes.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 15:52
@jeremiah-k
jeremiah-k force-pushed the bugfix/android-compose-resource-anr branch from 3e939c2 to a4eee02 Compare July 24, 2026 18:22
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 18:47
@jamesarich

Copy link
Copy Markdown
Collaborator

The notification half of this is a genuine fix, and I want to be clear about that up front: MeshService.onStartCommand -> startForeground was running a chain of blocking CMP getString calls on the binder thread inside Android's ~5s window. Moving that to getStringSuspend on the ServiceScope is the right call, and @Named("ServiceScope") is an established qualifier here (CoreServiceModule.kt:31), so the DI addition fits the existing shape.

Three things, none of which I consider merge blockers.

1. The concurrency machinery is heavier than the problem, and has a narrow race

commitServiceNotification validates the generation under serviceNotificationLock, then returns so that notificationManager.notify() runs outside the lock — deliberately, per the KDoc. That opens a last-writer window: an older job can pass its generation check, get descheduled, and have a newer generation's notify() land first; the older job's notify() then overwrites it with stale text. It needs the newer job to complete a full async render in the gap between the old job's commit and its very next statement, so it is unlikely rather than impossible, and it self-corrects on the next telemetry tick. But it is precisely the failure the generation counter exists to prevent.

The generation counter + CoroutineStart.LAZY + install-then-start() + manual previousJob.cancel() + synchronized blocks come to roughly 60 lines. A conflated flow gives the same guarantees for free, and is inherently ordered, so the race cannot arise:

private val serviceState = MutableSharedFlow<ServiceNotificationSnapshot>(
    extraBufferCapacity = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST,
)

init {
    scope.launch { serviceState.collectLatest { snapshot -> renderAndNotify(snapshot) } }
}

collectLatest conflates and cancels the in-flight render for you. If you would rather keep the current structure, tracking a lastNotifiedGeneration at the notify() site closes the window.

2. The Compose half may not do what the title says

Hoisting stringResource out of AnimatedContent into CurrentlyConnectedText does not make the resource load non-blocking — it relocates the same blocking main-thread load to the initial composition of ConnectionsScreen. So the notification change genuinely avoids blocking, while this change moves it earlier and dedupes it across recompositions.

That may well be the outcome you want, since blocking during an animation frame is worse than blocking at screen entry. Was there a measured jank or ANR behind it? The cost is real API friction — Rssi(label =), MaterialBatteryInfo(unknownLabel =), and a required text: param on CurrentlyConnectedInfo, all workaround surface for an upstream bug. Could you add a TODO naming the CMP version that lets these params be deleted, so they do not calcify into permanent API?

3. Minor: misleading sequencing left behind

MeshService.kt:135-136 now reads as though connectionManager.updateStatusNotification() feeds the notification handed to startForeground, but the update is async, so it no longer does. Not a bug — the async job re-notifys the same SERVICE_NOTIFY_ID moments later — but the adjacency implies a data dependency that has gone away. A comment would save the next reader the trip.

Checked and fine

runWithRenderScope parents its Job to the test job and cancels in finally, so no UncompletedCoroutinesError; kotlinx.coroutines.test is already api in core/testing; DisconnectButton already had a label param; and starting an already-cancelled LAZY job in the install path is a correct no-op.

@jeremiah-k

Copy link
Copy Markdown
Contributor Author

@jamesarich Thanks for the detailed review. This work was driven by a captured ANR rather than a theoretical cleanup: the main thread was blocked in CMP resource loading from stringResource inside the connected-device AnimatedContent, while service-notification formatting was also resolving CMP resources. So the issue was a UI/resource-loading stall, not a database race.

I agree the current generation/job machinery is heavier than necessary, and the notify-after-generation-check window means it is not completely generation-safe. I’m going to move the PR back to draft and rework that path around a single ordered StateFlow/collectLatest renderer.

I’ll retain the Connections hoisting as mitigation for the measured animation-path ANR, add a TODO identifying the temporary CMP workaround, and clarify that the initial foreground notification is only the immediate fallback.

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 19:18
@jeremiah-k
jeremiah-k force-pushed the bugfix/android-compose-resource-anr branch from a4eee02 to 51b102f Compare July 24, 2026 21:47
@jeremiah-k jeremiah-k changed the title fix(android): avoid blocking resource loads in connection status fix(android): avoid resource stalls in connection status Jul 24, 2026
@jeremiah-k

Copy link
Copy Markdown
Contributor Author

I replaced the generation/job machinery with one replaying, drop-oldest SharedFlow collector using collectLatest. That cancels obsolete resource rendering and serializes the complete render-and-notify action, so an older Binder post can no longer land after a newer one.

@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 22:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt (1)

380-398: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer safeCatching {} for the render path too.

The fallback post already uses safeCatching, while the primary render uses a hand-rolled try/catch(Exception) plus @Suppress("TooGenericExceptionCaught") and manual CancellationException rethrow. Using safeCatching here keeps cancellation semantics centralized and drops the suppression.

As per coding guidelines, "Use safeCatching {} from core:common in coroutine or suspend contexts; reserve runCatching for cleanup and teardown code."

♻️ Sketch
-    `@Suppress`("TooGenericExceptionCaught")
     private suspend fun renderAndPostServiceNotification(snapshot: ServiceNotificationSnapshot) {
-        try {
-            val rendered = renderServiceNotification(snapshot)
-            postServiceNotification(rendered.notification, rendered.message)
-        } catch (ex: CancellationException) {
-            throw ex
-        } catch (ex: Exception) {
-            Logger.e(ex) { "Failed to render service notification resources" }
-            val fallback =
-                createServiceStateNotification(
-                    name = applicationLabel,
-                    message = snapshot.previousMessage,
-                    nextUpdateAt = snapshot.nextUpdateAt,
-                )
-            safeCatching { postServiceNotification(fallback, snapshot.previousMessage) }
-                .onFailure { Logger.e(it) { "Failed to post fallback service notification" } }
-        }
+        safeCatching {
+            val rendered = renderServiceNotification(snapshot)
+            postServiceNotification(rendered.notification, rendered.message)
+        }
+            .onFailure { ex ->
+                Logger.e(ex) { "Failed to render service notification resources" }
+                val fallback =
+                    createServiceStateNotification(
+                        name = applicationLabel,
+                        message = snapshot.previousMessage,
+                        nextUpdateAt = snapshot.nextUpdateAt,
+                    )
+                safeCatching { postServiceNotification(fallback, snapshot.previousMessage) }
+                    .onFailure { Logger.e(it) { "Failed to post fallback service notification" } }
+            }
     }

Verify safeCatching rethrows CancellationException before dropping the explicit rethrow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt`
around lines 380 - 398, Update renderAndPostServiceNotification to wrap the
primary renderServiceNotification and postServiceNotification flow in
safeCatching, removing the `@Suppress` annotation and manual try/catch with
CancellationException handling. Preserve the existing error log, fallback
notification creation, and fallback posting behavior through the safeCatching
failure path, and verify safeCatching preserves coroutine cancellation by
rethrowing CancellationException.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt`:
- Around line 421-433: Decouple local-stats seeding from the node lookup in the
telemetry cache initialization block. Keep cachedDeviceMetrics assignment inside
the myNodeNum/nodeDBbyNum lookup, but assign cachedLocalStats directly from
repo.localStats when it is null, preserving the nonzero-uptime filter.

---

Nitpick comments:
In
`@core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt`:
- Around line 380-398: Update renderAndPostServiceNotification to wrap the
primary renderServiceNotification and postServiceNotification flow in
safeCatching, removing the `@Suppress` annotation and manual try/catch with
CancellationException handling. Preserve the existing error log, fallback
notification creation, and fallback posting behavior through the safeCatching
failure path, and verify safeCatching preserves coroutine cancellation by
rethrowing CancellationException.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8800bf02-ec62-4d8b-8fb5-64f5ac331c65

📥 Commits

Reviewing files that changed from the base of the PR and between a4eee02 and 51b102f.

📒 Files selected for processing (10)
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshNotificationManagerImpl.kt
  • core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshService.kt
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/TestScopes.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MaterialBatteryInfo.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicatorUiTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicatorUiTest.kt
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/TestScopes.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplTest.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MaterialBatteryInfo.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
  • core/service/src/androidHostTest/kotlin/org/meshtastic/core/service/MeshNotificationManagerImplConversationTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 22:37
…ryInfo

Add optional label parameters to Rssi and MaterialBatteryInfo composables,
enabling callers to supply pre-resolved display text without changing
existing invocations. This decouples animated composition slots from the
Compose Multiplatform resource lookup that can block the main thread.
…d exception containment

Replace blocking resource loads with suspend-aware formatting in notification
paths. Use deterministic test dispatchers for render assertions. Contain
exceptions from posting fallback notifications. Parent the render scope Job to
runTest so render-coroutine failures remain observable through test completion.
… and localize preview labels

Resolve Rssi and battery display values outside animated composition to
prevent recomposition jank. Localize connection preview labels through
core:resources for consistent string handling.
Replace the generation counter and manually cancelled lazy jobs with one replaying, drop-oldest SharedFlow collector. collectLatest cancels obsolete resource work and serializes the complete render-and-notify action, closing the unlocked Binder last-writer window without holding the snapshot lock across IPC.

Preserve the immediate foreground fallback, cached telemetry and message state, and fallback notification behavior. Use cancellation-aware error containment for asynchronous rendering, and seed persisted local stats independently of local-node row readiness after process or transport restart.

Cover latest-state selection, identical-state reposting after notifications are cleared, and local-stat recovery before the node cache is warm. Clarify the asynchronous startForeground sequence and document the caller-provided Connections labels as a temporary CMP-6615 workaround for Compose Multiplatform 1.11.1.
@jeremiah-k
jeremiah-k force-pushed the bugfix/android-compose-resource-anr branch from 51b102f to 1f8df84 Compare July 24, 2026 23:32
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 23:33
@jamesarich
jamesarich added this pull request to the merge queue Jul 25, 2026
Merged via the queue into meshtastic:main with commit c563814 Jul 25, 2026
15 checks passed
@jeremiah-k
jeremiah-k deleted the bugfix/android-compose-resource-anr branch July 25, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants