ionic-team / ionic-team/capacitor-geolocation

bug(ios): one transient CoreLocation error permanently kills location delivery — all later getCurrentPosition calls time out until app relaunch

Open
#91 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
12
Forks
19
PR merge metrics
No merged PRs in 30d

Description

### Bug description

On iOS, after **one** transient CoreLocation failure, **every subsequent `Geolocation.getCurrentPosition()` call in the app session fails with `OS-PLUG-GLOC-0010` (timeout)** — even when the device gets a GPS fix — until the app process is killed and relaunched.

### Root cause (traced through the plugin + IONGeolocationLib source)

1. `CLLocationManager.requestLocation()` frequently fails with the **transient** `kCLErrorLocationUnknown` on a cold start / indoors with `enableHighAccuracy: true`. Apple's docs say this error should be ignored while the manager keeps trying.
2. In `IONGLOCManagerWrapper` (ion-ios-geolocation), `locationManager(_:didFailWithError:)` sets `currentLocation = nil`, and the `currentLocationPublisher` pipeline turns that into a **failure** (`tryMap` throws `IONGLOCLocationError.locationUnavailable`).
3. In `GeolocationPlugin.swift` → `bindLocationPublisher()`, that error is handled with:
```swift
.catch { ... self?.callbackManager?.sendError(.positionUnavailable)
return Empty().eraseToAnyPublisher() }
```
`Empty()` **completes** the stream, so the Combine subscription (`locationCancellable`) is permanently finished.
4. `locationInitialized` is **never reset**, so the guard in `bindLocationPublisher()` (`guard !locationInitialized else { return }`) prevents any re-subscription for the rest of the process lifetime.
5. Net effect: every later `getCurrentPosition()` registers its callback and `requestSingleLocation()` runs — `didUpdateLocations` even fires with a valid fix — but **there is no subscriber left** to deliver it, so the only thing that ever resolves the call is the timeout timer (`locationTimeoutPublisher` has its own, still-alive subscription).

The only recovery paths are relaunching the app, or `appDidBecomeActive` — which re-binds only when `watchCallbacks` is non-empty, so plain `getCurrentPosition()` users never recover.

### Steps to reproduce

1. iOS device, app using `@capacitor/geolocation` 8.2.0, location permission granted.
2. Trigger one transient CoreLocation failure (easiest: cold GPS start indoors with `enableHighAccuracy: true` and a short-ish timeout; first call after boot/standby often does it) → call rejects with `OS-PLUG-GLOC-0002`.
3. Move somewhere with good GPS, call `getCurrentPosition()` repeatedly.
4. **Every** call now rejects with `OS-PLUG-GLOC-0010` ("Could not obtain location in time") regardless of signal, until the app is force-killed and reopened.

### Expected behavior

- A transient `kCLErrorLocationUnknown` should not permanently break location delivery — either filter it out (keep waiting, per Apple docs) or re-establish the subscription on the next request (e.g. reset `locationInitialized` in the `.catch`, or model errors as values so the stream never terminates).

### Real-world impact

Field app for construction workers: mandatory-GPS clock-in. Workers with the app open since morning got "no GPS" on every attempt for minutes and only succeeded after force-killing the app. We shipped a workaround (cap the plugin timeout, then fall back to WKWebView `navigator.geolocation`, which works fine in these "broken" sessions since it bypasses the plugin), but the plugin itself stays unusable after the first transient error.

### Plugin version

- `@capacitor/geolocation`: 8.2.0 (also affects 7.x by code inspection — same architecture since the IONGeolocationLib rewrite)
- Capacitor: 8.4.1
- iOS (WKWebView, `server.url` remote)

### Suggested fix

In `bindLocationPublisher()`'s `.catch`, reset `locationInitialized = false` (and drop `locationCancellable`) so the next request re-binds; or better, filter transient `kCLErrorLocationUnknown` in `IONGLOCManagerWrapper.didFailWithError` instead of nil-ing `currentLocation`, so the publisher never fails on a recoverable condition.

Contributor guide

Open the contributing guide

Research direction

Start in GeolocationPlugin.swift at bindLocationPublisher() and trace locationCancellable, locationInitialized, and the .catch path. Then inspect IONGLOCManagerWrapper.didFailWithError and its currentLocationPublisher behavior. Confirm the failure, completion, and later valid-fix sequence on iOS; done means transient CoreLocation errors no longer prevent later getCurrentPosition calls from receiving a valid location.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.