Geofencing API by Google helps provide contextual information to users based on their location. However, we have a limit of 100 active geofences at one time.
Lets break the limit with dynamic geofencing to suit our usecase.
We need to ping our users when they are near any Toll Plaza with an "Upcoming Toll Plaza" notification on their device. How many Tolls do we have in India? 500+. Before we get into registering 500+ geofences dynamically. Lets see what do we already know.
Geofence is a geographical region which can be monitored for transition events such as Entry, Exit or Dwell. Have a look at this diagram.
We are interested only in Entry and Exit transition events for now.
We know we have a limit of 100 geofences at a time.
Straight Outta Docs:
You can have multiple active geofences, with a limit of 100 per app, per device user. For each geofence, you can ask Location Services to send you entrance and exit events, or you can specify a duration within the geofence area to wait, or dwell, before triggering an event. You can limit the duration of any geofence by specifying an expiration duration in milliseconds. After the geofence expires, Location Services automatically removes it.
Let's Engineer. We have a limit of 100 active geofence. So we need to figure out a radius for a region which can never have more than 100 geofences inside it. According to our use case, we can safely assume 300km radius. We will call this region Parent Region. Inside this Parent Region we will have multiple Child Regions - 5km radius which are our actual geofences for Tolls. When the device exits the Parent Region we will setup a new Parent Region from the device's current location and reset the toll geofences. Using this, at no point device will have 100+ active geofences. As device moves out of the 300km region, new geofences will get registered dynamically.
We now have following tasks to perform:
Parent Region : Setup a Geofence of 300km radius from user's current location with Geofence transition event set to EXIT.
Filter out locations (Tolls) from the list of 500+ locations which fall inside the above region by calculating their distance.
Child Region : Setup a Geofence of 5km radius on all filtered locations with Geofence transition event set to ENTRY.
When transition event ENTRY is triggered on Child Region, display a notification to user.
When transition event EXIT is triggered on Parent Region, unregister all the existing Child Regions and repeat steps 1 to 3.
Note: 300km for Parent Region and 5km for Child Region are assumptions I have made for my use case. Feel free to set the radii as per your requirement.
Before we get into dynamic geofences. How do we register a single geofence? Check out this great article on raywenderlich.com on how to setup geofence in Android which I have followed in this article as well.
Lets start by creating models.
We will be fetching our tolls from Room database. Here's an Entity class for Toll table.
1) The first step is to setup a parent geofence with a region of 300km radius.
Put this code in your Launcher/Main Activity.
- We get user's current location.
- We create an instance of
- We setup parent geofence.
setupParentGeofence()function, we create Parent Geofence model with id,lat,long and radius of 300km.
- We build a Geofence with transition EXIT for parent region. We are passing an enum value
buildGeofence()function for distinction.
- Make sure to ask user for Background Location permission before setting up geofences in MainActivity. We include a location permission check here before registering geofences.
- Use geofencingClient object to register geofences. It takes a GeofencingRequest and a PendingIntent pointing to GeofenceBroadcastReceiver where we handle our geofencing event triggers. More about this later.
2) Now we setup our Child Regions (Toll locations) which fall in the above Parent Region.
setupGeofences() function in MainActivity as below.
We are using MVVM design pattern with LiveData.
mViewModel.tollsList.observewill fetch all the tolls from the database which we pass into
setupChildGeofences()function inside our helper class.
Hold up! as we have seen above we need a Geofence model for registering geofences. However, we get a list of Toll models from the database. Let's transform all Toll models into Geofence models using Transformations provided by androidx.lifecycle.
- Along with the tolls list, we are also passing user's current lat-long into
setupChildGeofences(). Why? Keep reading!
Let's look at
Location.distanceBetween()function to filter the list of locations. We use user's current lat-long and check if the toll's location is less than 300km from user's current location in distance.
We run a loop on this filtered list and register geofences similar to parent geofence. Double check you are passing
After successful registration, we store ids of these child geofences to SharedPreferences, so that we can unregister them later.
Congratulations! We have successfully registered one parent geofence and multiple child geofences inside it.
Let's jump right into code, where our Entry and Exit events are captured.
enqueueWork()is called from the
onReceive()of our registered GeofenceBroadcastReceiver below.
onHandleWork(), we have a geofencingEvent created from the intent. We pass this event to
handleEvent()function where all the magic happens.
If the transition event is
GEOFENCE_TRANSITION_ENTER, we have a child geofence entry event, so we generate a notification for the user with the Toll name and a title - "Upcoming Toll Plaza".
If the transition event is
GEOFENCE_TRANSITION_EXIT, we have a parent geofence exit event, so we setup a new parent region and child regions same as before.
Before registering new geofences, we unregister existing child geofences by Ids stored in SharedPreferences.
For device's current location we use,
Since we are already in a background thread now, we can directly get the list of Tolls and map each element to Geofence model.
I am open to feedbacks and suggestions. Please reach out if you have any doubts.