androidbackgroundlocationmapsgeofencing

Geofencing in background on Android 8 or 9 does not work


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();
}

Solution

  • 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.