skip to content

In expo-location, when do you use getCurrentPositionAsync versus watchPositionAsync, and what does each cost in time and battery?

level: middleimportance: should knowfreq 40%

answer

  1. one fix or a stream
  2. a fresh fix can take seconds
  3. last known is fast but stale
  4. remove() the subscription
  5. foreground only, distanceInterval

basics

~20 s

getCurrentPositionAsync asks for one fresh fix, which can take seconds; watchPositionAsync streams updates to a callback until you call remove() on its subscription, and only while the app is in the foreground. getLastKnownPositionAsync is the fast, possibly stale option.

solid answer

~40 s

`getCurrentPositionAsync` resolves with one `LocationObject` after the device obtains a fix; the source warns this can take several seconds, especially indoors, and suggests `getLastKnownPositionAsync` with `maxAge` and `requiredAccuracy` when a recent cached fix is good enough. `watchPositionAsync(options, callback, errorHandler)` resolves with a subscription and calls the callback on each update until `remove()` is called. Updates arrive only while the app is in the foreground; background tracking needs a task-based API. Both take `accuracy`, default `Accuracy.Balanced`; higher accuracy costs more power. For a watch, `distanceInterval` (meters) and, on Android, `timeInterval` limit how often updates fire. Tagging a saved receipt needs one fix; tracking a business trip's mileage on screen needs a watch.

code

tsx · 35 lines
tsx
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';

// One read when a receipt is saved: cached first, fresh fix as fallback.
export async function receiptCaptureCoords() {
  const last = await Location.getLastKnownPositionAsync({ maxAge: 60_000, requiredAccuracy: 200 });
  if (last) return last.coords;
  const current = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced });
  return current.coords;
}

// A stream only while the trip-mileage screen is mounted.
export function useTripPosition() {
  const [coords, setCoords] = useState<Location.LocationObjectCoords | null>(null);

  useEffect(() => {
    let subscription: Location.LocationSubscription | undefined;
    let unmounted = false;

    Location.watchPositionAsync(
      { accuracy: Location.Accuracy.Balanced, distanceInterval: 50 },
      (location) => setCoords(location.coords),
    ).then((sub) => {
      if (unmounted) sub.remove();
      else subscription = sub;
    });

    return () => {
      unmounted = true;
      subscription?.remove();
    };
  }, []);

  return coords;
}

go deeper

for a junior

Recall the difference: one fresh fix with getCurrentPositionAsync, a stream with watchPositionAsync, and remove() to stop the stream.

for a middle

Explain the time and power trade-offs, getLastKnownPositionAsync as the fast fallback, and the accuracy, distanceInterval and Android-only timeInterval options.

for a senior

Design reads that do not block the UI, watches that cannot leak, and accuracy choices matched to each feature's real need.

for a principal

Set a location budget for the product: which features may read location, how often, at what accuracy, and which ones justify background tracking.

## Three ways to read position `expo-location` offers three foreground reads, each with a different cost: | function | returns | speed | freshness | |---|---|---|---| | `getLastKnownPositionAsync(options)` | `LocationObject` or `null` | fast, no new fix | may be old | | `getCurrentPositionAsync(options)` | `LocationObject` | may take seconds | fresh | | `watchPositionAsync(options, callback, errorHandler)` | `LocationSubscription` | first update then a stream | continuous | A `LocationObject` has `coords` (including `latitude`, `longitude`, `altitude` and `accuracy`) and a `timestamp`. ## getCurrentPositionAsync: one fresh fix It asks the platform for a **new fix** and resolves once one arrives. The source notes that obtaining a fix **may take several seconds**, especially inside buildings, and suggests `getLastKnownPositionAsync` when a quick answer matters more than precision. Options: - **`accuracy`**: an `Accuracy` enum value, default **`Accuracy.Balanced`**; lower values let the platform avoid power-hungry providers such as GPS. - **`mayShowUserSettingsDialog`**: Android only, default `true`; may ask the user to turn on improved accuracy mode. ## getLastKnownPositionAsync: cheap and possibly stale It returns the position the device already knows, without requesting a new fix, or **`null`** when there is none or it fails the requirements: - **`maxAge`**: milliseconds after which a cached position counts as too old; - **`requiredAccuracy`**: the largest acceptable uncertainty radius in meters. A common pattern is **last-known first, current as a fallback**. ## watchPositionAsync: a stream with a handle It registers a callback and resolves with a **`LocationSubscription`**: 1. The callback receives a `LocationObject` on each update; an optional error handler receives error messages. 2. **`distanceInterval`** (meters) suppresses updates until the device has moved that far; **`timeInterval`** (milliseconds, Android only) sets a minimum time between updates. Their defaults may depend on `accuracy`. 3. **`subscription.remove()`** stops delivery. Forgetting it keeps location running after the screen is gone. 4. Updates arrive **only while the app is in the foreground**; on Android the module stops watching when the activity goes to the background and resumes when it returns. Background tracking uses `startLocationUpdatesAsync` with a task, which is a separate feature. ## Android specifics - **`mayShowUserSettingsDialog`** (default `true`) lets `getCurrentPositionAsync` and `watchPositionAsync` ask the user to switch the device to its improved accuracy mode; the source notes both calls already do what `enableNetworkProviderAsync` would, so calling it first is unnecessary. - **`timeInterval`** applies on Android only; on iOS, throttle a watch with `distanceInterval`. - A position read can fail when location services are off for the whole device; handle the rejected promise and explain it, rather than retrying in a loop. ## Cost Power use grows with **accuracy** and **update frequency**: - use the lowest accuracy that answers the question; - set a `distanceInterval` for watches so a stationary device does not wake the app; - stop the watch as soon as the feature is not visible. ## Choosing for an expense app - **Tagging a receipt with where it was captured**: one read. Try `getLastKnownPositionAsync({ maxAge: 60_000 })`, then fall back to `getCurrentPositionAsync({ accuracy: Accuracy.Balanced })`. - **A live trip-mileage screen**: `watchPositionAsync` with a `distanceInterval`, started when the screen mounts and removed when it unmounts. ## Pitfalls - Awaiting `getCurrentPositionAsync` on app start and blocking the UI while a fix arrives. - Calling `watchPositionAsync` in an effect without removing the subscription in the cleanup, including the race where the component unmounts before the promise resolves. - Expecting watch updates while the app is in the background. - Requesting the highest accuracy for a feature that needs only the city. Permission handling around these calls, the get and request functions and how a grant can be approximate, is a separate concern.

  • Why can getLastKnownPositionAsync return null?
    It never requests a new fix. It returns `null` when the device has no cached position, or when the cached one is older than `maxAge` or less accurate than `requiredAccuracy`. Code must then fall back to `getCurrentPositionAsync`.
  • What happens to a watchPositionAsync subscription when the app goes to the background?
    Updates stop: the source says they only occur while the app is in the foreground, and on Android the module stops watching when the activity is backgrounded and resumes on return. Tracking in the background needs `startLocationUpdatesAsync` with a registered task.

saying these in an interview costs you the question

  • getCurrentPositionAsync always resolves instantly from a cached value.
  • watchPositionAsync keeps delivering updates while the app is in the background.
  • A watch stops by itself when the component that started it unmounts.
  • Always request the highest accuracy to be safe.
  • timeInterval throttles watch updates on both platforms.