2025-03-28 04:59:59 +00:00
|
|
|
/**
|
|
|
|
* Sample React Native App
|
|
|
|
* https://github.com/facebook/react-native
|
|
|
|
*
|
2025-03-31 05:11:57 +00:00
|
|
|
* Generated with the TypeScript template
|
|
|
|
* https://github.com/react-native-community/react-native-template-typescript
|
|
|
|
*
|
2025-03-28 04:59:59 +00:00
|
|
|
* @format
|
|
|
|
*/
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
import {
|
|
|
|
SafeAreaView,
|
|
|
|
ScrollView,
|
|
|
|
StatusBar,
|
|
|
|
StyleSheet,
|
|
|
|
Text,
|
|
|
|
View,
|
2025-03-31 05:11:57 +00:00
|
|
|
Switch,
|
|
|
|
Button,
|
|
|
|
Alert,
|
|
|
|
PermissionsAndroid,
|
|
|
|
// ToastAndroid,
|
|
|
|
NativeModules,
|
2025-03-28 04:59:59 +00:00
|
|
|
} from 'react-native';
|
|
|
|
|
2025-03-31 05:11:57 +00:00
|
|
|
import BackgroundFetch from 'react-native-background-fetch';
|
|
|
|
import Geolocation, {
|
|
|
|
GeolocationError,
|
|
|
|
GeolocationResponse,
|
|
|
|
} from '@react-native-community/geolocation';
|
|
|
|
|
|
|
|
const {ForegroundHeadlessModule} = NativeModules;
|
|
|
|
|
|
|
|
const Colors = {
|
|
|
|
gold: '#fedd1e',
|
|
|
|
black: '#000',
|
|
|
|
white: '#fff',
|
|
|
|
lightGrey: '#ccc',
|
|
|
|
blue: '#337AB7',
|
|
|
|
brick: '#973920',
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Util class for handling fetch-event peristence in AsyncStorage.
|
|
|
|
import Event from './src/Event';
|
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
|
|
|
|
Geolocation.setRNConfiguration({
|
|
|
|
skipPermissionRequests: true,
|
|
|
|
locationProvider: 'auto',
|
|
|
|
});
|
|
|
|
|
|
|
|
const requestPermissions = async () => {
|
|
|
|
try {
|
|
|
|
const granted = await PermissionsAndroid.request(
|
|
|
|
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
|
|
{
|
|
|
|
title: 'Need permission to access location',
|
|
|
|
message:
|
|
|
|
'Location access is needed ' + 'so we can track your location!',
|
|
|
|
buttonNegative: 'Cancel',
|
|
|
|
buttonPositive: 'OK',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
const granted2 = await PermissionsAndroid.request(
|
|
|
|
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
|
|
|
|
{
|
|
|
|
title: 'Need permission to access location',
|
|
|
|
message:
|
|
|
|
'Location access is needed ' + 'so we can track your location!',
|
|
|
|
buttonNegative: 'Cancel',
|
|
|
|
buttonPositive: 'OK',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (
|
|
|
|
granted === PermissionsAndroid.RESULTS.GRANTED &&
|
|
|
|
granted2 === PermissionsAndroid.RESULTS.GRANTED
|
|
|
|
) {
|
|
|
|
console.log('You can use the location');
|
|
|
|
} else {
|
|
|
|
console.log('Location permission denied');
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.warn(err);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function geoLocationPromise(): Promise<GeolocationResponse | GeolocationError> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
Geolocation.getCurrentPosition(
|
|
|
|
info => {
|
|
|
|
console.log(info);
|
|
|
|
resolve(info);
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
console.log(error);
|
|
|
|
reject(error);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
timeout: 20000,
|
|
|
|
maximumAge: 0,
|
|
|
|
// enableHighAccuracy: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2025-03-28 04:59:59 +00:00
|
|
|
}
|
|
|
|
|
2025-03-31 05:11:57 +00:00
|
|
|
const App = () => {
|
|
|
|
const [enabled, setEnabled] = React.useState(false);
|
|
|
|
const [status, setStatus] = React.useState(-1);
|
|
|
|
const [events, setEvents] = React.useState<Event[]>([]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
async function asyncTask() {
|
|
|
|
await requestPermissions();
|
|
|
|
initBackgroundFetch();
|
|
|
|
loadEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
asyncTask();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
/// Configure BackgroundFetch.
|
|
|
|
///
|
|
|
|
const initBackgroundFetch = async () => {
|
|
|
|
const status: number = await BackgroundFetch.configure(
|
|
|
|
{
|
|
|
|
minimumFetchInterval: 15, // <-- minutes (15 is minimum allowed)
|
|
|
|
stopOnTerminate: false,
|
|
|
|
enableHeadless: true,
|
|
|
|
startOnBoot: false,
|
|
|
|
// Android options
|
|
|
|
forceAlarmManager: false, // <-- Set true to bypass JobScheduler.
|
|
|
|
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_NONE, // Default
|
|
|
|
requiresCharging: false, // Default
|
|
|
|
requiresDeviceIdle: false, // Default
|
|
|
|
requiresBatteryNotLow: false, // Default
|
|
|
|
requiresStorageNotLow: false, // Default
|
|
|
|
},
|
|
|
|
async (taskId: string) => {
|
|
|
|
console.log('[BackgroundFetch] taskId', taskId);
|
|
|
|
|
|
|
|
if (taskId === 'react-native-background-fetch') {
|
|
|
|
// console.log('[BackgroundFetch] taskId', taskId);
|
|
|
|
const event = await Event.create(taskId, false);
|
|
|
|
// Update state.
|
|
|
|
setEvents(prev => [...prev, event]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (taskId === 'com.transistorsoft.customtask') {
|
|
|
|
// Geolocation.getCurrentPosition(
|
|
|
|
// async info => {
|
|
|
|
// console.log(info);
|
|
|
|
// const event = await Event.create(taskId, false, `lat-${info.coords.latitude};long-${info.coords.longitude}`);
|
|
|
|
// },
|
|
|
|
// err => console.log(err),
|
|
|
|
// {
|
|
|
|
// timeout: 10000,
|
|
|
|
// maximumAge: 10000,
|
|
|
|
// enableHighAccuracy: false,
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
try {
|
|
|
|
const locInfo = (await geoLocationPromise()) as GeolocationResponse;
|
|
|
|
const event = await Event.create(
|
|
|
|
taskId,
|
|
|
|
false,
|
|
|
|
`lat-${locInfo.coords.latitude};long-${locInfo.coords.longitude}`,
|
|
|
|
);
|
|
|
|
// Update state.
|
|
|
|
setEvents(prev => [...prev, event]);
|
|
|
|
} catch (error) {
|
|
|
|
const g = error as GeolocationError;
|
|
|
|
if (g.message) {
|
|
|
|
const event = await Event.create(taskId, false, `${g.message}`);
|
|
|
|
// Update state.
|
|
|
|
setEvents(prev => [...prev, event]);
|
|
|
|
} else {
|
|
|
|
const event = await Event.create(taskId, false);
|
|
|
|
// Update state.
|
|
|
|
setEvents(prev => [...prev, event]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// console.log('Running [customtask] here!');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finish.
|
|
|
|
BackgroundFetch.finish(taskId);
|
|
|
|
},
|
|
|
|
(taskId: string) => {
|
|
|
|
// Oh No! Our task took too long to complete and the OS has signalled
|
|
|
|
// that this task must be finished immediately.
|
|
|
|
console.log('[Fetch] TIMEOUT taskId:', taskId);
|
|
|
|
BackgroundFetch.finish(taskId);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
setStatus(status);
|
|
|
|
setEnabled(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Load persisted events from AsyncStorage.
|
|
|
|
///
|
|
|
|
const loadEvents = () => {
|
|
|
|
Event.all()
|
|
|
|
.then(data => {
|
|
|
|
setEvents(data as Event[]);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
Alert.alert('Error', 'Failed to load data from AsyncStorage: ' + error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Toggle BackgroundFetch ON/OFF
|
|
|
|
///
|
|
|
|
const onClickToggleEnabled = (value: boolean) => {
|
|
|
|
setEnabled(value);
|
2025-03-28 04:59:59 +00:00
|
|
|
|
2025-03-31 05:11:57 +00:00
|
|
|
if (value) {
|
|
|
|
BackgroundFetch.start();
|
|
|
|
} else {
|
|
|
|
BackgroundFetch.stop();
|
|
|
|
}
|
2025-03-28 04:59:59 +00:00
|
|
|
};
|
|
|
|
|
2025-03-31 05:11:57 +00:00
|
|
|
/// [Status] button handler.
|
|
|
|
///
|
|
|
|
const onClickStatus = () => {
|
|
|
|
BackgroundFetch.status().then((status: number) => {
|
|
|
|
let statusConst = '';
|
|
|
|
switch (status) {
|
|
|
|
case BackgroundFetch.STATUS_AVAILABLE:
|
|
|
|
statusConst = 'STATUS_AVAILABLE';
|
|
|
|
break;
|
|
|
|
case BackgroundFetch.STATUS_DENIED:
|
|
|
|
statusConst = 'STATUS_DENIED';
|
|
|
|
break;
|
|
|
|
case BackgroundFetch.STATUS_RESTRICTED:
|
|
|
|
statusConst = 'STATUS_RESTRICTED';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Alert.alert('BackgroundFetch.status()', `${statusConst} (${status})`);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/// [scheduleTask] button handler.
|
|
|
|
/// Schedules a custom-task to fire in 5000ms
|
|
|
|
///
|
|
|
|
const onClickScheduleTask = () => {
|
|
|
|
const delay = 600000;
|
|
|
|
BackgroundFetch.scheduleTask({
|
|
|
|
taskId: 'com.transistorsoft.customtask',
|
|
|
|
stopOnTerminate: false,
|
|
|
|
enableHeadless: true,
|
|
|
|
delay: delay,
|
|
|
|
forceAlarmManager: false,
|
|
|
|
periodic: true,
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
Alert.alert(
|
|
|
|
'scheduleTask',
|
|
|
|
'Scheduled task with delay: ' + delay + 'ms',
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
Alert.alert('scheduleTask ERROR', error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Clear the Events list.
|
|
|
|
///
|
|
|
|
const onClickClear = () => {
|
|
|
|
Event.destroyAll();
|
|
|
|
setEvents([]);
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Fetch events renderer.
|
|
|
|
///
|
|
|
|
const renderEvents = () => {
|
|
|
|
if (!events.length) {
|
|
|
|
return (
|
|
|
|
<Text style={{padding: 10, fontSize: 16}}>
|
|
|
|
Waiting for BackgroundFetch events...
|
|
|
|
</Text>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return events
|
|
|
|
.slice()
|
|
|
|
.reverse()
|
|
|
|
.map(event => (
|
|
|
|
<View key={event.key} style={styles.event}>
|
|
|
|
<View style={{flexDirection: 'row'}}>
|
|
|
|
<Text style={styles.taskId}>
|
|
|
|
{event.taskId} {event.isHeadless ? '[Headless]' : ''}
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
<Text style={styles.remark}>Remark - {event.location}</Text>
|
|
|
|
<Text style={styles.timestamp}>{event.timestamp}</Text>
|
|
|
|
</View>
|
|
|
|
));
|
|
|
|
};
|
|
|
|
|
|
|
|
async function onLocPress(): Promise<void> {
|
|
|
|
try {
|
|
|
|
// const locInfo = (await geoLocationPromise()) as GeolocationResponse;
|
|
|
|
// ToastAndroid.show(
|
|
|
|
// `lat-${locInfo.coords.latitude};long-${locInfo.coords.longitude}`,
|
|
|
|
// 5000,
|
|
|
|
// );
|
|
|
|
ForegroundHeadlessModule.startService();
|
|
|
|
console.log('Did it run?');
|
|
|
|
} catch (error) {
|
|
|
|
const g = error as GeolocationError;
|
|
|
|
if (g.message) {
|
|
|
|
console.log(g.message);
|
|
|
|
} else {
|
|
|
|
console.log(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onLocStop() {
|
|
|
|
ForegroundHeadlessModule.stopService();
|
|
|
|
AsyncStorage.getItem('watchId', (err, result) => {
|
|
|
|
if (err) {
|
|
|
|
console.log('Couldnt get WatchID', err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(result);
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
Geolocation.clearWatch(+result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-03-28 04:59:59 +00:00
|
|
|
return (
|
2025-03-31 05:11:57 +00:00
|
|
|
<SafeAreaView style={{flex: 1, backgroundColor: Colors.gold}}>
|
|
|
|
<StatusBar barStyle={'light-content'}></StatusBar>
|
|
|
|
<View style={styles.container}>
|
|
|
|
<View style={styles.toolbar}>
|
|
|
|
<Text style={styles.title}>BGFetch Demo</Text>
|
|
|
|
<Button title="Loc" onPress={onLocPress} />
|
|
|
|
<Button title="Stop" onPress={onLocStop} />
|
|
|
|
<Switch value={enabled} onValueChange={onClickToggleEnabled} />
|
2025-03-28 04:59:59 +00:00
|
|
|
</View>
|
2025-03-31 05:11:57 +00:00
|
|
|
<ScrollView
|
|
|
|
contentInsetAdjustmentBehavior="automatic"
|
|
|
|
style={styles.eventList}>
|
|
|
|
{renderEvents()}
|
|
|
|
</ScrollView>
|
|
|
|
<View style={styles.toolbar}>
|
|
|
|
<Button title={'status: ' + status} onPress={onClickStatus} />
|
|
|
|
<Text> </Text>
|
|
|
|
<Button title="scheduleTask" onPress={onClickScheduleTask} />
|
|
|
|
<View style={{flex: 1}} />
|
|
|
|
<Button title="clear" onPress={onClickClear} />
|
|
|
|
</View>
|
|
|
|
</View>
|
2025-03-28 04:59:59 +00:00
|
|
|
</SafeAreaView>
|
|
|
|
);
|
2025-03-31 05:11:57 +00:00
|
|
|
};
|
2025-03-28 04:59:59 +00:00
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2025-03-31 05:11:57 +00:00
|
|
|
container: {
|
|
|
|
flexDirection: 'column',
|
|
|
|
flex: 1,
|
2025-03-28 04:59:59 +00:00
|
|
|
},
|
2025-03-31 05:11:57 +00:00
|
|
|
title: {
|
2025-03-28 04:59:59 +00:00
|
|
|
fontSize: 24,
|
2025-03-31 05:11:57 +00:00
|
|
|
flex: 1,
|
|
|
|
fontWeight: 'bold',
|
|
|
|
color: Colors.black,
|
|
|
|
},
|
|
|
|
eventList: {
|
|
|
|
flex: 1,
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
},
|
|
|
|
event: {
|
|
|
|
padding: 10,
|
|
|
|
borderBottomWidth: 1,
|
|
|
|
borderColor: Colors.lightGrey,
|
|
|
|
},
|
|
|
|
taskId: {
|
|
|
|
color: Colors.blue,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: 'bold',
|
|
|
|
},
|
|
|
|
headless: {
|
|
|
|
fontWeight: 'bold',
|
|
|
|
},
|
|
|
|
remark: {
|
|
|
|
color: Colors.brick,
|
2025-03-28 04:59:59 +00:00
|
|
|
},
|
2025-03-31 05:11:57 +00:00
|
|
|
timestamp: {
|
|
|
|
color: Colors.black,
|
2025-03-28 04:59:59 +00:00
|
|
|
},
|
2025-03-31 05:11:57 +00:00
|
|
|
toolbar: {
|
|
|
|
height: 57,
|
|
|
|
flexDirection: 'row',
|
|
|
|
paddingLeft: 10,
|
|
|
|
paddingRight: 10,
|
|
|
|
alignItems: 'center',
|
|
|
|
backgroundColor: Colors.gold,
|
2025-03-28 04:59:59 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
export default App;
|