Beacons in Swift and Android
In this post we will identify the key concepts needed to implement beacons in our Android and iOS apps, using the iBeacon protocol, resulting in a push notification when a user gets close to any of our beacons.
- iOS supports the iBeacon protocol out of the box on their own SDK, so no previous installation is needed.
#Android
- Android requires the use of a third-party library to make it work, so we went with AltBeacon, which is pretty much the only one available. We will add it to our build.gradle with the following line:
- Create a new class variable
- Now we will receive beacon updates, only if the user has allowed "location always" permission on the app. To request it, simply fill the plist properties "Privacy - Location Always and When In Use Usage Description" and "Privacy - Location Always Usage Description" and call*
- If the user accepted the permission, we will now receive callbacks on the didEnterRegion method:
Reference tutorial: https://www.raywenderlich.com/152330/ibeacon-tutorial-ios-swift
- We will also handle beacons in our Application class, which will have to implement the following interfaces:
BootstrapNotifier, BeaconConsumer, RangeNotifier
- We also need 4 class variables:
- The implemented interfaces will require us to implement their methods.
Here we are only interested on Ranging, which will tell us when a user enters the range of some specific Beacons.
The onBeaconServiceConnect method handles the initialisation of the Beacon Service on app launch.
Prerequisites
#Swift- iOS supports the iBeacon protocol out of the box on their own SDK, so no previous installation is needed.
#Android
- Android requires the use of a third-party library to make it work, so we went with AltBeacon, which is pretty much the only one available. We will add it to our build.gradle with the following line:
implementation 'org.altbeacon:android-beacon-library:2+' // If using Gradle plugin version < 3, use "compile" instead of "implementation"
Setup
#Swift
- We will manage beacons on our AppDelegate class, so now it has to implement CLLocationManagerDelegate.- Create a new class variable
let locationManager = CLLocationManager()referencing the location manager, from the core location library, inside the didFinishLaunchingWithOptions method we will set the AppDelegate as its delegate
locationManager.delegate = self- For each beacon (CLBeaconRegion)* we want to monitor, we now just need to call
locationManager.startMonitoring(for: beaconRegion)* A CLBeaconRegion can be created from primitive values where "name" can be any String value you choose, to be used only internally to identify the region:
CLBeaconRegion(proximityUUID: UUID(uuidString: uuidString), major: CLBeaconMajorValue(majorInt), minor: CLBeaconMinorValue(minorInt), identifier: name)
- Now we will receive beacon updates, only if the user has allowed "location always" permission on the app. To request it, simply fill the plist properties "Privacy - Location Always and When In Use Usage Description" and "Privacy - Location Always Usage Description" and call*
locationManager.requestAlwaysAuthorization()
* More detailed information on how to request permissions is not covered on this post, so please refer to the Apple documentation- If the user accepted the permission, we will now receive callbacks on the didEnterRegion method:
// Called when entering a beacon's region func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { guard let region = region as? CLBeaconRegion else { return } print("Beacon didEnterRegion! %@ ", region.description) // send push or do any other stuff }- We can also monitor when a user exits a beacon region if needed, along with other monitoring errors callbacks:
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { guard region is CLBeaconRegion else { return } print("Beacon didExitRegion! ", region.description) } func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { print("Failed monitoring beacon region: \(error.localizedDescription)") } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Beacon Location manager failed: \(error.localizedDescription)") }- To send a push for iOS 9.0 up to iOS 11.0, we can use the following code:
func sendPushNotification() { // Send notification if #available(iOS 10.0, *) { // Set Notification Content let content = UNMutableNotificationContent() content.title = "Entered Region" content.body = "There is a beacon nearby!" content.sound = .default() // Send notification let request = UNNotificationRequest(identifier: "BeaconNoticationId", content: content, trigger: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) } else { // Fallback on earlier versions let notification = UILocalNotification() notification.alertBody = "There is a beacon nearby!" notification.fireDate = Date() notification.repeatInterval = NSCalendar.Unit.minute UIApplication.shared.cancelAllLocalNotifications() UIApplication.shared.scheduledLocalNotifications = [notification] } }And that's it. With those lines and some extra adaptation to your own Swift code, your app will be beacon-ready and connected to the real world!
Reference tutorial: https://www.raywenderlich.com/152330/ibeacon-tutorial-ios-swift
#Android
For Android it is a bit more complex, but not that much.- We will also handle beacons in our Application class, which will have to implement the following interfaces:
BootstrapNotifier, BeaconConsumer, RangeNotifier
- We also need 4 class variables:
private BeaconManager beaconManager;
private RegionBootstrap regionBootstrap;
private BackgroundPowerSaver backgroundPowerSaver;
private Region region = new Region("someRegionId", null, null, null);
- Below all the code required to initialise the beacons:
private void initBeacons() {
beaconManager = BeaconManager.getInstanceForApplication(this);
beaconManager.getBeaconParsers().clear();
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout(LAYOUT_IBEACON));
beaconManager.bind(this);
// beaconManager.setBackgroundScanPeriod(1000);
// beaconManager.setBackgroundBetweenScanPeriod(5000);
// wake up the app when any beacon is seen
regionBootstrap = new RegionBootstrap(this, region);
// Simply constructing this class and holding a reference to it enables
// auto battery saving of about 60%
backgroundPowerSaver = new BackgroundPowerSaver(this);
}
You can find your Beacon Layout String depending on the manufacturer below:
https://beaconlayout.wordpress.com/
https://beaconlayout.wordpress.com/
- The implemented interfaces will require us to implement their methods.
Here we are only interested on Ranging, which will tell us when a user enters the range of some specific Beacons.
The onBeaconServiceConnect method handles the initialisation of the Beacon Service on app launch.
@Override
public void onBeaconServiceConnect() {
if (beaconManager != null) {
try {
// Monitoring
beaconManager.removeAllMonitorNotifiers();
beaconManager.addMonitorNotifier(this);
beaconManager.startMonitoringBeaconsInRegion(region);
// Ranging
beaconManager.removeAllRangeNotifiers();
beaconManager.addRangeNotifier(this);
beaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
The didRangeBeaconsInRegion method is the most important one. It will be called in intervals, as defined in the initialisation of the BeaconManager and will provide an array with all the beacons that interacted with the user. The array will be empty in case the user didn't get close to any Beacon.
@Override
public void didRangeBeaconsInRegion(Collection < Beacon > collection, Region region) {
}
In that method is where we need to implement all our logic of what our app should do when interacting with a Beacon. In case we want to send a notification to the user, check out the Android documentation here: https://developer.android.com/training/notify-user/build-notification
!!This method will be called every interval, so you might want to save in the Shared Preferences which beacons have already interacted with the user.
- Lastly, we can keep the monitoring methods empty as they don't provide any extra information:
@Override
public void didEnterRegion(Region region) {
// Do nothing
}
@Override
public void didExitRegion(Region region) {
// Do nothing
}
@Override
public void didDetermineStateForRegion(int i, Region region) {
// Do nothing
}
And that's it! Feel free to let us know of any questions in the comments below :)
Comments
Post a Comment