Skip to content

Commit e033903

Browse files
committed
Example app: Add Logging section to collect positions in CSV format. Add options configuration in Examples watchPosition
1 parent 17b38b4 commit e033903

File tree

12 files changed

+26787
-18577
lines changed

12 files changed

+26787
-18577
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ import Geolocation from '@react-native-community/geolocation';
144144
Geolocation.getCurrentPosition(info => console.log(info));
145145
```
146146

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

149153
## Methods
150154

example/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './index.tsx';

example/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function ExampleApp() {
2424
<Stack.Screen name="Home" component={Screens.HomeScreen} />
2525
<Stack.Screen name="Examples" component={Screens.ExamplesScreen} />
2626
<Stack.Screen name="TestCases" component={Screens.TestCasesScreen} />
27+
<Stack.Screen name="Logging" component={Screens.LoggingScreen} />
2728
</Stack.Navigator>
2829
</NavigationContainer>
2930
);

example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"react": "18.2.0",
1717
"react-native": "0.74.2",
1818
"react-native-background-timer": "^2.4.1",
19+
"react-native-fs": "^2.20.0",
1920
"react-native-safe-area-context": "4.10.5",
2021
"react-native-screens": "3.32.0"
2122
},
@@ -28,4 +29,4 @@
2829
"preset": "react-native"
2930
},
3031
"packageManager": "yarn@3.6.4"
31-
}
32+
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* Copyright (c) React Native Community
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
'use strict';
11+
12+
import React from 'react';
13+
import {
14+
StyleSheet,
15+
View,
16+
Text,
17+
Switch,
18+
TextInput,
19+
Platform,
20+
} from 'react-native';
21+
import type { GeolocationOptions } from '@react-native-community/geolocation';
22+
23+
export const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
24+
export const DEFAULT_DISTANCE_FILTER_M = 100;
25+
26+
export type WatchOptionFormValues = {
27+
enableHighAccuracy: boolean;
28+
timeout: string;
29+
maximumAge: string;
30+
distanceFilter: string;
31+
useSignificantChanges: boolean;
32+
interval: string;
33+
fastestInterval: string;
34+
};
35+
36+
export const initialWatchOptionValues: WatchOptionFormValues = {
37+
enableHighAccuracy: false,
38+
timeout: '',
39+
maximumAge: '',
40+
distanceFilter: '',
41+
useSignificantChanges: false,
42+
interval: '',
43+
fastestInterval: '',
44+
};
45+
46+
const parseNumber = (value: string) => {
47+
if (value.trim().length === 0) {
48+
return undefined;
49+
}
50+
51+
const parsed = Number(value);
52+
return Number.isFinite(parsed) ? parsed : undefined;
53+
};
54+
55+
export const buildWatchOptions = (
56+
values: WatchOptionFormValues
57+
): GeolocationOptions => {
58+
const options: GeolocationOptions = {};
59+
60+
if (values.enableHighAccuracy) {
61+
options.enableHighAccuracy = true;
62+
}
63+
64+
const timeoutValue = parseNumber(values.timeout);
65+
if (timeoutValue !== undefined) {
66+
options.timeout = timeoutValue;
67+
}
68+
69+
const maximumAgeValue = parseNumber(values.maximumAge);
70+
if (maximumAgeValue !== undefined) {
71+
options.maximumAge = maximumAgeValue;
72+
}
73+
74+
const distanceFilterValue = parseNumber(values.distanceFilter);
75+
if (distanceFilterValue !== undefined) {
76+
options.distanceFilter = distanceFilterValue;
77+
}
78+
79+
if (Platform.OS === 'ios') {
80+
options.useSignificantChanges = values.useSignificantChanges;
81+
}
82+
83+
if (Platform.OS === 'android') {
84+
const intervalValue = parseNumber(values.interval);
85+
if (intervalValue !== undefined) {
86+
options.interval = intervalValue;
87+
}
88+
89+
const fastestIntervalValue = parseNumber(values.fastestInterval);
90+
if (fastestIntervalValue !== undefined) {
91+
options.fastestInterval = fastestIntervalValue;
92+
}
93+
}
94+
95+
return options;
96+
};
97+
98+
export const buildCurrentPositionOptions = (
99+
values: WatchOptionFormValues
100+
): GeolocationOptions => {
101+
const options: GeolocationOptions = {};
102+
103+
if (values.enableHighAccuracy) {
104+
options.enableHighAccuracy = true;
105+
}
106+
107+
const timeoutValue = parseNumber(values.timeout);
108+
if (timeoutValue !== undefined) {
109+
options.timeout = timeoutValue;
110+
}
111+
112+
const maximumAgeValue = parseNumber(values.maximumAge);
113+
if (maximumAgeValue !== undefined) {
114+
options.maximumAge = maximumAgeValue;
115+
}
116+
117+
return options;
118+
};
119+
120+
type Props = {
121+
values: WatchOptionFormValues;
122+
onChange: <T extends keyof WatchOptionFormValues>(
123+
field: T,
124+
value: WatchOptionFormValues[T]
125+
) => void;
126+
};
127+
128+
export function WatchOptionsForm({ values, onChange }: Props) {
129+
return (
130+
<>
131+
<View style={styles.row}>
132+
<Text style={styles.label}>High accuracy (default: off)</Text>
133+
<Switch
134+
value={values.enableHighAccuracy}
135+
onValueChange={(next) => onChange('enableHighAccuracy', next)}
136+
/>
137+
</View>
138+
<View style={styles.row}>
139+
<Text style={styles.label}>
140+
Timeout (ms · default: {DEFAULT_TIMEOUT_MS})
141+
</Text>
142+
<TextInput
143+
style={styles.input}
144+
keyboardType="numeric"
145+
value={values.timeout}
146+
onChangeText={(next) => onChange('timeout', next)}
147+
placeholder={`${DEFAULT_TIMEOUT_MS}`}
148+
/>
149+
</View>
150+
<View style={styles.row}>
151+
<Text style={styles.label}>Maximum age (ms · default: Infinity)</Text>
152+
<TextInput
153+
style={styles.input}
154+
keyboardType="numeric"
155+
value={values.maximumAge}
156+
onChangeText={(next) => onChange('maximumAge', next)}
157+
placeholder="Infinity"
158+
/>
159+
</View>
160+
<View style={styles.row}>
161+
<Text style={styles.label}>
162+
Distance filter (m · default: {DEFAULT_DISTANCE_FILTER_M})
163+
</Text>
164+
<TextInput
165+
style={styles.input}
166+
keyboardType="numeric"
167+
value={values.distanceFilter}
168+
onChangeText={(next) => onChange('distanceFilter', next)}
169+
placeholder={`${DEFAULT_DISTANCE_FILTER_M}`}
170+
/>
171+
</View>
172+
{Platform.OS === 'ios' && (
173+
<View style={styles.row}>
174+
<Text style={styles.label}>Use significant changes (default: false)</Text>
175+
<Switch
176+
value={values.useSignificantChanges}
177+
onValueChange={(next) => onChange('useSignificantChanges', next)}
178+
/>
179+
</View>
180+
)}
181+
{Platform.OS === 'android' && (
182+
<>
183+
<View style={styles.row}>
184+
<Text style={styles.label}>Interval (ms · default: system)</Text>
185+
<TextInput
186+
style={styles.input}
187+
keyboardType="numeric"
188+
value={values.interval}
189+
onChangeText={(next) => onChange('interval', next)}
190+
placeholder="System"
191+
/>
192+
</View>
193+
<View style={styles.row}>
194+
<Text style={styles.label}>Fastest interval (ms · default: system)</Text>
195+
<TextInput
196+
style={styles.input}
197+
keyboardType="numeric"
198+
value={values.fastestInterval}
199+
onChangeText={(next) => onChange('fastestInterval', next)}
200+
placeholder="System"
201+
/>
202+
</View>
203+
</>
204+
)}
205+
</>
206+
);
207+
}
208+
209+
const styles = StyleSheet.create({
210+
row: {
211+
marginBottom: 12,
212+
flexDirection: 'row',
213+
alignItems: 'center',
214+
justifyContent: 'space-between',
215+
},
216+
label: {
217+
flex: 1,
218+
},
219+
input: {
220+
borderWidth: 1,
221+
borderColor: '#ccc',
222+
paddingHorizontal: 8,
223+
paddingVertical: 4,
224+
borderRadius: 4,
225+
minWidth: 100,
226+
textAlign: 'right',
227+
},
228+
});

example/src/examples/WatchPosition.tsx

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,43 @@
1111

1212
import React, { useState, useEffect } from 'react';
1313
import { StyleSheet, Text, View, Alert, Button } from 'react-native';
14-
import Geolocation from '@react-native-community/geolocation';
14+
import Geolocation, { type GeolocationResponse } from '@react-native-community/geolocation';
15+
import {
16+
WatchOptionsForm,
17+
buildCurrentPositionOptions,
18+
buildWatchOptions,
19+
initialWatchOptionValues,
20+
type WatchOptionFormValues,
21+
} from '../components/WatchOptionsForm';
1522

1623
export default function WatchPositionExample() {
24+
const [formValues, setFormValues] =
25+
useState<WatchOptionFormValues>(initialWatchOptionValues);
26+
const [position, setPosition] =
27+
useState<GeolocationResponse | null>(null);
28+
const [subscriptionId, setSubscriptionId] = useState<number | null>(null);
29+
1730
const watchPosition = () => {
1831
try {
32+
const currentOptions = buildCurrentPositionOptions(formValues);
33+
console.log('watchPosition.getCurrentPositionOptions', currentOptions);
34+
Geolocation.getCurrentPosition(
35+
(nextPosition) => {
36+
setPosition(nextPosition);
37+
},
38+
(error) => Alert.alert('GetCurrentPosition Error', JSON.stringify(error)),
39+
currentOptions
40+
);
41+
42+
const watchOptions = buildWatchOptions(formValues);
43+
console.log('watchPosition.startOptions', watchOptions);
1944
const watchID = Geolocation.watchPosition(
20-
(position) => {
21-
console.log('watchPosition', JSON.stringify(position));
22-
setPosition(JSON.stringify(position));
45+
(nextPosition) => {
46+
console.log('watchPosition', JSON.stringify(nextPosition));
47+
setPosition(nextPosition);
2348
},
24-
(error) => Alert.alert('WatchPosition Error', JSON.stringify(error))
49+
(error) => Alert.alert('WatchPosition Error', JSON.stringify(error)),
50+
watchOptions
2551
);
2652
setSubscriptionId(watchID);
2753
} catch (error) {
@@ -35,8 +61,6 @@ export default function WatchPositionExample() {
3561
setPosition(null);
3662
};
3763

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

4771
return (
4872
<View>
73+
<WatchOptionsForm
74+
values={formValues}
75+
onChange={(field, value) =>
76+
setFormValues((prev) => ({ ...prev, [field]: value }))
77+
}
78+
/>
4979
<Text>
5080
<Text style={styles.title}>Last position: </Text>
51-
{position || 'unknown'}
81+
{position ? JSON.stringify(position) : 'unknown'}
5282
</Text>
83+
{position && (
84+
<Text style={styles.caption}>
85+
Position timestamp:{' '}
86+
{new Date(position.timestamp).toLocaleTimeString()}
87+
</Text>
88+
)}
5389
{subscriptionId !== null ? (
5490
<Button title="Clear Watch" onPress={clearWatch} />
5591
) : (
@@ -63,4 +99,26 @@ const styles = StyleSheet.create({
6399
title: {
64100
fontWeight: '500',
65101
},
102+
row: {
103+
marginBottom: 12,
104+
flexDirection: 'row',
105+
alignItems: 'center',
106+
justifyContent: 'space-between',
107+
},
108+
label: {
109+
flex: 1,
110+
},
111+
input: {
112+
borderWidth: 1,
113+
borderColor: '#ccc',
114+
paddingHorizontal: 8,
115+
paddingVertical: 4,
116+
borderRadius: 4,
117+
minWidth: 100,
118+
textAlign: 'right',
119+
},
120+
caption: {
121+
marginBottom: 12,
122+
color: '#555',
123+
},
66124
});

example/src/examples/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const examples = [
1414
{
1515
id: 'watchPosition',
1616
title: 'watchPosition() / clearWatch()',
17-
description: 'Start / stop watching location changes',
17+
description:
18+
'Start / stop watching location changes and tweak watch options',
1819
render() {
1920
return <WatchPosition />;
2021
},

0 commit comments

Comments
 (0)