One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Location

Ask for location permission, read the device's position once or continuously, and turn addresses into coordinates

One.Location reads where the device is. It asks for permission, gets a single position or a stream of them, keeps tracking in the background when you opt in, and converts between addresses and coordinates. On iOS it is Core Location; on Android it is the platform LocationManager with its GPS and network providers; on web it is the Geolocation API.

For alerts when people enter or leave an area, use Geofencing, which shares this permission.

Usage

Add the reason people see in the permission prompt to your app config before prebuild:

import { one } from 'one/vite'
export default {
plugins: [
one({
native: {
app: {
name: 'MyApp',
location: { whenInUse: 'Show places near you.' },
},
},
}),
],
}

Then ask from a tap and read the position:

import { One } from 'one'
async function findNearby(search: (latitude: number, longitude: number) => Promise<void>) {
const status = await One.Location.requestWhenInUsePermission()
if (status !== 'whenInUse' && status !== 'always') return false
const { latitude, longitude } = await One.Location.getCurrentPosition()
await search(latitude, longitude)
return true
}

How it works

Permission. getPermissionStatus reads the current status at once, with no prompt. requestWhenInUsePermission shows the system prompt only when nothing has been decided, and resolves the status that results; otherwise it resolves the existing status straight away. Prompts need the app in the foreground, so call it from a tap rather than at launch.

One position. getCurrentPosition asks for a single fix. On iOS it uses Core Location’s one-shot request, unless a watch is already running and has a fix from the last 30 seconds, which it returns at once. On Android it returns the newer of the GPS and network providers’ last fixes when one is under 30 seconds old, and otherwise listens to both providers until a fix arrives, giving up after 30 seconds.

Watching. watchPosition streams positions until you call the function it returns. All watches share one stream of updates from the platform, at full accuracy with no distance filter, which stops when the last watch is removed. On Android the providers report about once a second.

Errors in a watch. A watch never throws. If permission is missing or removed, the error callback receives E_LOCATION_PERMISSION; on iOS and Android the watch then ends, while on web it stays registered until you call its stop function. Other failures, such as location services being switched off, send E_LOCATION_UNAVAILABLE and keep the watch, which resumes when a fix is possible again.

Background. watchPosition(onPosition, onError, { background: true }) keeps updates coming while another app is in front. On iOS it turns on background updates and the blue location indicator in the status bar. On Android it starts a foreground service whose ongoing notification names your app. Both need location.background in the config, which adds the background location mode on iOS and ACCESS_BACKGROUND_LOCATION on Android.

Geocoding. geocodeAddress turns text into places, and reverseGeocode turns a coordinate into places. iOS asks Apple’s service through CLGeocoder. Android uses the device’s Geocoder, returning up to five places, or none on a device that has no geocoder. Neither needs location permission, because you supply the address or coordinate.

Web. Positions come from navigator.geolocation with high accuracy, a 30 second cache, and a 10 second timeout. requestWhenInUsePermission asks for a position, which is what makes the browser prompt. Background watches, geocoding, and Always permission are not available.

API reference

FunctionReturns
getPermissionStatus()The status, at once
requestWhenInUsePermission()A promise of the status
requestAlwaysAuthorization()A promise of the status
getCurrentPosition()A promise of one position
watchPosition(onPosition, onError, options?)A function that stops it
geocodeAddress(address)A promise of places
reverseGeocode(latitude, longitude)A promise of places

The status is notDetermined, restricted, denied, whenInUse, or always.

