Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ import Geolocation from '@react-native-community/geolocation';
Geolocation.getCurrentPosition(info => console.log(info));
```

Check out the [example project](example) for more examples.
Check out the [example project](example) for more examples. The example app
also provides a **Logging** section that records `watchPosition` updates into
timestamped CSV files inside `/storage/Android/data/<app-name>/files/` (or the
platform-equivalent app directory) and offers a shortcut to clear previously
recorded logs on the device.

## Methods

Expand Down
1 change: 1 addition & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './index.tsx';
1 change: 1 addition & 0 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function ExampleApp() {
<Stack.Screen name="Home" component={Screens.HomeScreen} />
<Stack.Screen name="Examples" component={Screens.ExamplesScreen} />
<Stack.Screen name="TestCases" component={Screens.TestCasesScreen} />
<Stack.Screen name="Logging" component={Screens.LoggingScreen} />
</Stack.Navigator>
</NavigationContainer>
);
Expand Down
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"react": "18.2.0",
"react-native": "0.74.2",
"react-native-background-timer": "^2.4.1",
"react-native-fs": "^2.20.0",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.32.0"
},
Expand All @@ -28,4 +29,4 @@
"preset": "react-native"
},
"packageManager": "[email protected]"
}
}
228 changes: 228 additions & 0 deletions example/src/components/WatchOptionsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/**
* Copyright (c) React Native Community
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

'use strict';

import React from 'react';
import {
StyleSheet,
View,
Text,
Switch,
TextInput,
Platform,
} from 'react-native';
import type { GeolocationOptions } from '@react-native-community/geolocation';

export const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
export const DEFAULT_DISTANCE_FILTER_M = 100;

export type WatchOptionFormValues = {
enableHighAccuracy: boolean;
timeout: string;
maximumAge: string;
distanceFilter: string;
useSignificantChanges: boolean;
interval: string;
fastestInterval: string;
};

export const initialWatchOptionValues: WatchOptionFormValues = {
enableHighAccuracy: false,
timeout: '',
maximumAge: '',
distanceFilter: '',
useSignificantChanges: false,
interval: '',
fastestInterval: '',
};

const parseNumber = (value: string) => {
if (value.trim().length === 0) {
return undefined;
}

const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
};

export const buildWatchOptions = (
values: WatchOptionFormValues
): GeolocationOptions => {
const options: GeolocationOptions = {};

if (values.enableHighAccuracy) {
options.enableHighAccuracy = true;
}

const timeoutValue = parseNumber(values.timeout);
if (timeoutValue !== undefined) {
options.timeout = timeoutValue;
}

const maximumAgeValue = parseNumber(values.maximumAge);
if (maximumAgeValue !== undefined) {
options.maximumAge = maximumAgeValue;
}

const distanceFilterValue = parseNumber(values.distanceFilter);
if (distanceFilterValue !== undefined) {
options.distanceFilter = distanceFilterValue;
}

if (Platform.OS === 'ios') {
options.useSignificantChanges = values.useSignificantChanges;
}

if (Platform.OS === 'android') {
const intervalValue = parseNumber(values.interval);
if (intervalValue !== undefined) {
options.interval = intervalValue;
}

const fastestIntervalValue = parseNumber(values.fastestInterval);
if (fastestIntervalValue !== undefined) {
options.fastestInterval = fastestIntervalValue;
}
}

return options;
};

export const buildCurrentPositionOptions = (
values: WatchOptionFormValues
): GeolocationOptions => {
const options: GeolocationOptions = {};

if (values.enableHighAccuracy) {
options.enableHighAccuracy = true;
}

const timeoutValue = parseNumber(values.timeout);
if (timeoutValue !== undefined) {
options.timeout = timeoutValue;
}

const maximumAgeValue = parseNumber(values.maximumAge);
if (maximumAgeValue !== undefined) {
options.maximumAge = maximumAgeValue;
}

return options;
};

type Props = {
values: WatchOptionFormValues;
onChange: <T extends keyof WatchOptionFormValues>(
field: T,
value: WatchOptionFormValues[T]
) => void;
};

export function WatchOptionsForm({ values, onChange }: Props) {
return (
<>
<View style={styles.row}>
<Text style={styles.label}>High accuracy (default: off)</Text>
<Switch
value={values.enableHighAccuracy}
onValueChange={(next) => onChange('enableHighAccuracy', next)}
/>
</View>
<View style={styles.row}>
<Text style={styles.label}>
Timeout (ms · default: {DEFAULT_TIMEOUT_MS})
</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={values.timeout}
onChangeText={(next) => onChange('timeout', next)}
placeholder={`${DEFAULT_TIMEOUT_MS}`}
/>
</View>
<View style={styles.row}>
<Text style={styles.label}>Maximum age (ms · default: Infinity)</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={values.maximumAge}
onChangeText={(next) => onChange('maximumAge', next)}
placeholder="Infinity"
/>
</View>
<View style={styles.row}>
<Text style={styles.label}>
Distance filter (m · default: {DEFAULT_DISTANCE_FILTER_M})
</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={values.distanceFilter}
onChangeText={(next) => onChange('distanceFilter', next)}
placeholder={`${DEFAULT_DISTANCE_FILTER_M}`}
/>
</View>
{Platform.OS === 'ios' && (
<View style={styles.row}>
<Text style={styles.label}>Use significant changes (default: false)</Text>
<Switch
value={values.useSignificantChanges}
onValueChange={(next) => onChange('useSignificantChanges', next)}
/>
</View>
)}
{Platform.OS === 'android' && (
<>
<View style={styles.row}>
<Text style={styles.label}>Interval (ms · default: system)</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={values.interval}
onChangeText={(next) => onChange('interval', next)}
placeholder="System"
/>
</View>
<View style={styles.row}>
<Text style={styles.label}>Fastest interval (ms · default: system)</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={values.fastestInterval}
onChangeText={(next) => onChange('fastestInterval', next)}
placeholder="System"
/>
</View>
</>
)}
</>
);
}

const styles = StyleSheet.create({
row: {
marginBottom: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
flex: 1,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
minWidth: 100,
textAlign: 'right',
},
});
74 changes: 66 additions & 8 deletions example/src/examples/WatchPosition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,43 @@

import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Alert, Button } from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import Geolocation, { type GeolocationResponse } from '@react-native-community/geolocation';
import {
WatchOptionsForm,
buildCurrentPositionOptions,
buildWatchOptions,
initialWatchOptionValues,
type WatchOptionFormValues,
} from '../components/WatchOptionsForm';

export default function WatchPositionExample() {
const [formValues, setFormValues] =
useState<WatchOptionFormValues>(initialWatchOptionValues);
const [position, setPosition] =
useState<GeolocationResponse | null>(null);
const [subscriptionId, setSubscriptionId] = useState<number | null>(null);

const watchPosition = () => {
try {
const currentOptions = buildCurrentPositionOptions(formValues);
console.log('watchPosition.getCurrentPositionOptions', currentOptions);
Geolocation.getCurrentPosition(
(nextPosition) => {
setPosition(nextPosition);
},
(error) => Alert.alert('GetCurrentPosition Error', JSON.stringify(error)),
currentOptions
);

const watchOptions = buildWatchOptions(formValues);
console.log('watchPosition.startOptions', watchOptions);
const watchID = Geolocation.watchPosition(
(position) => {
console.log('watchPosition', JSON.stringify(position));
setPosition(JSON.stringify(position));
(nextPosition) => {
console.log('watchPosition', JSON.stringify(nextPosition));
setPosition(nextPosition);
},
(error) => Alert.alert('WatchPosition Error', JSON.stringify(error))
(error) => Alert.alert('WatchPosition Error', JSON.stringify(error)),
watchOptions
);
setSubscriptionId(watchID);
} catch (error) {
Expand All @@ -35,8 +61,6 @@ export default function WatchPositionExample() {
setPosition(null);
};

const [position, setPosition] = useState<string | null>(null);
const [subscriptionId, setSubscriptionId] = useState<number | null>(null);
useEffect(() => {
return () => {
clearWatch();
Expand All @@ -46,10 +70,22 @@ export default function WatchPositionExample() {

return (
<View>
<WatchOptionsForm
values={formValues}
onChange={(field, value) =>
setFormValues((prev) => ({ ...prev, [field]: value }))
}
/>
<Text>
<Text style={styles.title}>Last position: </Text>
{position || 'unknown'}
{position ? JSON.stringify(position) : 'unknown'}
</Text>
{position && (
<Text style={styles.caption}>
Position timestamp:{' '}
{new Date(position.timestamp).toLocaleTimeString()}
</Text>
)}
{subscriptionId !== null ? (
<Button title="Clear Watch" onPress={clearWatch} />
) : (
Expand All @@ -63,4 +99,26 @@ const styles = StyleSheet.create({
title: {
fontWeight: '500',
},
row: {
marginBottom: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
flex: 1,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
minWidth: 100,
textAlign: 'right',
},
caption: {
marginBottom: 12,
color: '#555',
},
});
3 changes: 2 additions & 1 deletion example/src/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const examples = [
{
id: 'watchPosition',
title: 'watchPosition() / clearWatch()',
description: 'Start / stop watching location changes',
description:
'Start / stop watching location changes and tweak watch options',
render() {
return <WatchPosition />;
},
Expand Down
Loading