michalchudziak / react-native-geolocation

Geolocation APIs for React Native
MIT License
1.31k stars 228 forks source link

Android: track user geolocation in background mode #285

Open VTSingle opened 10 months ago

VTSingle commented 10 months ago

Hey team,

Did someone found a way, how to track user geolocation in background mode? I implemented it on IOS and it's work perfectly without any extra libraries. But on Android it's doesn't work.

Can someone help me or give a advice?

Thank you!

shuklaharshit commented 8 months ago

Hey can you tell me how did you got it done in ios(code snippets), what are the caveats and did you do something extra for it ?thanks

OfryL commented 8 months ago

Hey, you can use react-native-background-timer to do it example/src/configs/BackgroundLocationUpdates.tsx

shuo-hiwintech commented 4 months ago

嘿,你可以使用example/src/configs/BackgroundLocationUpdates.tsxreact-native-background-timer来做到这一点

HI, can you please tell me if this example works in both android and ios?

SergioGCarvalho commented 1 month ago

@shuo-hiwintech i tried, not working, gets timeout if you try get a new location on background

ivanoikon commented 2 weeks ago

Are there any other solutions/packages to get background position? Using getPosition with background timer lead to a lot of timeout errors

quan118 commented 1 week ago

I've just created a fork that includes support for background location updates: https://github.com/quan118/react-native-geolocation

ivanoikon commented 1 week ago

I've just created a fork that includes support for background location updates: https://github.com/quan118/react-native-geolocation

I made some tests but unfortunately it has the same issues i found with react-native-geolocation-service + rn-foreground-service... on Android 14 after 10-15 seconds background position updates stop with no reason (foreground service seems alive). Thank you @quan118

artemkhalygov commented 1 week ago

I found a solution, that finally works for me. It's required to install 'react-native-background-actions'

First of all, you need to add these permissions to AndroidManifest.xml

After that, you should ask the user to allow tracking of his location. Important that you should ask it one by one.

async function requestMultiplePermissions() {
  try {
    await PermissionsAndroid.requestMultiple([
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    ])
    await PermissionsAndroid.requestMultiple([
      PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
    ])
    await PermissionsAndroid.requestMultiple([
      PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
    ])
  } catch (err) {
    console.warn('Permission request error:', err)
  }
}

And need to configure the background task.

import { useEffect } from 'react'
import { PermissionsAndroid, AppState } from 'react-native'
import BackgroundJob from 'react-native-background-actions'
import Geolocation from '@react-native-community/geolocation'

const getLocationTask = async (taskData: any) => {
  // Define a function to get the current position
  // const getCurrentPosition = () => new Promise((resolve, reject) => {
  //   Geolocation.getCurrentPosition(
  //     (position) => {
  //       console.log('Current Position:', position)
  //       resolve()
  //     },
  //     (error) => {
  //       console.log('Geolocation error:', error.code, error.message)
  //       reject(error)
  //     },
  //     { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 },
  //   )
  // })

  await new Promise((resolve) => {
    const intervalId = setInterval(async () => {
      if(!BackgroundJob.isRunning()) {
        clearInterval(intervalId) // Stop the interval if the background job is no longer running
        resolve()
        return
      }

      try {
        // await getCurrentPosition()
        await BackgroundJob.updateNotification({ taskDesc: 'Getting position...' })
      } catch (error) {
        console.log('Error getting position:', error)
      }
    }, 60 * 1000) // Get position every minute
  })
}
const options = {
  taskName: 'Gelocation Task',
  taskTitle: 'Fetching User Location',
  taskDesc: 'Fetching User Location background and foreground',
  taskIcon: {
    name: 'ic_launcher',
    type: 'mipmap',
  },
  color: theme.colors.primary,
  linkingURI: 'icansdriver://',
  parameters: {
    delay: 1000,
  },
}

// Function to start the background job
export const startBackgroundJob = async () => {
  try {
    console.log('Trying to start background service')
    await BackgroundJob.start(getLocationTask, options)
    console.log('Background service started successfully!')
  } catch (e) {
    console.log('Error starting background service:', e)
  }
}

// Function to stop the background job
export const stopBackgroundJob = async () => {
  try {
    console.log('Stopping background service')
    await BackgroundJob.stop()
    console.log('Background service stopped successfully!')
  } catch (e) {
    console.log('Error stopping background service:', e)
  }
}

export default function RequestGeolocation() {
  useEffect(() => {
    requestMultiplePermissions()

    AppState.addEventListener('change', nextAppState => {
      if(nextAppState === 'background') {
        startBackgroundJob()
      } else {
        stopBackgroundJob()
      }
    })
    // Configure Geolocation
    Geolocation.setRNConfiguration({
      authorizationLevel: 'always', // Request "always" location permission
      skipPermissionRequests: false, // Prompt for permission if not granted
    })

    // Watch for position updates
    const watchId = Geolocation.watchPosition(
      position => {
        console.log(position)
      // Send the position data to the server
      },
      error => {
        console.log(error)
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 100, // Minimum distance (in meters) to update the location
        interval: 60 * 1000, // Update interval (in milliseconds), which is 15 minutes
        fastestInterval: 30 * 1000, // Fastest update interval (in milliseconds)
        accuracy: {
          android: 'highAccuracy',
          ios: 'best',
        },
        showsBackgroundLocationIndicator: true,
        pausesLocationUpdatesAutomatically: false,
        activityType: 'fitness', // Specify the activity type (e.g., 'fitness' or 'other')
        useSignificantChanges: false,
        deferredUpdatesInterval: 0,
        deferredUpdatesDistance: 0,
        foregroundService: {
          notificationTitle: 'Tracking your location',
          notificationBody: 'Enable location tracking to continue', // Add a notification body
        },
      },
    )

    // To stop tracking (for example, when the component unmounts):
    return () => {
      Geolocation.clearWatch(watchId)
    }
  }, [])
  return null
}

In the code above you can uncomment const getCurrentPosition and get coordinates by timer, or you can remove this part of the code and use Geolocation.watchPosition instead. Even without adding it to the task it would watch location changes in background mode, because you have all permissions to do it.