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.
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}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.
| Function | Returns |
|---|---|
| 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.
| LocationPosition | Type | Description |
|---|---|---|
latitudenumberDegrees north. | number | Degrees north. |
longitudenumberDegrees east. | number | Degrees east. |
accuracynumberHorizontal accuracy radius in meters. | number | Horizontal accuracy radius in meters. |
altitudenumberMeters above sea level, or 0 when unknown. | number | Meters above sea level, or 0 when unknown. |
altitudeAccuracynumberVertical accuracy in meters, or -1. | number | Vertical accuracy in meters, or -1. |
coursenumberHeading in degrees from north, or -1. | number | Heading in degrees from north, or -1. |
speednumberMeters per second, or -1. | number | Meters per second, or -1. |
timestampnumberWhen the fix was taken, in Unix milliseconds. | number | When the fix was taken, in Unix milliseconds. |
| LocationPlace | Type | Description |
|---|---|---|
latitudenumberThe place coordinate. | number | The place coordinate. |
longitudenumberThe place coordinate. | number | The place coordinate. |
namestringA name for the place. | string | A name for the place. |
streetstringThe street name. | string | The street name. |
houseNumberstringThe number on the street. | string | The number on the street. |
citystringThe city or town. | string | The city or town. |
regionstringThe state or province. | string | The state or province. |
postalCodestringThe postal code. | string | The postal code. |
countrystringThe country name. | string | The country name. |
isoCountryCodestringThe two-letter country code. | string | The two-letter country code. |
Failures carry an error code:
| Code | Means |
|---|---|
| E_LOCATION_MANIFEST | A usage string is missing from the config |
| E_LOCATION_BACKGROUND | Asked while the app was not in front |
| E_LOCATION_PERMISSION | Permission is missing or was removed |
| E_LOCATION_UNAVAILABLE | No fix, or location services are off |
| E_LOCATION_GEOCODE | The geocoder failed |
| iOS | Android | Web | |
|---|---|---|---|
| Source | Core Location | GPS and network | Browser |
| Reuses a fix under 30s | During a watch | Yes | Yes |
| One fix gives up | System decides | After 30s | After 10s |
| Background | Status bar indicator | Notification | No |
| Geocoding | Apple | Device geocoder | Empty |
| Restricted status | Yes | Never | Never |
requestAlwaysAuthorization
prompts only when nothing has been decided. Once people chose While
Using, it resolves whenInUse, and they upgrade in Settings. It needs
location.alwaysAndWhenInUse on iOS and location.background on
Android, where it asks for the foreground grant and then the background
one.E_LOCATION_BACKGROUND, and without
location.background it sends E_LOCATION_MANIFEST. After a relaunch,
start the watch again yourself.accuracy with what your feature needs.notDetermined until the browser
answers a permission query.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.
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.
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.