I am trying to use the iBeacon library outside an activity context, to write its effective implementation but i am missing something as i am not getting the desired functionality.
It most likely seems that the service is not bound to my newly created class.... and i am not sure what i am missing here...
Here is my custom class:
public class BeaconUtils implements IBeaconConsumer, RangeNotifier, IBeaconDataNotifier {
private Context context;
protected static final String TAG = "BeaconUtils";
public BeaconUtils(Context context) {
this.context = context;
verifyBluetooth((Activity) context);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static void verifyBluetooth(final Activity activity) {
try {
if (!IBeaconManager.getInstanceForApplication(activity).checkAvailability()) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Bluetooth not enabled");
builder.setMessage("Please enable bluetooth in settings and restart this application.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
activity.finish();
//System.exit(0);
}
});
builder.show();
}
} catch (RuntimeException e) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Bluetooth LE not available");
builder.setMessage("Sorry, this device does not support Bluetooth LE.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
activity.finish();
//System.exit(0);
}
});
builder.show();
}
}
@Override
public void onIBeaconServiceConnect() {
Region region = new Region("MainActivityRanging", null, null, null);
try {
ZonizApplication.iBeaconManager.startMonitoringBeaconsInRegion(region);
ZonizApplication.iBeaconManager.setRangeNotifier(this);
ZonizApplication.iBeaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
e.printStackTrace();
}
ZonizApplication.iBeaconManager.setMonitorNotifier(new MonitorNotifier() {
@Override
public void didEnterRegion(Region region) {
//createNotification();
//Log.i(TAG, "I am in the range of an IBEACON: "+region.getProximityUuid());
//SyncServiceHelper.getInst().trySyncOffers(region.getProximityUuid());
}
@Override
public void didExitRegion(Region region) {
NotificationManager mNotificationManager;
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.cancel(0);
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
Log.i(TAG, "I have just switched from seeing/not seeing iBeacons: " + region.getProximityUuid());
createNotification();
}
});
}
@Override
public Context getApplicationContext() {
return this.context;
}
@Override
public void unbindService(ServiceConnection serviceConnection) {
ZonizApplication.iBeaconManager.unBind(this);
}
@Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
ZonizApplication.iBeaconManager.bind(this);
return true;
}
@Override
public void iBeaconDataUpdate(IBeacon iBeacon, IBeaconData iBeaconData, DataProviderException e) {
if (e != null) {
Log.d(TAG, "data fetch error:" + e);
}
if (iBeaconData != null) {
String displayString = iBeacon.getProximityUuid() + " " + iBeacon.getMajor() + " " + iBeacon.getMinor() + "\n" + "Welcome message:" + iBeaconData.get("welcomeMessage");
Log.d(TAG, displayString);
}
}
@Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons, Region region) {
for (IBeacon iBeacon : iBeacons) {
iBeacon.requestData(this);
String displayString = iBeacon.getProximityUuid() + " " + iBeacon.getMajor() + " " + iBeacon.getMinor() + "\n";
Log.d(TAG, displayString);
}
}
public void createNotification() {
// Prepare intent which is triggered if the
// notification is selected
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Build notification
// Actions are just fake
//if (currentUIID != null && !currentUIID.isEmpty()) {
Notification noti = new Notification.Builder(context)
.setContentTitle("New beacon in range")
.setContentText("You are currently in the range of a new beacon.").setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pIntent).build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// hide the notification after its selected
noti.flags |= Notification.FLAG_AUTO_CANCEL;
noti.defaults |= Notification.DEFAULT_SOUND;
noti.defaults |= Notification.DEFAULT_VIBRATE;
notificationManager.notify(0, noti);
//}
}
}
I am instantiating the beacon manager in my Application class:
iBeaconManager = IBeaconManager.getInstanceForApplication(this);
and i am binding this manager in my activity in the onCreate()
and onDestroy()
methods.
What am i missing?
I am instantiating my custom class in the activity like this:
private BeaconUtils beaconUtilities = new BeaconUtils(MainActivity.this);
The binding part:
beaconUtilities = new BeaconUtils(MainActivity.this);
ZonizApplication.iBeaconManager.bind(beaconUtilities);
I was able to get the code above working with a few modifications:
I commented out the verifyBluetooth((Activity) context);
line because it was crashing my MainActivity
with a NullPointerException
. If you see your activity launch properly, you may not need to do this. If you don't see it launch, then BeaconUtils
will be disposed of by Android along with your MainActivity
and can't get any callbacks when it sees iBeacons.
I had to change the createNotification
method to get it to work -- the original code did not display a notification for me, although I am not exactly clear why. The code I got to work is:
private void createNotification() {
NotificationCompat.Builder builder =
new NotificationCompat.Builder(context)
.setContentTitle("New beacon in range")
.setContentText("You are currently in the range of a new beacon.")
.setSmallIcon(R.drawable.ic_launcher);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(new Intent(context, MainActivity.class));
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
builder.setContentIntent(resultPendingIntent);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, builder.build());
}
Once I made this change, the log displayed the following line after launch:
01-21 12:52:43.112 I/BeaconUtils﹕ I have just switched from seeing/not seeing iBeacons: null
And the following notification was displayed:
In general, the best way to troubleshoot problems like this is to add log messages. In the case where you are not seeing many log messages, I would add them at the top of each lifecycle and callback method, including onCreate
, onIBeaconServiceConnect()
, etc. Once you do this, any messages you should see but don't give you a good idea where something is going wrong.
A few other tips:
Each time you launch your app from Eclipse/Android Studio, be sure to make some code change, otherwise the app won't be uninstalled and reinstalled, and the iBeacon Service won't restart. Unless the service is restarted, you won't get new entered region notifications for iBeacons that were already detected.
Be careful that you only have one monitorNotifier or rangingNotifier on your iBeaconManager. Whatever is the last notifier set is the one that will get all the callbacks.
If you don't see that your on onIBeaconServiceConnect()
method is being called (best to do this with a log line), then stop everything until you get that working.
In general, the IBeaconConsumer
interface is designed to work with an Activity
, Service
or Application
instance. There's nothing wrong doing this with a custom class like your BeaconUtils
, but you have to be extra careful that your context is set properly, and that whatever is holding a reference to your custom object doesn't dispose of it during the Android lifecycle. Edit: Also, when making custom bindService
and unbindService
methods, the methods must chain to the equivalent methods on the context. I am surprised this works at all as-is. See my related answer here: https://stackoverflow.com/a/21298560/1461050