Building Android GeoFence into Android application

4
327

Building Android GeoFence into Android application | In this tutorial, we’ll learn how to build Android geofences using Google API, into your Android application, for efficient use of Google’s locations service. Before we start, let’s first understand what exactly Geo-Fence API is, What it does, And why should you use instead of its alternatives. Let’s get started.

You’ve probably learned how to use the GPS on your device to get the location of the device. Maybe you’ve played with the FusedLocationProvider (See more details: Here) which smartly uses the GPS along with other sensors to get your current location. How about we take it to the next level? How about an app that tells you when you enter or leave a specific location? You could use this for social check-ins, or maybe as a parent, you can use this to tell when your kids get home? I’ve seen games that use this too — where they virtually hide loot, and when you go to the real world location, your device knows and can react.

A great and easy way to implement this is using a geofence. Simply, a geofence is a location in latitude and longitude, a radius around that location, and a time for which it is active. So, for example, you could put the latitude and longitude of your loot stash, a radius of say 10 meters, and data that it’s only active on weekends. The Location APIs will take this, and when you exit or enter that space, can be used to generate a notification. It’s up to you as a developer how you handle this!

Here is the sample output screenshot, it’s pretty basic, as it has too many back-end operations:

Sample APK: Fence_1.0

Sample Project: Fence

Wants to Learn Advanced Android Application development from scratch- Beyond Basics

Let’s start coding now!

Creating a New Project:

Open your Android Studio & create a new Project, give name whatever you like and We’ll keep all the things by default and clicked finish.

Adding Dependencies to Gradle file:

Add following Gradle dependencies, to use Google’s Play services:

compile 'com.google.android.gms:play-services:7.3.0'

Adding permissions to manifest.xml:

We need to get location permission to access locations:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Adding metadata to manifest.xml:

add the following code just above the closing tag</activity>.

<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

Android Studio will change the contents of the value to something else. That’s perfectly natural, and a sign that things are working well. If the goesgoogle_play_services_version red, make sure you’ve hit Gradle Sync.

Now in your main activity, Add following java code to your MainActivity.java file.

MainActivity.java

package com.shah.kaushal28.fence;

import android.app.PendingIntent;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.model.LatLng;

import java.util.ArrayList;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        ResultCallback<Status>{

    protected ArrayList<Geofence> mGeofenceList;
    protected GoogleApiClient mGoogleApiClient;
    private Button mAddGeofencesButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAddGeofencesButton = (Button) findViewById(R.id.add_geofences_button);
        // Empty list for storing geofences.
        mGeofenceList = new ArrayList<Geofence>();

        // Get the geofences used. Geofence data is hard coded in this sample.
        populateGeofenceList();

        // Kick off the request to build GoogleApiClient.
        buildGoogleApiClient();

    }

    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    public void populateGeofenceList() {
        for (Map.Entry<String, LatLng> entry : Constants.LANDMARKS.entrySet()) {
            mGeofenceList.add(new Geofence.Builder()
                    .setRequestId(entry.getKey())
                    .setCircularRegion(
                            entry.getValue().latitude,
                            entry.getValue().longitude,
                            Constants.GEOFENCE_RADIUS_IN_METERS
                    )
                    .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
                    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
                            Geofence.GEOFENCE_TRANSITION_EXIT)
                    .build());
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mGoogleApiClient.isConnecting() || !mGoogleApiClient.isConnected()) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mGoogleApiClient.isConnecting() || mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {

    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // Do something with result.getErrorCode());
    }

    @Override
    public void onConnectionSuspended(int cause) {
        mGoogleApiClient.connect();
    }

    public void addGeofencesButtonHandler(View view) {
        if (!mGoogleApiClient.isConnected()) {
            Toast.makeText(this, "Google API Client not connected!", Toast.LENGTH_SHORT).show();
            return;
        }

        try {
            LocationServices.GeofencingApi.addGeofences(
                    mGoogleApiClient,
                    getGeofencingRequest(),
                    getGeofencePendingIntent()
            ).setResultCallback(this); // Result processed in onResult().
        } catch (SecurityException securityException) {
            // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
        }
    }

    private GeofencingRequest getGeofencingRequest() {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofences(mGeofenceList);
        return builder.build();
    }

    private PendingIntent getGeofencePendingIntent() {
        Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
        // We use FLAG_UPDATE_CURRENT so that we get the 
          //same pending intent back when calling addgeoFences()
        return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }
    public void onResult(Status status) {
        if (status.isSuccess()) {
            Toast.makeText(
                    this,
                    "Geofences Added",
                    Toast.LENGTH_SHORT
            ).show();
        } else {
            // Get the status code for the error and log it using a user-friendly message.
            System.out.println("Error");
        }
    }
}

Layout file for this main activity:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.shah.kaushal28.fence.MainActivity">

    <Button
        android:id="@+id/add_geofences_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:onClick="addGeofencesButtonHandler"
        android:text="Add GeoFences" />
</RelativeLayout>

Now Create a new java class named “constants”. We’ll define the parameters(like radius, latitude, longitude etc.) of our fence into that class.

This class defines a HashMap of locations, each with a key that is a string with the friendly name of the location, and a LatLng (Latitude and Longitude) for the location.

A Geofence can be thought of as a location, with a radius around it, and a time of expiration. When the geofence is active, anything detected within the radius distance of the location, either entering or exiting the area, can trigger a notification. This can be in perpetuity, or for a limited time. In this example, we’re specifying a 20-meter radius, with a 12 hour expiration time.

In this example, I’ve hard coded several locations for a Geofence — you don’t need to do this, of course, and can have a more dynamic layout by serving up the list of geofences from a service call, for example:

package com.shah.kaushal28.fence;

/**
 * Created by Kaushal28 on 4/26/2017.
 */

import com.google.android.gms.maps.model.LatLng;

import java.util.HashMap;


public class Constants {

    public static final long GEOFENCE_EXPIRATION_IN_MILLISECONDS = 12 * 60 * 60 * 1000;
    public static final float GEOFENCE_RADIUS_IN_METERS = 20;

    public static final HashMap<String, LatLng> LANDMARKS = new HashMap<String, LatLng>();
    static {
        // San Francisco International Airport.
        LANDMARKS.put("Moscone South", new LatLng(37.783888,-122.4009012));

        // Googleplex.
        LANDMARKS.put("Japantown", new LatLng(37.785281,-122.4296384));

        // Test
        LANDMARKS.put("SFO", new LatLng(37.621313,-122.378955));
    }
}

Now that the list of GeoFences is defined, along with an expiration time and a radius, write the function in MainActivity.java that loads them, this is done by “populateGeofenceList()” method of main activity (see the code of main activity.)

This method iterates through the HashMap that was set up in Constants.java, and for each entry, creates a new Geofence using a Geofence.Builder. The parameters passed to this are the ID — which is the key on the HashMap (e.g. “Japantown”), a circular region that defines the Geofence, containing a latitude and longitude of the center of the region, and a radius that defines a circle around that center. It then defines the expiration duration (which can be 0 for never, check the docs), and a transition type that triggers an alert. This can be entering the fence, exiting the fence or both. In this case, you can see that it’s both.

Related:

Geofencing in Android

Handle Multiple Geofencing in Common Areas in google map in android application

Display current location(latitude and longitude) in google map and save it to the Firebase database in Android

Draw route and calculate distance between two locations in Google Maps Android

Draw route between two locations, Google Maps in Android

Building the API Client

When you set up the,onCreate you called a method buildApiClient()  See this method in your main Activity.

As mentioned earlier, the Google API Client is the main entry point to Google Play services. To create one, you need to specify the number of things:

  • Location of Connection Callbacks. The Google API Client is highly asynchronous in nature, and needs functions implemented somewhere that it calls when it’s connected (onConnected) and when the connection is suspended (onConnectionSuspended). In this lab, you’ll code these in this activity, so you set the location for the connection callbacks to this.
  • Location of Connection Failed callback. It also needs you to specify the location of the onConnectionFailed function which runs if there’s a failure in the connection
  • The APIs that you want to use. Google Play services support lots of APIs, so you specify which ones you want to connect to here. For Geofencing you use the Location APIs.

Building the Intent Service

A pending intent generates an intent when the transition event occurs. In the last step, you created a pending intent from an intent defined by a class called,GeofencesTransitionsIntentService and you’ll add that in this step.

Add a new class to your app called GeofenceTransitionsIntentService.First, we should make sure it and extends IntentService handles the incoming Intent correctly. Following is the code:

package com.shah.kaushal28.fence;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import android.util.Log;

import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofenceStatusCodes;
import com.google.android.gms.location.GeofencingEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Kaushal28 on 4/26/2017.
 */

public class GeofenceTransitionsIntentService extends IntentService {
    protected static final String TAG = "GeofenceTransitionsIS";

    public GeofenceTransitionsIntentService() {
        super(TAG);  // use TAG to name the IntentService worker thread
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        GeofencingEvent event = GeofencingEvent.fromIntent(intent);
        if (event.hasError()) {
            Log.e(TAG, "GeofencingEvent Error: " + event.getErrorCode());
            return;
        }
        String description = getGeofenceTransitionDetails(event);
        sendNotification(description);
    }

    private static String getGeofenceTransitionDetails(GeofencingEvent event) {
        String transitionString =
                GeofenceStatusCodes.getStatusCodeString(event.getGeofenceTransition());
        List triggeringIDs = new ArrayList();
        for (Geofence geofence : event.getTriggeringGeofences()) {
            triggeringIDs.add(geofence.getRequestId());
        }
        return String.format("%s: %s", transitionString, TextUtils.join(", ", triggeringIDs));
    }

    private void sendNotification(String notificationDetails) {
        // Create an explicit content Intent that starts MainActivity.
        Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);

        // Get a PendingIntent containing the entire back stack.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class).addNextIntent(notificationIntent);
        PendingIntent notificationPendingIntent =
                stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

        // Get a notification builder that's compatible with platform versions >= 4
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

        // Define the notification settings.
        builder.setColor(Color.RED)
                .setContentTitle(notificationDetails)
                .setContentText("Click notification to return to App")
                .setContentIntent(notificationPendingIntent)
                .setAutoCancel(true);

        // Fire and notify the built Notification.
        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(0, builder.build());
    }
}

You’re now ready to test your application. Set a geofence near you (Change the parameters in the constants class), and enter that into the class.Constants Run the app, and walk around the area, seeing if you can trigger an alert on your phone as you enter or leave it. It’s pretty cool, right? If You are Beginner Learn Android application development from the scratch


If you really liked the article, please subscribe to our YouTube Channel for videos related to this article.Please find us on Twitter and Facebook.

4 COMMENTS

  1. Hi, thanks for the tutorial, I just have a problem: when I run the app in the emulator and change its longitude and latitude I don’t recieve a notification and nothing

  2. Hello, I have some problems with application, when I am trying to press on button Add geofence nothing is happen, can someone help me ?

LEAVE A REPLY

Please enter your comment!
Please enter your name here