LocationPositionTypeDescription
latitudenumberDegrees north.
numberDegrees north.
longitudenumberDegrees east.
numberDegrees east.
accuracynumberHorizontal accuracy radius in meters.
numberHorizontal accuracy radius in meters.
altitudenumberMeters above sea level, or 0 when unknown.
numberMeters above sea level, or 0 when unknown.
altitudeAccuracynumberVertical accuracy in meters, or -1.
numberVertical accuracy in meters, or -1.
coursenumberHeading in degrees from north, or -1.
numberHeading in degrees from north, or -1.
speednumberMeters per second, or -1.
numberMeters per second, or -1.
timestampnumberWhen the fix was taken, in Unix milliseconds.
numberWhen the fix was taken, in Unix milliseconds.
LocationPlaceTypeDescription
latitudenumberThe place coordinate.
numberThe place coordinate.
longitudenumberThe place coordinate.
numberThe place coordinate.
namestringA name for the place.
stringA name for the place.
streetstringThe street name.
stringThe street name.
houseNumberstringThe number on the street.
stringThe number on the street.
citystringThe city or town.
stringThe city or town.
regionstringThe state or province.
stringThe state or province.
postalCodestringThe postal code.
stringThe postal code.
countrystringThe country name.
stringThe country name.
isoCountryCodestringThe two-letter country code.
stringThe two-letter country code.

Failures carry an error code:

CodeMeans
E_LOCATION_MANIFESTA usage string is missing from the config
E_LOCATION_BACKGROUNDAsked while the app was not in front
E_LOCATION_PERMISSIONPermission is missing or was removed
E_LOCATION_UNAVAILABLENo fix, or location services are off
E_LOCATION_GEOCODEThe geocoder failed

Platform differences

iOSAndroidWeb
SourceCore LocationGPS and networkBrowser
Reuses a fix under 30sDuring a watchYesYes
One fix gives upSystem decidesAfter 30sAfter 10s
BackgroundStatus bar indicatorNotificationNo
GeocodingAppleDevice geocoderEmpty
Restricted statusYesNeverNever

Limits and gotchas

Examples

Show a live distance

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function distanceMeters(a: { latitude: number; longitude: number }, b: { latitude: number; longitude: number }) {
const toRadians = (degrees: number) => (degrees * Math.PI) / 180
const dLat = toRadians(b.latitude - a.latitude)
const dLon = toRadians(b.longitude - a.longitude)
const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRadians(a.latitude)) * Math.cos(toRadians(b.latitude)) * Math.sin(dLon / 2) ** 2
return 2 * 6371000 * Math.asin(Math.sqrt(h))
}
function DistanceTo({ target }: { target: { latitude: number; longitude: number } }) {
const [meters, setMeters] = useState<number | null>(null)
const [problem, setProblem] = useState<string | null>(null)
useEffect(() => {
return One.Location.watchPosition(
(position) => {
setProblem(null)
setMeters(distanceMeters(position, target))
},
(error) => setProblem(error.code === 'E_LOCATION_PERMISSION' ? 'Location is off' : 'Searching…')
)
}, [target])
if (problem) return <Text>{problem}</Text>
return <Text>{meters === null ? 'Locating…' : `${Math.round(meters)} m away`}</Text>
}

Returning the stop function from the effect ends the watch when the screen unmounts, and a temporary outage shows as searching rather than an error.

Track a run in the background

import { One } from 'one'
type Point = { latitude: number; longitude: number; timestamp: number }
function startRun(onPoint: (point: Point) => void, onLost: (code: string) => void) {
return One.Location.watchPosition(
({ latitude, longitude, timestamp, accuracy }) => {
if (accuracy <= 30) onPoint({ latitude, longitude, timestamp })
},
(error) => onLost(error.code),
{ background: true }
)
}

Set location: { whenInUse: '…', background: true } in the config first. Dropping fixes worse than 30 meters keeps the route from zigzagging when the signal is weak. Call the returned function when the run ends.

Show the street for a coordinate

import { One } from 'one'
async function describe(latitude: number, longitude: number) {
const [place] = await One.Location.reverseGeocode(latitude, longitude)
if (!place) return 'Unknown place'
const street = [place.houseNumber, place.street].filter(Boolean).join(' ')
return [street || place.name, place.city].filter(Boolean).join(', ')
}

Every address field is optional, so the function builds the label from whatever came back and falls back to the place name.

Edit this page on GitHub.