iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Location

Accessing the user’s location on React Native: Expo Location for managed projects, react-native-geolocation-service for bare RN. Handle permissions with a rationale, request high-accuracy only when needed, and design for permission denial since users frequently say no.

Permissions, watch, background, accuracy

EXAMPLE
// 1) Install — Expo
// npx expo install expo-location
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';
import { Text, Button, View, Platform } from 'react-native';

// 2) Request permission + read once
export function CurrentLocation() {
    const [location, setLocation] = useState(null);
    const [error, setError] = useState(null);

    async function request() {
        const { status } = await Location.requestForegroundPermissionsAsync();
        if (status !== 'granted') {
            setError('Permission denied');
            return;
        }
        const pos = await Location.getCurrentPositionAsync({
            accuracy: Location.Accuracy.Balanced,
        });
        setLocation(pos);
    }

    return (
        <View>
            <Button title="Get location" onPress={request} />
            {error && <Text>{error}</Text>}
            {location && (
                <Text>
                    {location.coords.latitude.toFixed(4)},
                    {location.coords.longitude.toFixed(4)}
                    (±{location.coords.accuracy?.toFixed(0)}m)
                </Text>
            )}
        </View>
    );
}

// 3) Accuracy levels
// Location.Accuracy.Lowest         ≈ 3 km     (cheapest)
// Location.Accuracy.Low            ≈ 1 km
// Location.Accuracy.Balanced       ≈ 100 m
// Location.Accuracy.High           ≈ 10 m
// Location.Accuracy.Highest        ≈ best available
// Location.Accuracy.BestForNavigation  (use only for active turn-by-turn)

// Higher accuracy = more battery + GPS chip on.

// 4) Watch position changes
import { useEffect, useRef } from 'react';

export function WatchLocation() {
    const subscription = useRef(null);

    useEffect(() => {
        (async () => {
            const { status } = await Location.requestForegroundPermissionsAsync();
            if (status !== 'granted') return;

            subscription.current = await Location.watchPositionAsync(
                {
                    accuracy: Location.Accuracy.Balanced,
                    timeInterval: 5000,             // ms between updates
                    distanceInterval: 10,            // metres
                },
                (position) => {
                    console.log(position.coords);
                },
            );
        })();
        return () => { subscription.current?.remove(); };
    }, []);

    return null;
}

// 5) Permission flow with rationale
async function requestWithRationale() {
    const { status: existing } = await Location.getForegroundPermissionsAsync();
    if (existing === 'granted') return true;

    // Show your own rationale UI before requesting:
    const userAccepted = await showRationaleModal();
    if (!userAccepted) return false;

    const { status } = await Location.requestForegroundPermissionsAsync();
    if (status !== 'granted') {
        // User denied. On second request, OS may not show prompt — guide to Settings:
        Linking.openSettings();
        return false;
    }
    return true;
}

// Users who decline twice typically can't be re-prompted by the system — direct to Settings.

// 6) Reverse geocoding — coords → address
const [address] = await Location.reverseGeocodeAsync({
    latitude: pos.coords.latitude,
    longitude: pos.coords.longitude,
});
console.log(`${address.city}, ${address.region}, ${address.country}`);

// Forward geocoding — address → coords
const results = await Location.geocodeAsync('Sydney Opera House, Australia');
console.log(results[0]);   // { latitude, longitude, ... }

// 7) Distance calculation — Haversine
function haversine(a, b) {
    const R = 6371e3;
    const toRad = (d) => (d * Math.PI) / 180;
    const dLat = toRad(b.lat - a.lat);
    const dLon = toRad(b.lng - a.lng);
    const s = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLon / 2) ** 2;
    return 2 * R * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s));
}

haversine({ lat: -33.86, lng: 151.21 }, { lat: -33.87, lng: 151.22 });   // metres

// 8) Background location (USE SPARINGLY — review-board sensitive)
import * as TaskManager from 'expo-task-manager';

TaskManager.defineTask('BACKGROUND_LOCATION', async ({ data, error }) => {
    if (error) return console.error(error);
    if (data) {
        const { locations } = data;
        // Send to server / store locally
    }
});

async function startBackground() {
    const { status } = await Location.requestBackgroundPermissionsAsync();
    if (status !== 'granted') return;

    await Location.startLocationUpdatesAsync('BACKGROUND_LOCATION', {
        accuracy: Location.Accuracy.Balanced,
        timeInterval: 60_000,
        distanceInterval: 50,
        foregroundService: {
            notificationTitle: 'Tracking your run',
            notificationBody:  'We are recording your location',
        },
    });
}

// Justify the permission to users + Apple/Google reviewers. Misuse → app rejection.

// 9) Geofencing
import * as Location from 'expo-location';

TaskManager.defineTask('GEOFENCE', ({ data, error }) => {
    if (error) return;
    const { eventType, region } = data;
    if (eventType === Location.GeofencingEventType.Enter) {
        console.log('entered', region.identifier);
    }
});

await Location.startGeofencingAsync('GEOFENCE', [
    { identifier: 'home',  latitude: -33.86, longitude: 151.21, radius: 200 },
    { identifier: 'gym',   latitude: -33.87, longitude: 151.23, radius: 100 },
]);

// 10) iOS permission strings — Info.plist
// <key>NSLocationWhenInUseUsageDescription</key>
// <string>We use your location to find nearby venues</string>
// <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
// <string>We track your location for delivery updates</string>
// <key>NSLocationAlwaysUsageDescription</key>
// <string>Background updates for safety alerts</string>

// Expo: app.json
{
    "expo": {
        "plugins": [[
            "expo-location",
            {
                "locationAlwaysAndWhenInUsePermission": "We track your location for delivery updates.",
            },
        ]],
    },
}

// 11) Android manifest
// <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
// <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
// <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

// 12) Approximate vs precise (iOS 14+ / Android 12+)
// • User can grant 'approximate only' → ±5km accuracy
// • Detect: pos.coords.accuracy > 1000 likely means approximate
// • Provide downgrade UX for users who don't want precise sharing

// 13) Map UI — react-native-maps
// npm install react-native-maps
import MapView, { Marker } from 'react-native-maps';

<MapView style={{ flex: 1 }} initialRegion={{ latitude: -33.86, longitude: 151.21, latitudeDelta: 0.01, longitudeDelta: 0.01 }}>
    <Marker coordinate={{ latitude: -33.86, longitude: 151.21 }} title="Sydney" />
</MapView>

// 14) Common bugs
// • Forgetting iOS Info.plist strings → silent denial / app crash
// • Polling location every second → battery drain
// • Not handling permission denial → blank UI
// • Web build but no HTTPS → 'User denied geolocation' from Chrome (HTTPS required)
// • Background mode declared but no actual background updates → review board rejection
// • Not asking 'when in use' before 'always' → Android rejects the flow
// • Caching last location forever → user moves; show stale data
// • Calling requestForegroundPermissions inside render → infinite re-renders
// • Coords from emulator are usually (0, 0) — test on real device
// • Reverse geocoding rate-limited → cache results, throttle calls

Why it matters

Use expo-location (managed) or react-native-geolocation-service (bare). Default to Accuracy.Balanced + watchPositionAsync; tighten to BestForNavigation only when driving turn-by-turn. Show a rationale before requesting, declare every usage string up front, and resist background tracking unless your feature genuinely needs it — app stores reject misuse.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import * as Location from 'expo-location';
await Location.requestForegroundPermissionsAsync();
const { coords } = await Location.getCurrentPositionAsync();
console.log(coords.latitude, coords.longitude);
Try it Yourself »

Discussion

Loading…