watchPosition mints a distinct id per call and stores its callback in a single field:
private var locationCallback: LocationCallback? = null
private var watchId = 0
@JavascriptInterface
fun watchPosition(optionsJson: String): Int {
...
val currentWatchId = ++watchId
fusedLocationClient = LocationServices.getFusedLocationProviderClient(activity)
locationCallback = object : LocationCallback() { ... }
...
}
@JavascriptInterface
fun clearWatch(watchId: Int) {
locationCallback?.let { fusedLocationClient?.removeLocationUpdates(it) }
locationCallback = null
}Two things follow, and both are silent.
A second watch leaks the first
locationCallback is overwritten, not appended to. The first callback is still registered with the fused location provider — it keeps waking the app for location updates — but nothing holds a reference to it any more, so removeLocationUpdates can never be called with it. It runs until the process dies.
Two watchPosition calls is not an exotic pattern: a map view and a "distance to destination" widget on the same page is enough.
clearWatch ignores the id it is given
The parameter is never read. clearWatch(1) removes whatever callback the field currently holds, which is the most recent watch — so:
- clearing watch 1 while watch 2 is running stops watch 2;
- clearing watch 2 twice does nothing the second time and reports nothing;
- the id
watchPositionreturned is, in practice, a number the page can only use to be misled.
craft.d.ts declares watchPosition(): number and clearWatch(id: number), which is the W3C geolocation shape — where ids do identify individual watches.
Suggested fix
A map from id to callback:
private val locationCallbacks = mutableMapOf<Int, LocationCallback>()
fun clearWatch(watchId: Int) {
locationCallbacks.remove(watchId)?.let { fusedLocationClient?.removeLocationUpdates(it) }
}That fixes both at once: the first watch is no longer orphaned, and the id means what the SDK says it means.
Not migrated to Zig
Recorded as a deferral. Both methods are the Kotlin's own instance state — locationCallback, fusedLocationClient, watchId — and CraftNative has no reference to the CraftBridge that owns them, the same wall setFlashlight/toggleFlashlight sit behind. Migrating either half would leave the other reading a field that had stopped being maintained.