[Feature] Add fused-location skill for foreground FLP, Android 12 permissions, and update lifecycle
- Dominant language
- Python
- Stars
- 7.3k
- Forks
- 476
- Avg merge
- 8h 59m
- Merged PRs (30d)
- 7
Description
> [!NOTE]
> Feature requests are accepted only if the proposed skill fills a verified gap in state-of-the-art LLMs. Evaluations below used a **fast** model (Cursor Composer 2.5 Fast) and a **thinking** model (Cursor Grok 4.6). Gemini 3 Flash / Gemini 3.1 Pro were not available in this environment; please re-run the same prompts on those models if needed.
### What is the gap in the LLM's knowledge?
`android/skills` has no location category. Current models **already know** to reach for `FusedLocationProviderClient` and they **do not** grab `ACCESS_BACKGROUND_LOCATION` for on-screen features. The remaining, repeatable gaps are the **decision tree and Android 12 permission semantics**, not "does FLP exist".
Four prompts, no web/docs/skills, code from memory only:
| Prompt | Fast (Composer 2.5) | Thinking (Grok 4.6) |
| --- | --- | --- |
| **A.** Compose screen, get location **once**, city-level, targetSdk 35 | Pass: `ACCESS_COARSE_LOCATION` only + `getCurrentLocation(PRIORITY_BALANCED_POWER_ACCURACY)` | Pass: same + `CurrentLocationRequest` / `GRANULARITY_COARSE` |
| **B.** Track while screen visible (street-level), **must stop when leaving** | Pass: FINE+COARSE together, `requestLocationUpdates`, `ON_START`/`ON_STOP` | Pass: same + location-enabled check |
| **C.** Vague: "home screen, show current lat/lng" | Fail: requests FINE+COARSE and `results.values.all { it }`, so an Android 12 **approximate-only grant is treated as denied**. Also `PRIORITY_HIGH_ACCURACY` for a one-shot home screen. | Fail: requests FINE+COARSE, then starts **unbounded `requestLocationUpdates`** in a ViewModel (not tied to `ON_STOP`) for a one-shot display. `getCurrentLocation(..., null)` (no `CancellationToken`). |
| **D.** "I need **GPS** coordinates… keep them updated as the user moves" | Fail: FLP + HIGH_ACCURACY, but cleanup is only `DisposableEffect.onDispose` / `ViewModel.onCleared`. **Home / recents does not stop updates** (background leak without a background permission). | Fail: same lifecycle hole (`DisposableEffect(granted)` only). Seeds UI from **`lastLocation`** with no age check. Starts `PRIORITY_HIGH_ACCURACY` even if the user granted only coarse. |
When the prompt spelled out "coarse / once / stop on leave", both models complied. When a developer speaks naturally ("show my coordinates", "GPS", "keep updating"), they over-request fine, start a stream they do not stop on `ON_STOP`, and mishandle Android 12 approximate grants.
None of the eight runs used `SettingsClient.checkLocationSettings`. Permission granted was treated as "location is on".
These match documented platform rules:
- Approximate vs precise and "request fine **with** coarse": https://developer.android.com/develop/sensors-and-location/location/permissions
- Prefer `getCurrentLocation()` over managing updates / stale `getLastLocation()`: https://developer.android.com/develop/sensors-and-location/location/retrieve-current
### Proposed Skill
Path: `location/fused-location/SKILL.md` (same layout as `camera/camerax`).
Scope: **foreground only** — runtime permissions (coarse vs fine, Android 12+), `getCurrentLocation` vs `requestLocationUpdates`, bind updates to `ON_START`/`ON_STOP`, location settings. **Not** Maps, geofencing, or background location.
Full draft:
~~~~markdown
---
name: fused-location
description: >
Guide Android foreground location with FusedLocationProviderClient.
Use when implementing current location, location updates, runtime location
permissions, approximate vs precise location on Android 12+, or when an
app would otherwise use LocationManager GPS_PROVIDER.
license: Complete terms in LICENSE.txt
metadata:
author: community proposal
last-updated: '2026-08-29'
keywords:
- recipe
- Android
- location
- FusedLocationProvider
- FusedLocationProviderClient
- ACCESS_COARSE_LOCATION
- ACCESS_FINE_LOCATION
- getCurrentLocation
- requestLocationUpdates
- Compose
- permissions
- approximate location
- precise location
---
Procedural guidance for **foreground** location on Android using Play services
`FusedLocationProviderClient`. Grounded in
[Request location permissions](https://developer.android.com/develop/sensors-and-location/location/permissions)
and
[Get the last known location](https://developer.android.com/develop/sensors-and-location/location/retrieve-current).
**Out of scope:** Maps SDK, geofencing, activity recognition, and background
location (`ACCESS_BACKGROUND_LOCATION`, location foreground services).
## Step 1: pick accuracy and API
Ask what the feature actually needs. Do not default to GPS / fine / streaming.
| Need | Permission | API |
| --- | --- | --- |
| City / weather / "which area" **once** | `ACCESS_COARSE_LOCATION` only | `getCurrentLocation` + `PRIORITY_BALANCED_POWER_ACCURACY` or `PRIORITY_LOW_POWER` |
| Street-level **once** | `ACCESS_FINE_LOCATION` **and** `ACCESS_COARSE_LOCATION` | `getCurrentLocation` + `PRIORITY_HIGH_ACCURACY` |
| Street-level **while UI is visible** | Fine + coarse | `requestLocationUpdates` + `removeLocationUpdates` on `ON_STOP` |
`getLastLocation()` / `lastLocation` is a cache. It can be `null` or hours old.
Do **not** use it as the primary one-shot API.
[Prefer `getCurrentLocation()`](https://developer.android.com/develop/sensors-and-location/location/retrieve-current).
```kotlin
// WRONG — GPS provider, no fusion, easy to leak updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 2000L, 0f, listener
)
// WRONG — stale cache as the only source
fusedClient.lastLocation.addOnSuccessListener { loc -> show(loc) }
// CORRECT — one-shot, fused, cancellable
val cts = CancellationTokenSource()
fusedClient.getCurrentLocation(
Priority.PRIORITY_BALANCED_POWER_ACCURACY,
cts.token,
)
```
Dependency: `com.google.android.gms:play-services-location`.
## Step 2: declare and request permissions
### Manifest
- City-level: declare **only** `ACCESS_COARSE_LOCATION`.
- Precise: declare **both** `ACCESS_FINE_LOCATION` and `ACCESS_COARSE_LOCATION`.
On Android 12+ the system [ignores a fine-only request](https://developer.android.com/develop/sensors-and-location/location/permissions)
(`ACCESS_FINE_LOCATION must be requested with ACCESS_COARSE_LOCATION`).
- Do **not** declare `ACCESS_BACKGROUND_LOCATION` for on-screen features.
### Runtime
Use `ActivityResultContracts.RequestPermission` (coarse only) or
`RequestMultiplePermissions` (fine + coarse in **one** call).
Android 12+ users can grant **approximate** even when the app asked for precise.
That is a valid grant, not a denial.
```kotlin
// WRONG — approximate-only looks like "denied"
val granted = results.values.all { it }
// CORRECT — any location grant is success; inspect fine separately
val hasCoarse = results[Manifest.permission.ACCESS_COARSE_LOCATION] == true ||
context.hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
val hasFine = results[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
```
If the feature **requires** street-level accuracy and the user granted only
approximate, keep the feature working at coarse if possible; otherwise explain
why precise is needed and request `ACCESS_FINE_LOCATION` again (upgrade dialog).
Do not block the whole app on first approximate grant unless the UX truly cannot
function.
Do **not** treat the first denial as "permanently denied".
`shouldShowRequestPermissionRationale == false` right after the first dialog is
unreliable.
## Step 3: one-shot current location
```kotlin
@SuppressLint("MissingPermission")
suspend fun currentLocation(
client: FusedLocationProviderClient,
fineGranted: Boolean,
): Location? {
val cts = CancellationTokenSource()
val priority = if (fineGranted) {
Priority.PRIORITY_HIGH_ACCURACY
} else {
Priority.PRIORITY_BALANCED_POWER_ACCURACY
}
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { cts.cancel() }
client.getCurrentLocation(priority, cts.token)
.addOnSuccessListener { loc -> if (cont.isActive) cont.resume(loc) }
.addOnFailureListener { e ->
if (cont.isActive) cont.resumeWithException(e)
}
}
}
```
If the result is `null`, the usual causes are: permission missing, location
services off, or Play services unavailable — not "call `lastLocation` and hope".
## Step 4: updates only while the screen is visible
`ViewModel.onCleared()` and `DisposableEffect.onDispose` do **not** run when the
user presses Home or opens recents. Updates keep flowing in the background
without a background permission. Bind to `Lifecycle.Event.ON_START` / `ON_STOP`.
```kotlin
// WRONG — Home / recents leaves the callback registered
DisposableEffect(Unit) {
client.requestLocationUpdates(request, callback, Looper.getMainLooper())
onDispose { client.removeLocationUpdates(callback) }
}
// CORRECT
DisposableEffect(lifecycleOwner, client) {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { /* ui state */ }
}
}
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> client.requestLocationUpdates(
request, callback, Looper.getMainLooper()
)
Lifecycle.Event.ON_STOP -> client.removeLocationUpdates(callback)
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
client.removeLocationUpdates(callback)
}
}
```
Use `LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, intervalMillis)`
(or balanced/low-power for coarse). Do not use the deprecated
`LocationRequest.create()`.
## Step 5: resolve location settings
Permission granted ≠ location enabled. If `getCurrentLocation` returns `null` or
updates never arrive, check settings with
[`SettingsClient.checkLocationSettings`](https://developers.google.com/android/reference/com/google/android/gms/location/SettingsClient)
and, on `ResolvableApiException`, launch the system resolution dialog. Also
handle Play services missing / updating.
## Common pitfalls
- Defaulting to `ACCESS_FINE_LOCATION` + `PRIORITY_HIGH_ACCURACY` for "show my
coordinates" on a home screen.
- Starting `requestLocationUpdates` when `getCurrentLocation` is enough.
- `results.values.all { it }` after `RequestMultiplePermissions` (Android 12
approximate grant).
- Requesting `ACCESS_FINE_LOCATION` **without** `ACCESS_COARSE_LOCATION`.
- Pairing `ACCESS_BACKGROUND_LOCATION` with the first foreground request.
- Using `LocationManager` / `GPS_PROVIDER` instead of fused location.
- Passing `null` as the `CancellationToken` to `getCurrentLocation` and never
cancelling.
- Seeding UI from `lastLocation` with no age check.
## Official docs
- [Request location permissions](https://developer.android.com/develop/sensors-and-location/location/permissions)
- [Get current / last location](https://developer.android.com/develop/sensors-and-location/location/retrieve-current)
- [Request location updates](https://developer.android.com/develop/sensors-and-location/location/request-updates)
- [Approximate location (Android 12+)](https://developer.android.com/develop/sensors-and-location/location/permissions)
~~~~
### Additional Context
- Repo currently has `camera`, `identity`, `system`, and other categories, but **no `location/`** tree.
- README says public PRs are not accepted, so this is an issue-only proposal rather than a PR.
- The skill is intentionally narrower than "maps + geofence + background" so it stays in the "LLM underperforms" zone (decision tree + Android 12), not a restatement of FLP existence.
- Happy to add Maps / background location as **separate** skills if this one lands.
Contributor guide
Research direction
Start by comparing the proposed location/fused-location/SKILL.md with the existing camera/camerax skill layout and repository README. Add the supplied foreground fused-location guidance at that path, preserving its Android 12 permissions, current-location, lifecycle, and settings scope. Done means the new skill matches the repository format and clearly excludes Maps, geofencing, and background location.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- documentation
- Issue type
- Feature
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100