Question

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);
Was it helpful?

Solution

I was able to get the code above working with a few modifications:

  1. 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.

  2. 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:

enter image description here

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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

OTHER TIPS

For anyone else interested, I have used the great work done here by the original author and implemented it as a PhoneGap/Cordova plugin. Please feel free to contribute, comment, etc at:

https://github.com/TheMattRay/iBeaconGap

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top