React native android: Access location data when your app is in the background without ACCESS_BACKGROUND_LOCATION permission
I’m writing this dirty guide for anyone feeling stuck. This is meant for people who have primarily only written javascript code but are somewhat familiar with the way android activities and services work. Unfortunately, this will not help people who are using Expo’s managed workflow. As I don’t intend to make this into a spoon-feeding exercise there is not a lot of code. I only want this post to act as a beacon, you have to get your ship to the harbor.
Who should read this?
Anyone developing a react-native android app that relies heavily on location data being available frequently (every few seconds) for a defined period — e.g. rideshare apps need to access driver’s location for the duration of the ride, food delivery apps for the entire duration from accepting an order to delivery, etc. Put another way, this is mostly meant for apps where location is tracking is really required once the user initiates some kind of action. This action could for example be a driver starting a ride which should result in the driver’s real-time location made available to the riders for the duration of the ride, or that a delivery agent accepts an order for a food delivery app, an app that records a specific activity such as running or cycling once the user presses a “record activity” button, etc. Read on if you want to avoid going through android’s app review process but still get reliable location updates for your app.
The problem
If you’ve followed the changes that android is making to tighten the access to location data when your app is in the background, you must have already heard of numerous stories of apps getting rejected by Playstore. Playstore now has a mandatory app review process for apps that access location in the background. Developers have described that the appeal/support process is very time-consuming. Check this issue on expo’s GitHub to get a sense of what I’m saying.
Android has made very quick changes to the way location permissions work in android API 29 and more so in API 30. Starting API 30, Users are required to grant those permissions explicitly from the settings page of your app. And that, you can’t ask for both ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION permissions at the same time. Android “may automatically accept/deny” a permission request on the user’s behalf and that the permission dialog may not open if the user has previously denied permission.
It doesn’t take a UX ninja to figure out that it would result in a lot of users not being able to complete this critical step in your app if you rely on accessing location in the background, that is, if you somehow get past the review process.
This is also problematic because Oreo onwards, an app can only receive location updates in the background only a few times each hour. You can read up about this in detail here and here or you can just use the below gist:
Most apps don’t need to access location in the background and hence shouldn’t be requesting and using that. Apps should instead access location in the foreground using a foreground service.
When is your app considered to be using foreground location?
Quoting from the android docs:
The system considers your app to be using foreground location if a feature of your app accesses the device’s current location in one of the following situations:
1. An activity that belongs to your app is visible.
2. Your app is running a foreground service. When a foreground service is running, the system raises user awareness by showing a persistent notification. Your app retains access when it’s placed in the background, such as when the user presses the Home button on their device or turns their device’s display off.
#1 is easy, you could easily listen to location changes when the app is visible in a useEffect()/ComponentDidMount lifecycle method using expo-location’s watchPositionAsync or react-native-geolocation-service’s watchPosition method.
But #2 is what this post is about — your app’s UI may not be visible, the screen may be locked but your app could still frequently access the device’s current location without having the permission to access the location in the background. I guess a lot of people desperately want this. So, how do we do this? Easy, by starting a foreground service. It’s important to note what android docs say:
An app is considered to be accessing location in the background unless one of the following conditions is satisfied:
1. An activity belonging to the app is visible.
2. The app is running a foreground service that has declared a foreground service type of
location
.To declare the foreground service type for a service in your app, set your app’s
targetSdkVersion
orcompileSdkVersion
to29
or higher. Learn more about how foreground services can continue user-initiated actions that require access to location.
I did a quick POC today using this package which uses headlessjs under the hood and react-native-geolocation-service.
Follow this guide and you should be good to go for the most part.
The only changes that I made were:
1. To the manifest file to declare the service of the type location to take care of #2 above .
2. To the onStart and onStop functions in the above guide to set up watchPosition listener to get location updates every 5 seconds and to clear listener respectively.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> ...
...
... <!-- android:foregroundServiceType="location" -->
<service android:name="com.supersami.foregroundservice.ForegroundService" android:foregroundServiceType="location"></service>
<service android:name="com.supersami.foregroundservice.ForegroundServiceTask" android:foregroundServiceType="location"></service>
</application>
</manifest>
I first tested on an emulator running android 11. Once I started the foreground service, I pressed the home button and turned off the device display and observed the logs. I got 172 updates in 860 seconds, so this seemed very reliable.
Then to ensure that this works with previous versions as well, I tested with 3 physical devices running android 6, 9 and 10 and I got similar results.
This seems way better for the use-cases that I described above than —
1. Asking for ACCESS_BACKGROUND_LOCATION permission from the user which is a problem in itself.
2. Relying on background location updates that may be infrequent at best.
3. Having to go through the app review process — which may hold up your release/update for an indefinite period.
I hope this helps a lot of people. If it did, please pay it forward by writing a detailed guide for beginners. Thanks for reading through the post. You may now get on with life feeling a little more at ease! :-)