Skip to content

[location] Gate Android puck pulsing on layer visibility and lifecycle - #2739

Open
ajtrasatti wants to merge 2 commits into
mapbox:mainfrom
ajtrasatti:fix/hidden-puck-pulse-lifecycle
Open

ajtrasatti wants to merge 2 commits into
mapbox:mainfrom
ajtrasatti:fix/hidden-puck-pulse-lifecycle

Conversation

@ajtrasatti

@ajtrasatti ajtrasatti commented Oct 31, 2026 •

Copy link
Copy Markdown

Problem

I noticed high CPU usage with location pulsing enabled, including after turning device location off. A native-only reproduction isolates a lifecycle defect: enabling pulsingEnabled before the first location update starts the pulse animator even though the native puck layer is hidden. The animator continues changing the hidden layer's emphasis-circle radius and color, sustaining map rendering without a visible puck.

Turning device location off does not necessarily clear the last location or hide the native layer. The reproduction below removes that ambiguity by using a custom provider that emits no locations.

Resulting behavior

Run the pulse only when the native puck layer is visible and the location component is started. Preserve the caller's requested pulse setting so pulsing resumes when those conditions are met. A visible, active puck keeps its existing pulse appearance and cadence.

In our app, disabling pulsing is the immediate mitigation. This PR contributes the lifecycle fix so hidden pucks do not need that workaround.

Implementation

  • Gate pulse start/resume on both native layer visibility and component started state, including settings and accuracy-radius updates.
  • Prevent settings changes from reactivating a stopped location component.
  • Ignore pending pulse renderer callbacks while the layer is hidden.
  • Install the repeat listener once, so repeated starts do not accumulate listeners.

Scope

This change handles native hidden/stopped states, including before the first location update. Native layer visibility is distinct from viewport culling: a puck outside the camera viewport can still have a visible native layer. Offscreen-culling behavior is outside this fix.

Visible pulsing continues to generate render work and can prevent map idle. This PR preserves that behavior and makes no public API or pulse-design changes.

Companion iOS wrapper fix: rnmapbox/maps#4431 addresses rnmapbox/maps#3787 by removing the hidden puck renderer while preserving camera following. The two PRs address the hidden-rendering paths at their respective ownership layers.

Reproduction and validation

Reproduced on unmodified public Android SDK 11.34.1 using a native app, blank style, fixed camera, and a custom location provider that emits no locations. The reproduction requires no React Native code, tile sources, markers, or incoming location updates.

The patched native probe uses the location module compiled from this branch with the public 11.34.1 engine on an API 34 ARM64 emulator:

  • Before the first location update, the pulse animator is stopped and the blank-map probe settles to zero render-finished events. The unmodified release continues animating/rendering in that state.
  • First location, hide/show, stop/settings-update/start, pulse off/on, style reload, and actual app background/foreground stop or resume pulsing as expected. The probe uses reflection to observe internal state and exercise internal hide/show; app background/foreground uses the actual application lifecycle.
  • The repeat-listener count remains one across these transitions.

Automated checks: 95 tests pass across the five affected test classes in an isolated Android library build of the same modified source/test files against public SDK 11.34.1, with Robolectric 4.14 and MockK 1.13.7. Removing the lifecycle/renderer/listener fixes causes regression failures; restoring them passes. The stopped-settings test also fails when the outer plugin's lifecycle guard is removed. Upstream :plugin-locationcomponent:ktlint passes.

Validation limit: the full upstream :plugin-locationcomponent:testDebugUnitTest task cannot resolve private 11.35 snapshot core/common dependencies without registry credentials (HTTP 401). The isolated public-release test run is not a full upstream build or test pass. Physical-device energy measurements remain outside this validation.

Native recordings and logs are retained locally. The CLI refused video attachment because it requires upstream repository write access; no recording is attached to this PR.

Pull request checklist

  • Describe the Android behavior change and reproduction.
  • Add regression tests and validate the native code path.
  • Add a changelog entry.
  • No public API changes.
  • Full upstream checks (blocked by private snapshot dependency access).

@CLAassistant

CLAassistant commented Oct 31, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@ajtrasatti ajtrasatti changed the title [location] Stop pulsing while the puck is hidden or stopped [location] Gate Android puck pulsing on layer visibility and lifecycle Oct 31, 2026
@ajtrasatti
ajtrasatti marked this pull request as ready for review October 31, 2026 14:00
@ajtrasatti
ajtrasatti requested a review from a team as a code owner October 31, 2026 14:00
@ajtrasatti
ajtrasatti requested a review from natiginfo October 31, 2026 14:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants