Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Geofence with BroadcastReceiver is not always triggered

Before when I was using geofence in my application I was using IntentService as its callback and everything was ok. But now because of changes in Android 8 I wasn't able to start service like that anymore. So instead now I am using BroadcastReceiver as my Geofencing callback and that receiver is starting service (foreground one for android 8). But with that change I noticed that geofencing doesn't work good anymore. It's often not being trigerred at all, the worse case I think is with the application in the foreground. And the best case is when the app is killed, then the geofencing is being trigerred. Is there something wrong with BroadcastReceivers and geofencing or am I doing something wrong? I provide my code:

Generic class for adding geofencing:

public abstract class BaseGeofence<T extends BaseGeofenceObject> implements OnSuccessListener<Void>, OnFailureListener {
    protected int RADIUS_IN_METERS = 300;
    protected GeofencingClient client;
    protected Context context;

    public BaseGeofence(Context context) {
        this.client = LocationServices.getGeofencingClient(context);
        this.context = context;
    }

    @SuppressLint("MissingPermission")
    public void addGeofencing(final T object) {
        if (GPSUtils.wasLocationChecked()) {
            Geofence geofence = (new Geofence.Builder()
                    .setRequestId(Integer.toString(getId(object)))

                    .setCircularRegion(
                            object.getLat(),
                            object.getLon(),
                            RADIUS_IN_METERS
                    )
                    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
                            Geofence.GEOFENCE_TRANSITION_EXIT)
                    .setExpirationDuration(Geofence.NEVER_EXPIRE)
                    .build());
            client.addGeofences(getGeofencingRequest(geofence), getGeofencePendingIntent(object))
                    .addOnSuccessListener(this).addOnFailureListener(this);
        }


    }

    private GeofencingRequest getGeofencingRequest(Geofence geofence) {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofence(geofence);
        return builder.build();
    }

    private PendingIntent getGeofencePendingIntent(T object) {
        return PendingIntent.getBroadcast(context, getId(object), createIntent(object), PendingIntent.FLAG_UPDATE_CURRENT);
    }

    protected abstract Intent createIntent(T object);

    protected abstract int getId(T object);

    @Override
    public abstract void onSuccess(Void aVoid);

    @Override
    public abstract void onFailure(@NonNull Exception e);
}

MyGeofencing class:

public class MyGeofencing extends BaseGeofence<MyObject> {

        private MyObject myObject;

        public MyGeofencing(Context context) {
            super(context);
        }

        @Override
        protected Intent createIntent(MyObject object) {
            this.myObject = object;
            Intent intent = new Intent(context, GeofenceTransitionsReceiver.class);

            //put extras to intent 

            return intent;
        }

        @Override
        public void addGeofencing(MyObject object) {
            if (object.getGeofencingRadius() == 0) RADIUS_IN_METERS = 300;
            else RADIUS_IN_METERS = object.getGeofencingRadius();
            super.addGeofencing(object);
        }

        @Override
        protected int getId(MyObject object) {
            return object.getId();
        }



    }

My Receiver:

public class GeofenceTransitionsReceiver extends StartingServiceBroadcastReceiver {


    @Override
    public void onReceive(Context context, Intent intent) {
        if (GeofencingEvent.fromIntent(intent) != null && GeofencingEvent.fromIntent(intent).getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_ENTER) {

            startConnectionService(context, "extra");


        } else if (GeofencingEvent.fromIntent(intent) != null && GeofencingEvent.fromIntent(intent).getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_EXIT) {
            // do something
        }
    }

protected void startConnectionService(Context context, String extra) {
        Intent beaconIntent = new Intent(context, BeaconConnectionService.class);
        beaconIntent.putExtra("extra", extra);        
        startService(context, beaconIntent);
    }
}

StartingServiceBroadcastReceiver starts foreground service if it's android 8 or normal one otherwise

public abstract class StartingServiceBroadcastReceiver extends BroadcastReceiver {
    @Override
    public abstract void onReceive(Context context, Intent intent);

    protected void startService(Context context, Intent intent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent);
        } else context.startService(intent);
    }
}

And my service extends FlexibleFOregroundService which starts foreground notification if needed:

public class FlexibleForegroundService extends Service {

    private static final String CHANNEL_ID = "test";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        makeServiceForegroundIfNeeded();
    }

    public void stopService(){
        stopSelf();
    }

    private void makeServiceForegroundIfNeeded() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel();

            String message = "test";

            Notification notification =
                    new Notification.Builder(this, CHANNEL_ID)
                            .setContentTitle("test")
                            .setStyle(new Notification.BigTextStyle()
                                    .bigText(message))
                            .setContentText(message)
                            .setSmallIcon(R.mipmap.ic_launcher)
                            .build();

            startForeground(1, notification);
        }
    }


    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Information", importance);
            channel.setDescription("test");
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }




}

Is there some way I can improve working of geofencing on all Android devices?

like image 886
matip Avatar asked Oct 22 '18 11:10

matip


People also ask

When would you use a BroadCastReceiver?

Broadcast in android is the system-wide events that can occur when the device starts, when a message is received on the device or when incoming calls are received, or when a device goes to airplane mode, etc. Broadcast Receivers are used to respond to these system-wide events.

What is the limit of BroadCastReceiver in Android?

As a general rule, broadcast receivers are allowed to run for up to 10 seconds before they system will consider them non-responsive and ANR the app.

How do I stop BroadCastReceiver?

To stop receiving broadcasts, call unregisterReceiver(android. content. BroadcastReceiver) . Be sure to unregister the receiver when you no longer need it or the context is no longer valid.

Can broadcast receiver run in background?

A broadcast receiver will always get notified of a broadcast, regardless of the status of your application. It doesn't matter if your application is currently running, in the background or not running at all.

How does broadcastreceiver detect a geofence transition?

After detecting the transition event via the PendingIntent , the BroadcastReceiver gets the geofence transition type and tests whether it is one of the events the app uses to trigger notifications -- either GEOFENCE_TRANSITION_ENTER or GEOFENCE_TRANSITION_EXIT in this case. The service then sends a notification and logs the transition details.

Does the geofence always trigger?

By how geofences work, it SHOULD NOT trigger or will trigger sometimes, not always. Have you perhaps had this kind of problem? If so, did you managed to find out the reason behind it?

How does a broadcastreceiver work?

A BroadcastReceiver gets updates when an event occurs, such as a transition into or out of a geofence, and can start long-running background work. The following snippet shows how to define a PendingIntent that starts a BroadcastReceiver :

Does geofencing trigger when app is closed down?

I'm developing an app which, with other possible features, also uses Geofencing. Problem with EVERYONE having using built-in geofence api is, it does NOT trigger when app is closed down (press home button OR back then leave a phone to sleep a while).


1 Answers

I am assuming you are already aware of the cases where you have to Reregister geofences Your geofences will get deregistered whenever user disables the GPS.

What I have observed is you get geofence events more frequently when you are using Location Services like when map is open.

Can you also try putting action like "ACTION_GEOFENCE_TRANSITION" in the receivers intent-filter inside the manifest file. Put the same as an action when you create the pending intent.

like image 196
Vicky Avatar answered Nov 14 '22 00:11

Vicky