I have some days dealing with Geofence in android and haven't found a way to make it work.
As for now i have:
GeofenceBroadcastReceiver
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "GeofenceBroadcastReceiver";
private static final String CHANNEL_ID = "GeofenceChannel";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent: " + intent);
Log.d(TAG, "Received intent action: " + intent.getAction());
Log.d(TAG, "Received intent extras: " + intent.getExtras());
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent == null) {
Log.e(TAG, "Invalid geofencing event");
return;
}
if (geofencingEvent.hasError()) {
Log.e(TAG, "Geofencing error: " + geofencingEvent.getErrorCode());
return;
}
int transition = geofencingEvent.getGeofenceTransition();
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
switch (transition) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
for (Geofence geofence : triggeringGeofences) {
showNotification(context, "Entered zone: " + geofence.getRequestId());
}
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
// Handle the parent geofence exit event
if (triggeringGeofences.get(0).getRequestId().equals("PARENT_GEOFENCE")) {
GeofenceManager geofenceManager = new GeofenceManager(context);
geofenceManager.removeAllGeofences();
// Send a local broadcast to notify MainActivity
Intent parentExitIntent = new Intent("com.tmc.safetynet.PARENT_GEOFENCE_EXIT");
LocalBroadcastManager.getInstance(context).sendBroadcast(parentExitIntent);
}
break;
}
}
private void showNotification(Context context, String message) {
createNotificationChannel(context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle("Geofence Alert")
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify((int) System.currentTimeMillis(), builder.build());
}
private void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Geofence Channel";
String description = "Channel for geofence notifications";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}
GeofenceManager
public class GeofenceManager implements OnCompleteListener<Void> {
private static final String TAG = "GeofenceManager";
private static final float PARENT_RADIUS = 50000; // 60 KM
private static final float CHILD_RADIUS = 10000; // 5 KM
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
private Context context;
private GeofencingClient geofencingClient;
private PendingIntent geofencePendingIntent;
private List<Geofence> childGeofences;
private String tag;
public GeofenceManager(Context context) {
this.context = context;
this.geofencingClient = LocationServices.getGeofencingClient(context);
this.childGeofences = new ArrayList<>();
}
public void setupParentGeofence(Location currentLocation, String tag) {
this.tag = tag;
ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Location permission not granted.");
return;
}
Geofence parentGeofence = new Geofence.Builder()
.setRequestId("PARENT_GEOFENCE")
.setCircularRegion(currentLocation.getLatitude(), currentLocation.getLongitude(), PARENT_RADIUS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
GeofencingRequest geofencingRequest = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(parentGeofence)
.build();
geofencingClient.addGeofences(geofencingRequest, getGeofencePendingIntent()).addOnCompleteListener(this);
//intent.putExtra("com.google.android.gms.location.GEOFENCE_EXTRA", geofencingRequest);
}
public void setupChildGeofences(List<Location> filteredLocations, String tag) {
this.tag = tag;
Log.d("GeofenceManager", "Filtered locations count: " + filteredLocations.size());
for (Location location : filteredLocations) {
Geofence childGeofence = new Geofence.Builder()
.setRequestId(location.getLatitude() + "," + location.getLongitude())
.setCircularRegion(location.getLatitude(), location.getLongitude(), CHILD_RADIUS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build();
childGeofences.add(childGeofence);
}
GeofencingRequest geofencingRequest = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofences(childGeofences)
.build();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
geofencingClient.addGeofences(geofencingRequest, getGeofencePendingIntent()).addOnCompleteListener(this);
}
private PendingIntent getGeofencePendingIntent() {
if (geofencePendingIntent != null) {
return geofencePendingIntent;
}
Intent intent = new Intent(context, GeofenceBroadcastReceiver.class);
intent.setAction("com.tmc.safetynet.ACTION_GEOFENCE_EVENT");
// Add FLAG_IMMUTABLE for Android S (API level 31) and above
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags |= PendingIntent.FLAG_IMMUTABLE;
}
geofencePendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags);
return geofencePendingIntent;
}
public void removeAllGeofences() {
geofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this);
}
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d("GeofenceManager", "Geofences " + tag + " added/removed successfully.");
} else {
Log.e("GeofenceManager", "Failed to add/remove " + tag + " geofences.", task.getException());
}
}
}
MyActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_geo_fence)
geofenceManager = new GeofenceManager(this);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
setupParentGeofence();
}
private void setupParentGeofence() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
}
fusedLocationClient.getLastLocation().addOnCompleteListener(new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
if (task.isSuccessful() && task.getResult() != null) {
Location currentLocation = task.getResult();
geofenceManager.setupParentGeofence(currentLocation, "Parent");
fetchAndFilterLocations(currentLocation);
} else {
Toast.makeText(GeoFenceActivity.this, "Failed to get current location", Toast.LENGTH_SHORT).show();
}
}
});
}
private BroadcastReceiver parentGeofenceExitReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
setupParentGeofence();
}
};
private void fetchAndFilterLocations(Location currentLocation) {
DatabaseReference polygonsRef = FirebaseDatabase.getInstance().getReference("xxxx");
polygonsRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
List<Location> filteredLocations = new ArrayList<>();
for (DataSnapshot countrySnapshot : dataSnapshot.getChildren()) {
for (DataSnapshot provinceSnapshot : countrySnapshot.getChildren()) {
for (DataSnapshot districtSnapshot : provinceSnapshot.getChildren()) {
List<Double> latitudes = new ArrayList<>();
List<Double> longitudes = new ArrayList<>();
for (DataSnapshot zoneSnapshot : districtSnapshot.getChildren()) {
double lat = Double.parseDouble(Objects.requireNonNull(zoneSnapshot.child("lat").getValue(String.class)));
double lng = Double.parseDouble(Objects.requireNonNull(zoneSnapshot.child("long").getValue(String.class)));
latitudes.add(lat);
longitudes.add(lng);
}
// Calculate the center point of the polygon
double centerLat = average(latitudes);
double centerLng = average(longitudes);
Location centerLocation = new Location("");
centerLocation.setLatitude(centerLat);
centerLocation.setLongitude(centerLng);
float distance = currentLocation.distanceTo(centerLocation);
if (distance <= PARENT_RADIUS) {
filteredLocations.add(centerLocation);
}
}
}
}
geofenceManager.setupChildGeofences(filteredLocations, "child");
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
Log.e(TAG, "Failed to read data from polygons table", databaseError.toException());
}
});
}
Manifest
<receiver android:name=".GeofenceBroadcastReceiver"/>
But each time i run the code i get in console:
D/GeofenceManager: Filtered locations count: 56 D/GeofenceManager: Geofences child added/removed successfully.
D/GeofenceBroadcastReceiver: Received intent: Intent { act=com.tmc.safetynet.ACTION_GEOFENCE_EVENT flg=0x10 cmp=com.tmc.safetynet/.GeofenceBroadcastReceiver } D/GeofenceBroadcastReceiver: Received intent action: com.tmc.safetynet.ACTION_GEOFENCE_EVENT D/GeofenceBroadcastReceiver: Received intent extras: null E/GeofenceBroadcastReceiver: Invalid geofencing event
And my geofences doesn't appear in the map. The code that throws Invalid geofencing event is located inside GeofenceBroadcastReceiver
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent: " + intent);
Log.d(TAG, "Received intent action: " + intent.getAction());
Log.d(TAG, "Received intent extras: " + intent.getExtras());
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent == null) {
Log.e(TAG, "Invalid geofencing event");
return;
}
Any idea?
Best regards