Radius Networks Developer Blog

Building Apps With Eddystone

David G. Young 14 Jul 2015 Permalink

With Eddystone™, the new beacon format from Google, developers have more options than ever in buidling beacon applications. To understand the basics of working with Eddystone, it’s useful to show the process of putting together an app. If you are new to Eddystone, check out our companion blog post, Introducing Eddystone.

This example will focus on making an Android app using the Android Beacon Library, which fully supports Eddystone. If you want to try this yourself, you’ll need an Android device with 4.3+ and a computer with an Android Studio development environment. Of course, if you actually want the app to respond to beacons, you’ll need a beacon that supports Eddystone, too.

Creating Your Project

To begin, create a new project in Android Studio, selecting the “Blank Activity” template and naming your activity “RangingActivity”.

The first thing you need to do is add the Android Beacon Library into your dependencies. To do this, open up your build.gradle (module: app) file, which should look like this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.0'
}

Edit the above to add a line for Android Beacon Library support like this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.0'
    compile 'org.altbeacon:android-beacon-library:2.3.5+'
}

Coding the Activity

Now that you have beacon support in your project, you can edit the RangingActivity.java class to add beacon detection code. The boilerplate for the class that was Generated by Android Studio looks like this:

public class RangingActivity extends ActionBarActivity {

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


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_ranging, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

We need to make our Activity class implement two interfaces needed to range for beacons: BeaconConsumer and RangeNotifier. These define callback methods when the beacon scanning service is ready and when beacons are discovered in range. We also make a class instance variable for the BeaconManager, which is the main entry point for working with the Android Beacon Library. The top of the class definition should now look like this:

