mapbox / mapbox/mapbox-maps-android

Unexpected Camera Shift After Disabling Location Tracking

Open
#2,513 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auto-triaged bug :beetle:
Dominant language
Kotlin
Stars
578
Forks
161
PR merge metrics
No merged PRs in 30d

Description

Environment

  • Devices affected: Various Android devices, including Poco M6 Pro (Android 14)
  • Maps SDK Version: Mainly 11.2.1, but also reproduced on 11.7.2

Observed behavior and steps to reproduce

There is an unexpected camera behavior when using location tracking

val followPuckViewportState = viewport.makeFollowPuckViewportState(
    FollowPuckViewportStateOptions.Builder()
        .pitch(45.0)
        .bearing(FollowPuckViewportStateBearing.SyncWithLocationPuck)
        .zoom(17.0)
        .build()
)

viewport.transitionTo(
    followPuckViewportState,
    viewport.makeDefaultViewportTransition(
        DefaultViewportTransitionOptions.Builder().maxDurationMs(500).build()
    )
)

Steps to reproduce:

  1. Enable location tracking with the code above (camera will move to user's last coordinate).
  2. Do not receive any coordinate updates.
  3. Disable location tracking by manually moving the camera.
  4. Get new coordinate.

Observed behavior:
The camera returns to the previous location as if location tracking was still active (but with the previous coordinate, not the new coordinate). After this, if we receive new coordinates, then everything is as expected: location tracking is disabled.

Expected behavior

If we disable location tracking (either by moving the camera or by calling viewport.idle()), then it no longer affects the camera in any way, all internal deferred actions are cancelled in ViewportPlugin.

Notes / preliminary analysis

In the debug I saw that FollowPuckViewportStateImpl.stopUpdatingCamera() is called, in which removeIndicatorListenerIfNeeded() is called. In removeIndicatorListenerIfNeeded() there is such code

private fun removeIndicatorListenerIfNeeded() {
  if (isObservingLocationUpdates && dataSourceUpdateObservers.isEmpty() && !isFollowingStateRunning) {
    locationComponent.removeOnIndicatorPositionChangedListener(indicatorPositionChangedListener)
    locationComponent.removeOnIndicatorBearingChangedListener(indicatorBearingChangedListener)
    isObservingLocationUpdates = false
    // when unsubscribed from the location updates, we don't want to keep an outdated location, so
    // when user transition to the FollowPuckViewportState, there wouldn't be any unintentional jump.
    lastBearing = null
    lastLocation = null
  }
}

dataSourceUpdateObservers is not empty.

If replace

viewport.makeDefaultViewportTransition(
    DefaultViewportTransitionOptions.Builder().maxDurationMs(500).build()
)

with

viewport.makeImmediateViewportTransition()

then there is no such problem. And dataSourceUpdateObservers is empty on stopUpdatingCamera().

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with FollowPuckViewportStateImpl.stopUpdatingCamera() and removeIndicatorListenerIfNeeded(), then inspect how DefaultViewportTransition handles deferred actions in ViewportPlugin. Reproduce the sequence with a 500 ms transition and compare it with makeImmediateViewportTransition(); done means disabling tracking prevents the camera from returning to the previous location when a later coordinate arrives.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.