OPTION 1 - PUBLISHER / SUBSCRIBER PATTERN
This is a simple example in which latitude
and longitude
are tracked in the background using the publisher/subscriber
pattern. A summary of what is happening:
- The
LocationService
function has internal state through a closure. It returns an object with several methods on it. The important one to get your head around issubscribe
. When this is called it is passed a reference to a function as an argument, and that reference is added to thesubscribers
array. -
componentDidMount
callslocationService.subscribe(this.onLocationUpdate)
passing the class methodonLocationUpdate
into thesubscribers
array. -
TaskManager
executes its callback each time new data is received. The callback contains a call toLocationService.setLocation({latitude, longitude})
. This takes thelatLng
object and passes it to every function contained in thesubscribers
array. This includes the class methodonLocationUpdate
and therefore the classstate
is updated, which consequently re-renders the component.
/* LOCATION SERVICE - PUB/SUB SOLUTION */
/* PUBLISHER */
const LocationService = () => {
let subscribers = []
let location = {
latitude: 0,
longitude: 0
}
return {
subscribe: (sub) => subscribers.push(sub),
setLocation: (coords) => {
location = coords
subscribers.forEach((sub) => sub(location))
},
unsubscribe: (sub) => {
subscribers = subscribers.filter((_sub) => _sub !== sub)
}
}
}
export const locationService = LocationService()
/* SUBSCRIBER */
import * as Location from 'expo-location'
import * as Permissions from 'expo-permissions'
import * as TaskManager from 'expo-task-manager'
import React from 'react'
import { Text, View } from 'react-native'
import { locationService } from './src/locationService'
const LOCATION_TASK_NAME = 'background-location-task'
export default class App extends React.Component {
state = {
latitude: 0,
longitude: 0,
}
onLocationUpdate = ({ latitude, longitude }) => {
this.setState({
latitude: latitude,
longitude: longitude
})
}
async componentDidMount() {
locationService.subscribe(this.onLocationUpdate)
const { status } = await Permissions.askAsync(
Permissions.LOCATION,
Permissions.USER_FACING_NOTIFICATIONS
)
if (status === 'granted') {
await Location.startLocationUpdatesAsync(
LOCATION_TASK_NAME,
{
accuracy: Location.Accuracy.Highest,
distanceInterval: 1,
timeInterval: 5000
}
)
}
}
componentWillUnmount() {
locationService.unsubscribe(this.onLocationUpdate)
}
render() {
const { latitude, longitude } = this.state
return (
<View>
<Text>Lat: {latitude}</Text>
<Text>Lng: {longitude}</Text>
</View>
)
}
}
TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
if (error) {
return
}
if (data) {
const { latitude, longitude } = data.locations[0].coords
locationService.setLocation({
latitude,
longitude
})
}
})