Skip to content

[Feature] Add fused-location skill for foreground FLP, Android 12 permissions, and update lifecycle #166

Description

@endlessYoung

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:

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:

---
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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions