The Beacons App is a Gluon code sample. For a full list of Gluon code samples, refer to the Gluon website.

In this tutorial, we’ll explain how to create the Beacons application that can be deployed on desktop, Android and iOS devices. It makes use of the Bluetooth Low Energy API on Gluon Charm Down, to scan for beacons.

Before you start, be sure that you have checked the list of prerequisites, and you have installed the Gluon plugin for your IDE. Otherwise follow these instructions.

Note: This tutorial will use the plugin for IntelliJ, but it works as well on NetBeans and Eclipse.

Code: The code for this project can be found in the samples repository at GitHub. The sample is located under the directory beacons. The reader can clone this repository or create the entire project from scratch, based on the following steps.

BLE and Beacons

Bluetooth Low Energy (BLE), a wireless personal area network technology, while providing the same range of communications as classic bluetooth, considerably reduces the power consumption and cost.

Based on this principle, beacons are small wireless devices designed to transmit a simple radio signal continuously, which can be received by nearby smartphones. They transmit only their ID and their signal power, and the smartphone can read those values and estimate the distance to that device.

Among other uses, one of the possible applications for it is for proximity sensing: physical proximity to certain devices equipped with this technology can be estimated based on their radio receiver’s rssi value. This allows for indoor positioning, indoor behavior tracking, in-store interaction between customers and retailers, indoor navigation, and so on.

iBeacons and other beacon protocols

iBeacon is a brand name created by Apple in 2014, a new location technology that lets any phone using iOS7 or newer constantly scan the environment for BLE devices such as beacons. It is a proprietary, closed standard. Other standards like AltBeacon are open. Google launched Eddystone, its open source beacon, in 2015.

Hardware

If you want to use this technology in production, you will need real beacons equipped with BLE, and implementing one of the mentioned protocols. Those are small devices that can be purchased online from different vendors like Estimote, Kontakt or Gimbal. You can find an extensive list of vendors here.

For development, you can simulate a beacon with different apps on Android and iOS.

Charm Down Implementation Details

Under the hood, Charm Down creates the implementation for accessing native bluetooth services on Android and iOS, so developers don’t need to worry about this.

Beacon Protocol

Beacons transmit small amounts of data. Beacon IDs consists of a maximum of three values: A universally unique identifier (UUID), a major value and a minor value. Every beacon also transmits information about its signal power.

According to the Bluetooth core specification, a beacon advertises a data package called the Scan Response Data. The scan response is divided into what are called AD structures.

Usually, these will be the 31 bytes sent by the beacon on every transmission, corresponding to two AD structures:

Scan Response Data. Source: https://www.pubnub.com/blog/2015-04-14-building-android-beacon-android-ibeacon-tutorial-overview/

Android

Android API provides a Bluetooth adapter, and a BLE scanner:

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
scanner.startScan(new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        ScanRecord scanRecord = result.getScanRecord();
        // process scanRecord
    }
});

The resulting ScanRecord object can be processed following the mentioned bytes structure to obtain a ScanDetection instance that can be passed finally to the service callback.

ScanDetection detection = processAD(scanRecord.getBytes());
callback.accept(detection);

iOS

iBeacon is closed protocol, but iOS7+ provides a Location framework for dealing with different location services like GPS and beacons. A CLBeaconRegion object defines a type of region that is based on the device’s proximity to a Bluetooth beacon.

While a beacon is in range, the startRangingBeaconsInRegion method is used to begin receiving notifications when the relative distance to the beacon changes.

These notifications will be received through the locationManager:didRangeBeacons:inRegion method, returning an array of CLBeacon with the beacons in range, sorted by proximity. Selecting the first one on that array allows generating a ScanDetection instance that can be passed to the service callback.

Creating the project

Let’s create a new project using the Gluon plugin. Open IntelliJ and click Create New Project…​ and select Gluon on the left. Select Gluon Mobile - Multiple View Project with Glisten-Afterburner from the list of available Projects:

Plugins Window

Add the package name and change the main class name.

Package and Main

Press Next, and add the Beacons and Settings views.

Views

Press Next and select a valid JDK 8:

JDK8

Press Next and add the project name, and modify the location if required.

Name and Location

Press Next, and finally import the gradle project. Review the settings and click Ok.

Gradle Import

The project will be created and opened. The main class Beacons is shown, containing the code to instantiate the views and the navigation drawer.

Beacons class

Modifying the project

Let’s start modifying the default project to create our Beacons application.

The views are created with FXML. And these files can be edited with Scene Builder. Since the version 8.3.0, the Gluon Charm dependencies are included, and you can easily design Gluon Mobile views.

First of all, edit the beacons.fxml file with Scene Builder, remove the content of the VBox, and add a GridPane, populated with some labels to show the beacon values. Also three circles will inform about its proximity (unknown, far, near or immediate):

Beacons FXML

Once-the fx:id tags have been added to each control, select View→Show sample Controller Skeleton from Scene Builder, copy the content and paste it in the BeaconsPresenter.

Beacons FXML

Charm Down BLE service

The Charm Down API provided for beacon detection in Charm Down consists of two classes: Configuration and ScanDetection, the Proximity enum, and the service BleService and service factory BleServiceFactory interfaces.

Before we can use this service, we need to add it to the build script. Right click in the root project, and select the Gluon Mobile Settings option. Locate the ble service and select it.

Device Plugin

Press OK, and it will be included in the list of plugins:

build.gradle
jfxmobile {
    downConfig {
        version = '3.8.0'
        plugins 'ble', 'display', 'lifecycle', 'statusbar', 'storage'
    }
    ...
}

Refresh the project (right click on the project, Gradle→Refresh Gradle Project), you should find charm-down-plugin-ble-3.8.0.jar under Project and External Dependencies.

To use the service, you just need to call Services.get(BleService.class). It will return an optional with the service, if it is available for the platform. In this particular case, BLE service is available on Android and iOS.

Use Configuration to set the UUID of the group of devices with that same ID that will be scanned.

When a beacon is detected, an instance of ScanDetection is created that provides the UUID, major and minor id, rssi, and proximity (unknown, far, near, immediate).

To use the service, you only need to provide a callback to process the ScanDetection object that will be returned from the scan implementation.

The following code snippet does everything that is required to be notified of the presence of beacons with the provided UUID.

Let’s add scanDetection, an observable wrapper of ScanDetection. We get an instance of the BleService, and create a callback that will set the scan detection into scanDetection. With a new detection, all the labels will be updated with the detection features (UUID, major id, minor id, rssi and proximity).

BeaconsPresenter.java
public class BeaconsPresenter extends GluonPresenter<Beacons> {
    ...
    private final ObjectProperty<ScanDetection> scanDetection = new SimpleObjectProperty<ScanDetection>() {
        @Override
        protected void invalidated() {
            labelUUID.setText(get().getUuid());
            labelMajor.setText(String.valueOf(get().getMajor()));
            labelMinor.setText(String.valueOf(get().getMinor()));
            labelRSSI.setText(String.valueOf(get().getRssi()));
            labelDistance.setText(get().getProximity().name());
        }
    };

    public void initialize() {
            Services.get(BleService.class)
                .map(bleService -> {

                Consumer<ScanDetection> callback = (ScanDetection t) -> {
                    javafx.application.Platform.runLater(() -> scanDetection.set(t));
                };
                ...
                Configuration conf = new Configuration(settings.getUuid());
                bleService.startScanning(conf, callback);
                    ...
                    return null;
            }).orElseGet(() -> {
                // BLE service not available for this platform
                ...
                return null;
            });
    }
}

Note that the callback handler is called on a non-JavaFX Thread. Hence, to update user interface components we have to call Platform.runLater().

Let’s bind now the circles' stroke property to the proximity of the detection, adding also a DropShadow effect to the related circle.

BeaconsPresenter.java
public class BeaconsPresenter extends GluonPresenter<Beacons> {
    ...
    public void initialize() {
        ...
        circleFar.setFill(null);
        circleFar.setStroke(Color.TRANSPARENT);
        circleFar.strokeProperty().bind(Bindings.createObjectBinding(() -> {
            if (scanDetection.get() != null && scanDetection.get().getProximity().equals(Proximity.FAR)) {
                circleFar.setEffect(new DropShadow(10, Color.GREEN));
                return Color.GREEN;
            }
            circleFar.setEffect(null);
            return Color.GRAY;
        }, labelDistance.textProperty()));
        circleNear.setFill(null);
        circleNear.setStroke(Color.TRANSPARENT);
        circleNear.strokeProperty().bind(Bindings.createObjectBinding(() -> {
            if (scanDetection.get() != null && scanDetection.get().getProximity().equals(Proximity.NEAR)) {
                circleNear.setEffect(new DropShadow(15, Color.GREEN));
                return Color.GREEN;
            }
            circleNear.setEffect(null);
            return Color.GRAY;
        }, labelDistance.textProperty()));
        circleImmediate.setFill(null);
        circleImmediate.setStroke(Color.TRANSPARENT);
        circleImmediate.strokeProperty().bind(Bindings.createObjectBinding(() -> {
            if (scanDetection.get() != null && scanDetection.get().getProximity().equals(Proximity.IMMEDIATE)) {
                circleImmediate.setEffect(new DropShadow(20, Color.GREEN));
                return Color.GREEN;
            }
            circleImmediate.setEffect(null);
            return Color.GRAY;
        }, labelDistance.textProperty()));
        ...
    }
}

Setting the UUID

Let’s create a Settings class, that will contain the basic settings for this application. For now, just the uuid string that will be scanned. By default we will use this one: "74278BDA-B644-4520-8F0C-720EAF059935".

Settings.java
public class Settings {

    public static final String UUID = "74278BDA-B644-4520-8F0C-720EAF059935";

    private final StringProperty uuid = new SimpleStringProperty(UUID);

    public final StringProperty uuidProperty() {
            return this.uuid;
    }

    public final String getUuid() {
            return this.uuidProperty().get();
    }

    public final void setUuid(final String uuid) {
            this.uuidProperty().set(uuid);
    }
}

Start/Stop scanning

Finally, we can add a pair of buttons to the AppBar, to start and stop scanning.

BeaconsPresenter.java
public class BeaconsPresenter {
    ...
    public void initialize() {
        ...
        final Button buttonScan = MaterialDesignIcon.BLUETOOTH_SEARCHING.button();
        final Button buttonStop = MaterialDesignIcon.STOP.button();

        beacons.showingProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue) {
                buttonScan.setOnAction(e -> {
                    bleService.stopScanning();
                    Configuration conf = new Configuration(settings.getUuid());
                    bleService.startScanning(conf, callback);
                    buttonStop.setDisable(false);
                });
                buttonStop.setOnAction(e -> {
                    bleService.stopScanning();
                    buttonStop.setDisable(true);
                });
                labelStatus.textProperty().bind(new When(buttonScan.disableProperty())
                        .then("Scanning for " + settings.getUuid())
                        .otherwise("Stopped"));
                buttonScan.disableProperty().bind(buttonStop.disableProperty().not());
                buttonStop.setDisable(true);

                AppBar appBar = getApp().getAppBar();
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
                        getApp().getDrawer().open());
                appBar.setTitleText("Beacons");
                appBar.getActionItems().addAll(buttonScan, buttonStop);
            }
        });
        ...
    }
}

If we run the application by selecting the Gradle task Other→run, this is what we should see:

Beacons App

Note that the Gradle Tasks window can be enabled from the Window menu, selecting Show View→Other…​ and then selecting from the Gradle folder Gradle Tasks.

Mobile requirements

On Android, the AndroidManifest.xml file has to include the bluetooth required permissions.

AndroidManifest.xml
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

On iOS, the Default-Info.plist file in the iOS directory in the sample, has to include the key NSLocationAlwaysUsageDescription to require location services. You can change its description.

Default-Info.plist
<key>NSLocationUsageDescription</key>
<string>This app needs location services</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs location services</string>

When running the application, notice that if bluetooth is not enabled, a popup will ask you to enable it (both in iOS and Android), and for iOS, the first time you click on the start scanning button, a popup will ask you to enable location services. This can be changed in Settings → Security → Location.

Deploying on mobile

Let’s deploy on Android, using androidInstall task from the Gradle menu. Activate Bluetooth if needed. Using a real beacon or another device as a beacon, configured with the given UUID, press start on the app and you should see the beacon:

Android Beacons App

Settings

Finally, let’s add a SettingsPane in order to edit the UUID string.

Edit the settings.fxml, remove the VBox content and add a SettingsPane in the center of the view.

Settings FXML

In SettingsPresenter, inject the Settings, and create a DefaultOption for editing the UUID, with a TextField. Given the length of the UUID string, we’ll set the vertical layout for this option. Add the uuidOption to the settingsPane, and create an Action item to restore the default value.

SettingsPresenter.java
public class SettingsPresenter extends GluonPresenter<Beacons> {

    @FXML
    private View settings;

    @Inject
    private Settings config;

    @FXML
    private SettingsPane settingsPane;

    public void initialize() {
        settings.setShowTransitionFactory(BounceInRightTransition::new);

        final Option<StringProperty> uuidOption = new DefaultOption<>(MaterialDesignIcon.BLUETOOTH_SEARCHING.graphic(),
                        "Set the UUID", "Set the UUID to be scanned", "Beacon Settings", config.uuidProperty(), true);

        ((OptionBase<StringProperty>) uuidOption).setLayout(Orientation.VERTICAL);

        settingsPane.getOptions().add(uuidOption);
        settingsPane.setSearchBoxVisible(false);

        settings.showingProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue) {
                AppBar appBar = getApp().getAppBar();
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
                        getApp().getDrawer().open()));
                appBar.setTitleText("Settings");
                appBar.getActionItems().add(MaterialDesignIcon.SYNC.button(e -> config.setUuid(Settings.UUID)));
            }
        });
    }
}

Now, deploy the application again, go to Settings and change the UUID to the required value.

Editing UUID

Adding other settings or settings persistence are left out of this tutorial. The reader can have a look at the Notes tutorial.

Conclusions

Throughout this post we’ve covered in detail the basic steps to use the Bluetooth Low Energy API within a Multi View project.

If you have made it this far, congratulations, we hope this sample and the documentation was helpful to you! In case you have any questions, your first stop should be the Gluon support page, where you can access the latest documentation and the Gluon knowledge base. Gluon also recommends the community to support each other over at the Gluon StackOverflow page. Finally, Gluon offers commercial support as well, to kick start your projects.

While the functionality provided in Charm Down to use Bluetooth Low Energy API to scan for beacons devices from a Java Client Application could be very simple, and there are more usecases for BLE applications, we encourage developers to create pull requests if they want to contribute to Charm-Down development.