0

I am developing an Android app using AltBeacon Library from RadiusNetworks. I am using a nav-drawer (which launches activities, not fragments) to show different sections. Thus I have one main activity for the drawer which launches different activities by means of intents.

I would like to have nearby iBeacons info (ids and distance) available everywhere in the app. How can approach this?

I have tried the following (if we map my app structure with the reference one provided by RadiusNetworks):

  • BeaconReferenceApp: same as reference app. Launches MainActivity.
  • MainActivity: I have merged Monitoring and Ranging activities. This activity also manages the nav-drawer.
  • Section1: this one extends MainActivity and displays some info depending on iBeacons collected data.

In MainActivity I do the following:

public void onBeaconServiceConnect() {
        beaconManager.setRangeNotifier(new RangeNotifier() {
            @Override
            public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                for (Beacon beacon : beacons) {
                    Log.i("TAG","Beacon detected with id1: "+beacon.getId1()+" id2:"+beacon.getId2()+" id3: "+beacon.getId3()+" distance: "+beacon.getDistance());
                }
            }

        });

        try {
            beaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
        } catch (RemoteException e) {   }
    }

I can see in the log the iBeacon info, but just once (first detection). However, I would like to get this info continuously, everytime the iBeacon is seen (as happens in the reference app), so that I can pass it to my other activities.

How can I solve this? Should I use a different app structure (because of the nav-drawer) or am I using the library in a wrong way?

Thank you.

UPDATE: Code for MainActivity and BeaconReferenceApp.

BeaconReferenceApp:

public class BeaconReferenceApp extends Application implements BootstrapNotifier {

    private static final String TAG = "BeaconApp";
    private RegionBootstrap regionBootstrap;
    private BackgroundPowerSaver backgroundPowerSaver;
    private boolean haveDetectedBeaconsSinceBoot = false;
    private MainActivity mainActivity = null;

