I have implemented geofencing in the application. But the issue is whenever I open the application the geofence is added again. Thus it immediately results in triggering an entry alert if we are within a geofencing area.
Current implementation - workflow:
- User opens the App
- A service called
GeoFenceMakerService
is started from the main activity. GeoFenceMakerService
invoke an API call and fetches the details of the places (Lat, Long, Place Id, Place Name, Radius, etc) from the server. There can be one or more places.- The place details (API result) will be saved to the local database.
- If there are 1 or more places, then it builds the geofence object for each place (initializeGeoFence).
- Build the geofencing request and create pending intent. This will be passed to the Geofencing client to add the geofence. (
geofencingClient.addGeofences(getGeofencingRequest(), createGeoFencePendingIntent())
) - Stop the
GeoFenceMakerService
after adding the geofences.
Issue:
The createGeoFencePendingIntent()
is always creating new Pending intent and may trigger an entry alert if we are within a geofencing area whenever we open the application.
Similar solution found from stack overflow: Was to implement a check using Shared preferences for pending intent. You can see the full solution from here. But am not satisfied with this because, "it is better to check if the pending intent exists instead of writing in the sharedpreferences that we have added the geofences, because when we reboot the phone or change the location accuracy, they are removed and the pending intent does not exist anymore, but the sharedpreferences would not be updated" - (One already mentioned there as comment for the solution mentioned).
Current solution required: Need to check whether the Geofence is already added or not and add geofence only if none exists.
I am attaching the entire GeoFenceMakerService
code for reference.
package com.****.*****.services;
import android.Manifest;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import com.****.****.api.Api;
import com.****.****.api.ApiMethods;
import com.****.****.models.Place;
import com.****.****.models.response.PlacesListResponse;
import com.****.****.receiver.GeofenceBroadcastReceiver;
import com.****.****.utils.DatabaseManager;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import java.util.ArrayList;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class GeoFenceMakerService extends Service {
public static final String TAG = "GeoFenceMakerService";
private ArrayList<Place> places = null;
private GeofencingClient geofencingClient = null;
private ArrayList<Geofence> geofenceList = null;
private PendingIntent geoFencePendingIntent;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.d(TAG, "GeoFenceMakerService Created");
geofencingClient = LocationServices.getGeofencingClient(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//handleStart(intent, startId);
Log.d(TAG, "Inside onStartCommand");
getPlacesList();
return START_NOT_STICKY;
}
private void getPlacesList() {
Log.i(TAG, "getPlacesList: ");
ApiMethods apiMethods;
Call<PlacesListResponse> listResponseCall;
apiMethods = Api.getInstance(this);
listResponseCall = apiMethods.getAllPlacesList();
Log.i(TAG, "Getting places list from API");
listResponseCall.enqueue(new Callback<PlacesListResponse>() {
@Override
public void onResponse(Call<PlacesListResponse> call, Response<PlacesListResponse> response) {
if (response.code() == Api.STATUS_200) {
handlePlacesList(response.body());
} else {
Log.e(TAG, "Get places list api failed with code: " + response.code());
}
}
@Override
public void onFailure(Call<PlacesListResponse> call, Throwable t) {
Log.e(TAG, "Get places list api failed" + t.getLocalizedMessage());
}
});
}
private void handlePlacesList(PlacesListResponse placesListResponse) {
Log.i(TAG, "handlePlacesList: ");
DatabaseManager databaseManager = new DatabaseManager(this);
if (placesListResponse != null) {
Log.i(TAG, "Got places list response");
places = placesListResponse.getPlaces();
databaseManager.savePlacesToDatabase(places);
if (!(places.size() == 0)) {
initializeGeoFence();
addGeoFence2();
}
/* initializeGeoFence();
addGeoFence2();*/
} else {
Log.e(TAG, "Places list response is null");
}
}
private void initializeGeoFence() {
// Toast.makeText(this, "initializing geo fence", Toast.LENGTH_LONG).show();
Log.i(TAG, "initializing geo fence");
Geofence geofence;
geofenceList = new ArrayList<>();
for (Place place : places) {
Log.i(TAG, "Adding geofence at " + place.getLatitude() + ", " + place.getLongitude());
// @TODO Fetch and set the Geofence Radius dynamically
geofence = new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(place.getPlaceId() + "")
.setCircularRegion(
place.getLatitude(),
place.getLongitude(),
500f)
.setExpirationDuration(60 * 60 * 1000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
geofenceList.add(geofence);
}
}
private void addGeoFence2() {
Log.i(TAG, "addGeoFence2: ");
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Make sure permission is granted before starting the service. NOTE: Requesting permission from Service is not allowed.
return;
}
geofencingClient.addGeofences(getGeofencingRequest(), createGeoFencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "Geo fence added");
// Toast.makeText(HomeActivity.this, "Geo fence added", Toast.LENGTH_LONG).show();
GeoFenceMakerService.this.stopSelf();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "Geo fence add failed. ");
e.printStackTrace();
// Toast.makeText(HomeActivity.this, "Geo fence add failed", Toast.LENGTH_LONG).show();
GeoFenceMakerService.this.stopSelf();
}
});
}
private GeofencingRequest getGeofencingRequest() {
// Toast.makeText(this, "getGeofencingRequest", Toast.LENGTH_LONG).show();
Log.i(TAG, "getGeofencingRequest");
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(geofenceList);
return builder.build();
}
private PendingIntent createGeoFencePendingIntent() {
Intent intent;
Log.d(TAG, "createGeoFencePendingIntent");
if (geoFencePendingIntent == null) {
Log.i(TAG, "GeoFence pending intent is null. Creating new instance");
intent = new Intent(this, GeofenceBroadcastReceiver.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
geoFencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
}
return geoFencePendingIntent;
}
}