Dynamic Geofencing 100+ in Kotlin - Android
Tags: geofence, android, location, kotlin
Published: 2020-02-15 19:29:43

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. 

#What's our use case?

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+](http://tis.nhai.gov.in/). Before we get into registering 500+ geofences dynamically. Lets see what do we already know.

##Geo.. What?

Geofence is a geographical region which can be monitored for transition events such as Entry, Exit or Dwell. Have a look at this diagram.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e1tni5u3eqdcpngmjsgh.png)

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.

##Geo.. How?

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: 

1. _Parent Region_ : Setup a Geofence of 300km radius from user's current location with Geofence transition event set to **EXIT**. 

2. Filter out locations (Tolls) from the list of 500+ locations which fall inside the above region by calculating their distance.

3. _Child Region_ : Setup a Geofence of 5km radius on all filtered locations with Geofence transition event set to **ENTRY**.

4. When transition event **ENTRY** is triggered on _Child Region_, display a notification to user.

5. 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._


##Geo..Code!

Before we get into dynamic geofences. How do we register a single geofence? Check out this [great article](https://www.raywenderlich.com/7372-geofencing-api-tutorial-for-android) on raywenderlich.com on how to setup geofence in Android which I have followed in this article as well. 

Lets start by creating models.

Geofence
- id
- latitude
- longitude
- radius

{% gist https://gist.github.com/ibrahimbroachwala/02b17b93e717aee9c97db18a174240b3 %}

Toll
- id
- name
- latitude
- longitude


We will be fetching our tolls from _Room_ database. Here's an Entity class for _Toll_ table.

{% gist https://gist.github.com/ibrahimbroachwala/f061df464c9ac5ff6307529be7a0c412 %}


1) The first step is to setup a parent geofence with a region of 300km radius.

Put this code in your Launcher/Main Activity.

{% gist https://gist.github.com/ibrahimbroachwala/8c94557a8ddc672043d087e66027304c %}

What's happening?
- We get user's current location.
- We create an instance of `GeofenceHelper` class.
- We setup parent geofence. 

`GeofenceHelper`:

{% gist https://gist.github.com/ibrahimbroachwala/13d8332f55553da9e4f9d1e42f1dd223 %}

What's happening?
- Inside `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 `GeofenceType.PARENT` to `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_.

Update `setupGeofences()` function in MainActivity as below.

{% gist https://gist.github.com/ibrahimbroachwala/b1d70660c5a16aafe62826c1de66c926 %}

What's happening?
- We are using MVVM design pattern with LiveData. `mViewModel.tollsList.observe` will 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.

{% gist https://gist.github.com/ibrahimbroachwala/f99dd8047e34c37ebb6521a0b533497a %}


- Along with the tolls list, we are also passing user's current lat-long into `setupChildGeofences()`. Why? Keep reading!

Let's look at `setupChildGeofences()` function.

{% gist https://gist.github.com/ibrahimbroachwala/0ffc5e689201c07611eefca774e97da1 %}

What's happening?

- We use `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 `GeofenceType.CHILD` to `buildGeofence()`function.

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

##Geo..Dynamic!

Let's jump right into code, where our Entry and Exit events are captured.
 
`GeofenceTransitionsJobIntentService`

{% gist https://gist.github.com/ibrahimbroachwala/21672c285a45dc87b760d69a430f5dda %}

What's happening?

- GeofenceTransitionsJobIntentService's `enqueueWork()` is called from the `onReceive()` of our registered GeofenceBroadcastReceiver below.

{% gist https://gist.github.com/ibrahimbroachwala/f4f512dc0eacaba596c066bd552f5810 %}

- In `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, `event.triggeringLocation.latitude` and `event.triggeringLocation.longitude`.

- Since we are already in a background thread now, we can directly get the list of Tolls and map each element to Geofence model. 

####Awesome! We have successfully implemented dynamic geofencing in Android.####

I am open to feedbacks and suggestions. Please reach out if you have any doubts.