    public class RangingActivity extends ActionBarActivity implements BeaconConsumer, RangeNotifier {
        private BeaconManager mBeaconManager;

Next, we override the onResume method:

@Override
public void onResume() {
    super.onResume();
    mBeaconManager = BeaconManager.getInstanceForApplication(this.getApplicationContext());
    // Detect the main Eddystone-UID frame:
    mBeaconManager.getBeaconParsers().add(new BeaconParser().
            setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"));
    mBeaconManager.bind(this);
}

This method gets called when the Activity appears, and is a good place to initialize our beacon logic. Here, we get an instance of the BeaconManager and then configure it to detect Eddystone-UID frames. The setBeaconLayout method will tell the Android Beacon Library how to decode an Eddystone UID frame. You don’t need to understand the layout string – just know that his defines the format of the frame to the beacon parser. The last line of the method binds the app to the library’s beacon scanning service, so it can start looking for beacons. When it is ready to find beacons, it calls back to the onBeaconServiceConnect method, which we will define like this:

@Override
public void onBeaconServiceConnect() {
    Region region = new Region("all-beacons-region", null, null, null);
    try {
        mBeaconManager.startRangingBeaconsInRegion(region);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    mBeaconManager.setRangeNotifier(this);
}

In this method, we define what’s called a beacon Region, which is a way to set a matching pattern of what beacons you are interested in. By creating an “all-beacons-region” with null values for all identifiers, this tells the library that we want to know about any beacon we see. The startRangingBeaconsInRegion method tells the library to start looking for beacons that match this region definition. The last line sets the rangeNotifier to this class, so our the same RangingActivity class will get callbacks each time a beacon is seen. That callback method can be defined as follows:

@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
    for (Beacon beacon: beacons) {
        if (beacon.getServiceUuid() == 0xfeaa && beacon.getBeaconTypeCode() == 0x00) {
            // This is a Eddystone-UID frame
            Identifier namespaceId = beacon.getId1();
            Identifier instanceId = beacon.getId2();
            Log.d("RangingActivity", "I see a beacon transmitting namespace id: " + namespaceId +
                    " and instance id: " + instanceId +
                    " approximately " + beacon.getDistance() + " meters away.");
            runOnUiThread(new Runnable() {
                public void run() {
                    ((TextView)RangingActivity.this.findViewById(R.id.message)).setText("Hello world, and welcome to Eddystone!");
                }
            });
        }
    }
}

This method gets called about once per second with a list of all beacons that are visible. The method loops through this list of beacons and checks to see if any of them are Eddystone-UID frames. You can tell if a beacon is an Eddystone beacon because it will have a serviceUuid of 0xfeaa, and a beaconTypeCode of x00. (For the Eddystone-TLM frame, the beaconTypeCode will be 0x20 and for Eddystone-URL the beaconType code will be 0x10).

If it is an Eddystone-UID frame, we access the two identifiers (the namespace identifier and the instance identifier) as well as the estimated distance and log them with the Log.d statement above.

Next we adjust the user interface of our app to say “Hello world, and welcome to Eddystone!”

The last thing we need to do to our RangingActivity class is unbind from the BeaconManager when the activity closes. This will tell the library it can stop being active, which will save battery. We put this code in the onPause method:

@Override
public void onPause() {
    super.onPause();
    mBeaconManager.unbind(this);
}

If you have made all of the changes above, your activity class should look like this:

public class RangingActivity extends ActionBarActivity implements BeaconConsumer, RangeNotifier {

    private BeaconManager mBeaconManager;

    @Override
    public void onResume() {
        super.onResume();
        mBeaconManager = BeaconManager.getInstanceForApplication(this.getApplicationContext());
        // Detect the main Eddystone-UID frame:
        mBeaconManager.getBeaconParsers().add(new BeaconParser().
                setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"));
        mBeaconManager.bind(this);
    }

    public void onBeaconServiceConnect() {
        Region region = new Region("all-beacons-region", null, null, null);
        try {
            mBeaconManager.startRangingBeaconsInRegion(region);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mBeaconManager.setRangeNotifier(this);
    }

    @Override
    public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
        for (Beacon beacon: beacons) {
            if (beacon.getServiceUuid() == 0xfeaa && beacon.getBeaconTypeCode() == 0x00) {
                // This is a Eddystone-UID frame
                Identifier namespaceId = beacon.getId1();
                Identifier instanceId = beacon.getId2();
                Log.d("RangingActivity", "I see a beacon transmitting namespace id: " + namespaceId +
                        " and instance id: " + instanceId +
                        " approximately " + beacon.getDistance() + " meters away.");
                runOnUiThread(new Runnable() {
                    public void run() {
                        ((TextView)RangingActivity.this.findViewById(R.id.message)).setText("Hello world, and welcome to Eddystone!");
                    }
                });
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mBeaconManager.unbind(this);
    }

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_ranging, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Updating the Layout

If you have the above code in place, you’ll notice there is an undefined symbol error for R.id.message. This is the identifier of the field we want to update on our Activity layout when a beacon appears. To fix this error, we need to modify the layout for the Ranging Activity so this TextView field has an identifier. Edit the file res/activity_ranging.xml and change the TextView definition to look like this:

<TextView android:text="@string/hello_world" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:id="@+id/message"/>

Running the App

When you run the app without a beacon nearby, it will display a simple “Hello world” message on a white screen. Once it comes in range of a beacon, this screen will change to show you “Hello world, and welcome to Eddystone!”

To try this out, turn off your beacon that supports Eddystone and launch the app. Turn on the beacon, and watch the app react!

If you don’t want to type everything in from above, you can download a working copy of the source code for this example app here.

Next Steps

Obviously, this is a very simple app, but it demonstrates the basics of working with Eddystone. More complex apps that support Eddystone may be built that react differently based on the presence of different beacons, and react with data coming from cloud-based servers. You can even make the apps perform special functions depending on how far you are away from the beacon—one meter, ten meters or more.

While this example just focuses on how to work with the main Eddystone-UID frame, you can see other coding examples of using the Eddystone-TLM and Eddystone-URL frames in the documentation for the Android Beacon Library.

Visit us here to access to more Radius Networks products that support Eddystone.