Geofencing in background on Android 8 or 9 does not work
I discovered that my geoFenceBroadCastReceiver started to work correctly in the emulator when I had google maps open. I simply could not figure out what the problem was, and as it turns out, I was missing a piece of the puzzle obviously. The behavior is also described in this question (aptly titled): Geofence Broadcast Receiver not triggered but when I open the google map, it works, as well as filed numerous times as an issue in the Android Location Samples project Issue 239, Issue 247 Issue 266.
I don't see the actual answer to this problem posted as an answer here, so for posterity I will provide some suggestions. Issue 264 seems to point to the solution, i.e. using a JobIntentService
BUT
There's a code comment in the GeoFence Sample LocationUpdateIntentService
Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates less frequently than the interval specified in the {@link com.google.android.gms.location.LocationRequest} when the app is no longer in the foreground.
that seems to confirm what @Vinayak points out in his answer here
O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
The geofence broadcast receiver isn't allowed to get timely location updates (apparently even in an IntentService). You'll have to run location client in a foreground service that's requesting fine location access to get more accurate/timely geoFence triggering. So seemingly the right answer is to run the geoFence in a foreGroundService.
If you go the foreGroundService route, you'll also need to create a Notification Channel, as otherwise, you'll run into problems like this one.
See Also:
https://developer.android.com/guide/components/foreground-services
https://developer.android.com/training/location/change-location-settings#location-request
https://developer.android.com/training/location/request-updates
https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20com.google.android.gms.location.LocationCallback)
O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
I have been working with GeoFence for such a long time, I had the same question and I got the answer by myself after trying different solutions, So basically, GeoFence only get triggers if any app in the phone is fetching the location for some x duration. If you test the GeoFence sample app provided by google then you can see that the app works only when you open the Google maps application, its because Google Maps is the only app in the device that requests locations passively.
For Prove you can clone GeoFence sample and the LocationUpdateForGroundService sample from this below link https://github.com/googlesamples/android-play-location Run both of them GeoFence and LocationUpdateForGroundService at the same time, You will notice by changing the lat and long from the emulator that now you dont need to open Google maps any more because now there is another app which is requesting location.
So do create a foreground service in the GeoFence application and use Fuse Location Client to request location updates for some x duration.
I think I found a solution, tested on Android 9. I used the Google documentation https://developer.android.com/training/location/geofencing but I replaced the service by a broadcast receiver.
My GeofenceManager :
private val braodcastPendingIntent: PendingIntent
get() {
val intent = Intent(mContext, GeofenceTransitionsBroadcastReceiver::class.java)
val pending = PendingIntent.getBroadcast(
mContext.applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
return pending
}
fun createGeofenceAlerts(latLng: LatLng, radiusMeter: Int, isBroadcast: Boolean) {
val enter = buildGeofence(ID_ENTER, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_ENTER)
val exit = buildGeofence(ID_EXIT, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_EXIT)
val dwell = buildGeofence(ID_DWELL, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_DWELL)
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build()
val pending = if (isBroadcast) {
braodcastPendingIntent
} else {
servicePendingIntent
}
fencingClient.addGeofences(request, pending).addOnSuccessListener {
Timber.i("succes")
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show()
}.addOnFailureListener { e ->
Timber.e(e, "failure")
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show()
}
}
private fun buildGeofence(id: String, center: LatLng, radius: Int, transitionType: Int): Geofence {
val builder = Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.latitude,
center.longitude,
radius.toFloat())
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE)
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY)
}
return builder.build()
}
My BroadcastReceiver, obviously you need to declare it in the manfifest :
class GeofenceTransitionsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.i("received")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
Timber.e("Geofence error")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
|| geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = GeofenceManager.getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences, true
)
// Send notification and log the transition details.
GeofenceManager.sendNotification(context, geofenceTransition, geofenceTransitionDetails)
Timber.i(geofenceTransitionDetails)
} else {
// Log the error.
Timber.e("Unknown geo event : %d", geofenceTransition)
}
}
The important part is to know that on Android 8 and 9 the geofencing has a latency of 2 minutes.