I try to make appear a push alert to the user when he reach a defined zone.
So I coded my app from : https://developer.android.com/training/location/geofencing
It is working perfectly if my app is running with a service following the location of the user.
It also work if I start google map for example, that will track my location too. Pushes will appear.
But if I close my app the push won't appear, so the geofencing is not detected if no app is tracking my location.
Is it normal ? How to make it work always ? What is the point of geofencing if you need a foreground service following your location ?
public void createGeofenceAlerts(LatLng latLng, int radius) {
final Geofence enter = buildGeofence(ID_ENTER, latLng, radius, Geofence.GEOFENCE_TRANSITION_ENTER);
final Geofence exit = buildGeofence(ID_EXIT, latLng, radius, Geofence.GEOFENCE_TRANSITION_EXIT);
final Geofence dwell = buildGeofence(ID_DWELL, latLng, radius, Geofence.GEOFENCE_TRANSITION_DWELL);
GeofencingRequest request = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build();
fencingClient.addGeofences(request, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Timber.i("succes");
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Timber.e(e,"failure");
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show();
}
});
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
PendingIntent pending = PendingIntent.getService(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pending;
}
private Geofence buildGeofence(String id, LatLng center, int radius, int transitionType) {
Geofence.Builder builder = new Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.getLatitude(),
center.getLongitude(),
radius)
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE);
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY);
}
return builder.build();
}
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.