android / android/skills

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

Open Beginner friendly
#166 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.