    public void onCreate() {
        super.onCreate();
        BeaconManager beaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this);
        beaconManager.getBeaconParsers().add(new BeaconParser().
                setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));


        Log.d(TAG, "setting up background monitoring for beacons and power saving");
        // wake up the app when a beacon is seen
        Region region = new Region("backgroundRegion", null, null, null);
        regionBootstrap = new RegionBootstrap(this, region);
        backgroundPowerSaver = new BackgroundPowerSaver(this);
    }

  public void didEnterRegion(Region arg0) {

        Log.d(TAG, "did enter region.");
        // regionBootstrap.disable();
        if (!haveDetectedBeaconsSinceBoot) {
            Log.d(TAG, "auto launching MainActivity");

            // The very first time since boot that we detect an beacon, we launch the MainActivity
            Intent intent = new Intent(this, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            this.startActivity(intent);
            haveDetectedBeaconsSinceBoot = true;
        } else {

               sendNotification();
        }

    }

public void didExitRegion(Region region) {}
public void didDetermineStateForRegion(int state, Region region) {}
public void setMainActivity(MainActivity activity) {this.mainActivity = activity;}

MainActivity:

public class MainActivity extends ActionBarActivity implements BeaconConsumer {

    protected FrameLayout frameLayout;
    protected ListView mDrawerList;
    protected String[] listArray = { "Item1", "Item2", "Item3", "Item4" };
    protected static int position;
    private static boolean isLaunch = true;
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle actionBarDrawerToggle;
    private BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this);


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        verifyBluetooth();
        beaconManager.bind(this);

        setContentView(R.layout.navigation_drawer_base_layout);

        frameLayout = (FrameLayout)findViewById(R.id.content_frame);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // set up the drawer's list view with items and click listener
        mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, listArray));
        mDrawerList.setOnItemClickListener(new OnItemClickListener() {

            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {

                openActivity(position);
            }
        });

        // enable ActionBar app icon to behave as action to toggle nav drawer
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeButtonEnabled(true);

        // ActionBarDrawerToggle ties together the proper interactions between the sliding drawer and the action bar app icon
        actionBarDrawerToggle = new ActionBarDrawerToggle(
                this,                       /* host Activity */
                mDrawerLayout,              /* DrawerLayout object */
                R.string.open_drawer,       /* "open drawer" description for accessibility */
                R.string.close_drawer)      /* "close drawer" description for accessibility */
        {

            public void onDrawerClosed(View drawerView) {
                getSupportActionBar().setTitle(listArray[position]);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
                super.onDrawerClosed(drawerView);
            }

            public void onDrawerOpened(View drawerView) {
                getSupportActionBar().setTitle(getString(R.string.app_name));
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
                super.onDrawerOpened(drawerView);
            }

            public void onDrawerSlide(View drawerView, float slideOffset) {
                super.onDrawerSlide(drawerView, slideOffset);
            }

            public void onDrawerStateChanged(int newState) {
                super.onDrawerStateChanged(newState);
            }
        };
        actionBarDrawerToggle.syncState();
        mDrawerLayout.setDrawerListener(actionBarDrawerToggle);


        /**
         * MainActivity is intended just to add navigation drawer in the app.
         * Open some activity with layout on launch. Check checking if this 
         * MainActivity is called first time when we are
         * opening our first activity.
         * */
        if(isLaunch){
            /**
             *Setting this flag false so that next time it will not open
             * first activity.
             * Use this flag because we are using MainActivity
             * as parent activity to our other activities.
             * In this case this base activity will always be call when any
             * child activity will launch.
             */
            isLaunch = false;
            openActivity(0);
        }
    }

    /**
     * Launching activity when any list item is clicked.
     */
    protected void openActivity(int position) {

        mDrawerLayout.closeDrawer(mDrawerList);
        MainActivity.position = position; //Setting currently selected position in this field so that it will be available in child activities.

        switch (position) {
            case 0:
                startActivity(new Intent(this, Item1.class));
                break;
            case 1:
                startActivity(new Intent(this, Item2.class));
                break;
            case 2:
                startActivity(new Intent(this, Item3.class));
                break;
            case 3:
                startActivity(new Intent(this, Item4.class));
                break;
            default:
                break;
        }
    }

    public void onDestroy() {
        super.onDestroy();
        beaconManager.unbind(this);
    }

    public void onResume() {
        super.onResume();
        ((BeaconReferenceApp) this.getApplicationContext()).setMainActivity(this);
        if (beaconManager.isBound(this)) beaconManager.setBackgroundMode(false);
    }

    public void onPause() {
        super.onPause();
        ((BeaconReferenceApp) this.getApplicationContext()).setMainActivity(null);
        if (beaconManager.isBound(this)) beaconManager.setBackgroundMode(true);
    }

    private void verifyBluetooth() {}

    public void onBeaconServiceConnect() {
        beaconManager.setRangeNotifier(new RangeNotifier() {
            @Override
            public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                for (Beacon beacon : beacons) {
                    Log.i("TAG", "Beacon detected with id1: " + beacon.getId1() + " id2:" + beacon.getId2() + " id3: " + beacon.getId3() + " distance: " + beacon.getDistance());
                }
            }

        });

        try {
            beaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
        } catch (RemoteException e) {   }
    }

}

2 Answers2

2

The easiest way to do this is to do the ranging in a custom android.app.Application class. Each Android application has exactly one of these and it is a good place to put code that you want to be global to the entire app. To define your own such class, you declare it in your manifest by adding an attribute to the like so:

<application
    ...
    android:name="MyApplication">

Then you can define a class with the same name that does ranging like this:

public class MyApplication extends Application implements BeaconConsumer {
    private BeaconManager mBeaconManager;

    public void onCreate() {
        super.onCreate();
        mbeaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this);
        mBeaconManager.bind(this);
    }
    public void onBeaconServiceConnect() {
        mBeaconManager.setRangeNotifier(new RangeNotifier() {
            @Override
            public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                for (Beacon beacon : beacons) {
                    Log.i("TAG","Beacon detected with id1: "+beacon.getId1()+" id2:"+beacon.getId2()+" id3: "+beacon.getId3()+" distance: "+beacon.getDistance());
                }
            }

        });

        try {
            mBeaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
        } catch (RemoteException e) {   }
    }

If you can perform all the logic in the Application class, this is all you need to do. However, you may need to forward the ranging data to individual activities or fragments. If so, the easiest way is to get a reference to the central Application class from the fragment or the activity like this:

MyApplication myApplication = ((MyApplication)this.getApplication());

You can then access centrally-stored data from the Application class, or tell the Application class to forward the didRangeBeaconsInRegion callback to your activity or fragment.

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • Hello David. Thank you for the answer on having the info available throughput the app. I had already defined an Application class, which contains exactly the same code as BeaconReferenceApp.java from the reference application, and launches my Main.java. The problem is that in my Main.java (which handles monitoring, ranging and nav-drawer) I just get the iBeacon info the first time is seen. –  Mar 08 '15 at 20:42
  • I'd need to see the code in your MainActivity that sets connects to the beacon service, as well as the code that gets the monitoring callback to be sure, but I suspect there is a conflict between what you have in the Application class and what is in the MainActivity class. Generally speaking, only one part of the application should be monitoring and ranging at any given time (because there can only be one monitorNotifier and only one rangeNotifier). This is why if you want to share this across many parts of the app I recommend doing it entirely inside the Application. – davidgyoung Mar 09 '15 at 16:21
  • Hello David, I have updated my question with code for both Application and MainActivity. Should I re-structure my app? Thanks for your time. –  Mar 09 '15 at 17:52
  • I don't see anything wrong at first glance. Are you saying you get a call to `didRangeBeaconsInRegion` once, but not a second time? And on your device you do not see the same problem with the reference app? – davidgyoung Mar 09 '15 at 20:46
  • That's it. In my app I get the Log.i log just once (first time Ibeacon is seen), whereas on the reference app (where monitoring and ranging are in separated activities) I get the log everytime I receive an Ibeacon message. The full app can be seen here: https://github.com/GITdanieljc/Beaconing –  Mar 09 '15 at 21:51
  • I ran the project, but could not reproduce the same results. I *do* see repeated ranging callbacks in the Activity: ```$ adb logcat | grep TAG``` then I see: ```I/TAG (15143): Beacon detected with id1: 2f234454-cf6d-4a0f-adf2-f4911ba9ffa6 id2:2 id3: 1 distance: 0.622195153249643 I/TAG (15143): Beacon detected with id1: 2f234454-cf6d-4a0f-adf2-f4911ba9ffa6 id2:2 id3: 1 distance: 0.6113168807527769 I/TAG (15143): Beacon detected with id1: 2f234454-cf6d-4a0f-adf2-f4911ba9ffa6 id2:2 id3: 1 distance: 0.6127136969706317 4``` over and over – davidgyoung Mar 10 '15 at 00:56
  • Hi David, I have some news. I have tried with a friend's device (LG L70, running Android 4.4.2) and it works as expected and as you already tested! However, I can't make it work on any of my phones (Nexus 4 - Lollipop, Vodafone Smart 4 Turbo - 4.4.4), so it has to be whether a device or Android version issue. Nevertheless I tried again on 4.4.4 device, changing the _didEnterRegion_ method so that it does nothing. With this modification I get one log message each time I have the app on screen, that it: app on screen - 1 log, hide app and re-open it - 1 additional log, and so on... –  Mar 10 '15 at 20:31
  • Can you please modify your app to turn on library debug with `beaconManager.setDebug(true);` and then capture a LogCat output across the time where you fail to get detections? Take the results and paste them into a new issue here: https://github.com/AltBeacon/android-beacon-library/issues – davidgyoung Mar 10 '15 at 20:43
  • You can find it at #138 (https://github.com/AltBeacon/android-beacon-library/issues/138). I can considered my question solved (as it looks like a device specific problem) and I'll keep track of issue #138, if you agree. –  Mar 10 '15 at 21:27
  • if you prefer, you can leave the question open until I analyze. I will then post a new answer referencing the issue – davidgyoung Mar 10 '15 at 21:51
  • David, I have just solved my problem. Please see my answer below. –  Mar 14 '15 at 16:01
1

I have solved the issue modifying the Beaconing class (I only show changes):

public class Beaconing extends Application implements BootstrapNotifier, BeaconConsumer {

....

private BeaconManager beaconManager = null;

....

public void onCreate() {
        super.onCreate();
        beaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this);

....

}

....

public void onBeaconServiceConnect() {
    beaconManager.setBackgroundMode(true);
}

SO I implemented BeaconConsumer, set beaconManager to null and got it in onCreate(), and I added onBeaconServiceConnect().

This way works for me and I actually get continuous ranging.

Nevertheless I don't know the reason why it did not worked before. I will be happy if someone knows and explains it.

Thanks @davidgyoung (https://stackoverflow.com/users/1461050/davidgyoung) for your help! :)

Community
  • 1
  • 1