Android Library

Overview

There are a few different SDK’s that Aisle411 makes available for your application depending on your needs. The first is VenueKit -- an SDK built for making quick requests to our servers and returning relevant information about venues as well as items and locations inside of those venues. The second is RasterMap -- an SDK that raps around our older mapping code to provide easy inclusion into your application for displaying indoor maps of venues and handling user interactions with a map. The third is LocatorKit -- an SDK built for getting the user location from many different indoor location of the device while incorporating many different technologies.

The SDK’s are all built using gradle, and as such any applications using them will need to be built using gradle. All of the SDK’s share a common underlying framework called Core, which will also need to be included in your application in order to use any of the SDK’s. Finally, gson, the json parser from Google, will need to be included in your application to allow Core to do the necessary parsing of the json returned from our servers.

Requirements

Below are the requirements to use the Aisle411 Android Library
  • Valid Aisle411 Credentials
    • This includes a partnerId and a partnerSecret that are passed into desired SDKs.
    • LocatorKit Locator Credentials
      • Note that these are only required for the specified LocatorKit location providers.
  • Android 4.0.3 or later
  • Gradle build system (Android Studio)

VenueKit

VenueKit implements all of our venue and item searches by following the RESTful design pattern given by Virgil Dobjanschi. It is responsible for making the requests to our servers, parsing the JSON, and returning the result in the appropriate POJO’s. Because of the design, the requests are both cached and made independently of any activity’s lifecycle. So, rotating the screen or resuming the activity will not cost the application any additional bandwidth, battery, or time due to missed or restarted requests.

Inclusion

To include VenueKit into your application, place the core.aar and venuekit.aar files into a folder named ‘libs’ at the root of the module. After this, include the following lines in the dependencies section of gradle file for the module:

compile 'com.google.code.gson:gson:2.4'
compile(name:'core', ext:'aar')
compile(name:'venuekit', ext:'aar')

Setup

Before getting into the code, VenueKit requires the following permissions that must be placed in the manifest file:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

In the onCreate of the starting activity for the application, init(Context) and then setup(PARTNER_ID, PARTNER_SECRET, APP_NAME, APP_VERSION, SUB_DOMAIN) must be called on VenueKit. There is no harm in calling these functions multiple times and they are thread safe. An example of this is given below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    VenueKitApi.init(this);
    VenueKitApi.setup(PARTNER_ID, PARTNER_SECRET, "Android-Aisle411-SDKSample",
        "1.0", Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN);			      
}

The PARTNER_ID and PARTNER_SECRET will be given to you by Aisle411. The APP_NAME should be changed to the name of your application and the APP_VERSION should be changed to the version number of your application. The SUB_DOMAIN should be changed to Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN when you're ready to start communicating with the production servers.

Requesting

Once VenueKit has been included, intialized, and setup, searches can be made. The general layout for these requests is given below, and specific examples are given as subsections.

A request is started by calling into the appropriate search function on VenueKit. A requestId is returned from the search function. This id is unique to the request that has been started, and it is used to identify the result when it comes back. A flag is also used to tell if the request is currently pending. More on the reason for this later.

private boolean mRequesting;
private long mRequestId;

private void makeRequest() {
    mRequesting = true;
    // start the request and get the request id that we should be listening for the result from
    {SearchType} search = new {SearchType}(SEARCH_PARAMS);
    mRequestId = VenueKitApi.startSearch(search);
}

Because the requests are made in a separate service from the activity that called it, communication of the results from a search are done using BroadcastReceiver’s and Intent’s. An IntentFilter must be created to target the specific result and a BroadcastReceiver must be registered with the IntentFilter. The result, when it is broadcast back, is identified by the previously mentioned request id.

private final IntentFilter mResultFilter = new IntentFilter(VenueKitApi.TYPE_OF_RESULT);
private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        handleResultIntent(intent);
    }			      
};

private void handleResultIntent(@NonNull Intent intent) {
    if (!mRequesting || mRequestId != intent.getLongExtra(VenueKitApi.REQUEST_ID_EXTRA, 0L)) {
        return;
    }
    mRequestig = false;				      
    // received the result, now unpack it
    {ResultType} result = intent.getParcelableExtra(VenueKitApi.RESULT_EXTRA);
}

The BroadcastReceiver must be registered with Android to receive requests. This is done in OnResume(). The reason for this is to keep from leaking any resources, since the only lifecycle event guaranteed to be called before an activity is killed is OnPause(). This opens up the possibility for the activity to miss the desired request. The activity is responsible for checking if a request has been missed by using VenueKit.isRequestPending(REQUEST_ID).

@Override
protected void onResume() {
    super.onResume();
    // register the receiver to listen for the result
    registerReceiver(mResultReceiver, mResultFilter);
    // check to see if the result was missed
    if (mRequesting && !VenueKitApi.isRequestPending(mRequestId)) {
        makeRequest();
    }
}

@Override
protected void onPause() {
    super.onPause();
    // unregister the receiver so we don't leak anything
    unregisterReceiver(mResultReceiver);
}

Single Search Request

A single search request is made in the same way as described above with the {SearchType} being filled in as VenueItemsSearch and the result type being filled in as VenueItemsResult. VenueItemsSearch has several overloaded constructors for creation and offers setters and getters for all of the parameters as well. The possible params are the following:

  • VenueId
    • The id of the venue to search within.
    • Required
  • SearchString
    • Handles the searching for a specific string and identifies that string as a specific type of search. The different possible are SEARCH_TYPE_TERM - a normal search term such as milk, or SEARCH_TYPE_GTIN - a search for some identifying product code such as UPC or ISBN.
    • Both user input term searches and hard coded UPC/ISBN searches are handled with this
  • SearchPager
    • Handles the page of results to return back. PageNumber identifies which page of results to return starting at 1 and up, and PageSize sets the number of results returned for each page. IE, page 1 size 10 will return items[0] - items[9]
    • If omitted, the default settings are of page 1 and pageSize 10
  • SearchSettings
    • Handles settings specific to the request being made such as timeoutLength and requestCacheLength. These set how long the request can go for before timing out as well as how long a valid request result can be cached.

VenueItemsResult returns items, itemSuggestions, or typoSuggestions. Items is an array that exactly matched the specific search given. ItemSuggestions are items that were a close match, and this array will only be filled in if there were no exact match items available. TypoSuggestions is an array of strings that offer possible alternate searches to the one that was received. This array is only filled out if there are no exact matches or suggested matches and typo suggestions could be matched to the original search.

The array of items returned in either items or itemSuggestions is made up of a collection that extends BaseVenueItem. This base class offers common params across all items. If type specific information is desired, getType() can be called on the item to the cast the base item to it’s actual instance made up of one of FeaturedVenueItem, GTINsVenueItem, GTINVenueItem, or TermVenueItem.

Sample code for making a complete search is now given:

private static final long PARTNER_ID = 0L;
private static final String PARTNER_SECRET = null;
private static final long VENUE_ID = 0L;
private static final String SEARCH_STRING = null;
private static final boolean IS_GTIN_SEARCH = false;

private final IntentFilter mItemsResultFilter = new
    IntentFilter(VenueKitApi.SEARCH_VENUE_ITEMS_RESULT);
private final BroadcastReceiver mItemsResultReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        handleItemsResultIntent(intent);
    }
};

private boolean mRequestingItems;
private long mItemsRequestId;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    VenueKitApi.init(this);
    VenueKitApi.setup(PARTNER_ID, PARTNER_SECRET, "Android-Aisle411-ItemsSample",
         "1.0", Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN);
     makeItemsRequest();
}

@Override
protected void onResume() {
    super.onResume();
    // register the receiver to listen for the result
    registerReceiver(mItemsResultReceiver, mItemsResultFilter);
    // check to see if the result was missed
    if (mRequestingItems && !VenueKitApi.isRequestPending(mItemsRequestId)) {
          makeItemsRequest();
    }
}    

@Override					  
protected void onPause() {
    super.onPause();
     // unregister the receiver so we don't leak anything
      unregisterReceiver(mItemsResultReceiver);
}

private void makeItemsRequest() {
     mRequestingItems = true;
     // start the request and get the request id that we should be listening for the result from
     VenueItemsSearch search = new VenueItemsSearch(VENUE_ID, new 
         SearchString(SEARCH_STRING, IS_GTIN_SEARCH));
     mItemsRequestId = VenueKitApi.startSearch(search);
}

private void handleItemsResultIntent(@NonNull Intent intent) {
    if (!mRequestingItems || mItemsRequestId !
        intent.getLongExtra(VenueKitApi.REQUEST_ID_EXTRA, 0L)) {
        return;
    }
    mRequestingItems = false;
    // received the result, now unpack it
    VenueItemsResult result = intent.getParcelableExtra(VenueKitApi.RESULT_EXTRA);
}

Multiple Search Request

A multiple search request is made in the same way as described above with the {SearchType} being filled in as VenueItemsListSearch and the result type being filled in as VenueItemsListResult. VenueItemsListSearch accepts the venueId to search in as well as several overloaded constructors for creation and offers setters and getters for all of the parameters as well. The possible param classes are the following:

  • VenueId
    • The id of the venue to search within
    • Required
  • SearchList
    • The list of searches to make and include in the results. It is made up of a name to identify the list as well as a collection of items to search. The items can specify either a term or code.
  • SearchSettings
    • Handles settings specific to the request being made such as timeoutLength and requestCacheLength. These set how long the request can go for before timing out as well as how long a valid request result can be cached.

VenueItemsListResult returns information specific to the list that was searched for as well as an array of items that matched the search.

The array of items returned in is made up of a collection that extends BaseVenueItem. This base class offers common params across all items. If type specific information is desired, getType() can be called on the item to the cast the base item to it’s actual instance made up of one of FeaturedVenueItem, GTINsVenueItem, GTINVenueItem, or TermVenueItem.

Sample code for making a complete search is now given:

private static final long PARTNER_ID = 0L;
private static final String PARTNER_SECRET = null;
private static final long VENUE_ID = 0L;

private final IntentFilter mItemsListResultFilter = new
    IntentFilter(VenueKitApi.SEARCH_VENUE_ITEMS_LIST_RESULT);
private final BroadcastReceiver mItemsListResultReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {     
        handleItemsListResultIntent(intent);
    }
};

private boolean mRequestingItemsList;
private long mItemsListRequestId;

@Override					  
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    VenueKitApi.init(this);
    VenueKitApi.setup(PARTNER_ID, PARTNER_SECRET, "Android-Aisle411-ItemsSample",
         "1.0", Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN);
    makeItemsListRequest();
}

@Override
protected void onResume() {
    super.onResume();
    // register the receiver to listen for the result
    registerReceiver(mItemsListResultReceiver, mItemsListResultFilter);
    // check to see if the result was missed
    if (mRequestingItemsList && !VenueKitApi.isRequestPending(mItemsListRequestId)) {
        makeItemsListRequest();
    }
}

@Override
protected void onPause() {	  
    super.onPause();
    // unregister the receiver so we don't leak anything
    unregisterReceiver(mItemsListResultReceiver);
}

private void makeItemsListRequest() {
    mRequestingItemsList = true;
    // start the request and get the request id that we should be listening for the result from
    VenueItemsListSearch search = new VenueItemsListSearch(VENUE_ID,
        getItemsSearchList());
    mItemsListRequestId = VenueKitApi.startSearch(search);
}    

private SearchList getItemsSearchList() {
    SearchList searchList = new SearchList(“testSearchList”);
    searchList.addItem(new TermSearchItem(“milk”));
    searchList.addItem(new TermSearchItem(“eggs”));
    return searchList;
}

private void handleItemsListResultIntent(@NonNull Intent intent) {					  
    if (!mRequestingItemsList || mItemsListRequestId !=
        intent.getLongExtra(VenueKitApi.REQUEST_ID_EXTRA, 0L)) { 
        return;
    }
    mRequestingItemsList = false;
    // received the result, now unpack it
    VenueItemsListResult result = intent.getParcelableExtra(VenueKitApi.RESULT_EXTRA);
}

RasterMap

RasterMap allows a RasterMapResult file returned from the VenueKit sdk to be displayed to the user and interacted with. It also allows items returned from the VenueKit sdk to be displayed on the map. The SDK wraps around our previous Android wrapping library to provide easy inclusion into your application while offering convenience methods to make using it simpler.

Inclusion

To include RasterMap into your application, place the core.aar, venuekit.aar, and rastermap.aar files into a folder named ‘libs’ at the root of the module. After this, include the following lines in the dependencies section of gradle file for the module:

compile 'com.google.code.gson:gson:2.4'
compile(name:'core', ext:'aar')
compile(name:'venuekit', ext:'aar')
compile(name:’rastermap’, ext:'aar')

Setup

Before getting into the code, loading the RasterMap file requires VenueKit and VenueKit requires the following permissions that must be placed in the manifest file:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

In general, the above location permissions are required, but you can get by without them if any searches that have an overloaded option of SearchLocation have an instance of that class provided with the appropriate search latitude and longitude provided.

In the onCreate of the starting activity for the application, and before making any requests on VenueKit, init(Context) and then setup(PARTNER_ID, PARTNER_SECRET, APP_NAME, APP_VERSION, SUB_DOMAIN) must be called. There is no harm in calling these functions multiple times and they are thread safe. An example of this is given below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    VenueKitApi.init(this);
    VenueKitApi.setup(PARTNER_ID, PARTNER_SECRET, "Android-Aisle411-SDKSample",
        "1.0", Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN);			      
}

In the onCreate of the activity or fragment used to display the map, and before making any requests on RasterMap, init(Context) must be called. There is no harm in calling this function multiple times and it is thread safe. An example of this is given below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RasterMapBundleApi.init(this);
}

Loading and Displaying

Once VenueKit and RasterMap has been included, intialized, and setup, searches and loading and displaying a map can be done. A walk through is given in the subsections below.

Loading File

A raster map file search request is made by passing in a search of type RasterMapSearch to VenueKit and waiting for the result of type RasterMapResult. RasterMapSearch has several overloaded constructors for creation and offers setters and getters for all of the parameters as well. The possible params are the following:

  • VenueId
    • The id of the venue to get the file for.
    • Required
  • MaxFileCacheCount
    • The maximum number of raster map files the api can store on the device under application storage. The default is 15, and in general the files are a fewmegabytes.
  • SearchSettings
    • Handles settings specific to the request being made such as timeoutLength and requestCacheLength. These set how long the request can go for before timing out as well as how long a valid request result can be cached.

RasterMapResult returns a Uri filled with the full path of the file on the device and it includes the name of the file as it is stored in the application’s data path. It also includes an error field in case any errors occurred while retrieving the file.

Sample code for making a complete search is now given:

private static final long PARTNER_ID = 0L;
private static final String PARTNER_SECRET = null;
private static final long VENUE_ID = 0L;

private final IntentFilter mFileResultFilter = new
    IntentFilter(VenueKitApi.SEARCH_RASTER_MAP_RESULT);
private final BroadcastReceiver mFileResultReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        handleFileResultIntent(intent);
    }
};    

private boolean mRequestingFile;					
private long mFileRequestId;
private Aisle411Error mFileError;

@Override					
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    VenueKitApi.init(this)
    VenueKitApi.setup(PARTNER_ID, PARTNER_SECRET
        "Android-Aisle411-RastermapFileSample", "1.0",
        Aisle411Url.AISLE411_PRODUCTION_SUB_DOMAIN);
    makeFileRequest();
}

@Override
protected void onResume() {					
    super.onResume();
    // register the receiver to listen for the result
    registerReceiver(mFileResultReceiver, mFileResultFilter);
    // check to see if the result was missed
    if (mRequestingFile && !VenueKitApi.isRequestPending(mFileRequestId)) {
        makeFileRequest();
    }
}

@Override
protected void onPause() {
    super.onPause();
    // unregister the receiver so we don't leak anything
    unregisterReceiver(mFileResultReceiver);
}

private void makeFileRequest() {
    mRequestingFile = true;
    // start the request and get the request id that we should be listening for the result from
    RasterMapSearch search = new RasterMapSearch(VENUE_ID);
    mFileRequestId = VenueKitApi.startSearch(search);
}

private void handleFileResultIntent(@NonNull Intent intent) {
    if (!mRequestingFile || mFileRequestId !=
        intent.getLongExtra(VenueKitApi.REQUEST_ID_EXTRA, 0L)) {
            return;
        }
    mRequestingFile = false;
    // received the result, now unpack it
    RasterMapResult result = intent.getParcelableExtra(VenueKitApi.RESULT_EXTRA);
    mFileError = result.getError();
}

Parsing to Bundle

The MapView cannot use the RasterMapResult returned from VenueKit. Instead, the MapView takes in a parsed version of the file called a MapBundle. The MapBundle is not parcelable, so it must be reset any time view creation takes place. The RasterMap api makes this process simpler and more straightforward by storing the parsed bundle with code that lives on the activity’s lifecycle and therefore independent of any activity or fragment.

The first step in parsing the bundle is to call into the RasterMapBundleApi to parse the file result into a bundle. After this is done, the result will be notified through a broadcast and the code can then call to get the RasterMapBundleResult. The file result passed in cannot be null and should not have any errors.

Sample code for parsing a bundle is now given:

private final IntentFilter mBundleResultFilter = new
    IntentFilter(RasterMapBundleApi.PARSE_BUNDLE_RESULT);

private final BroadcastReceiver mBundleResultReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        handleBundleResultIntent(intent);
    }
};

private RasterMapResult mFileResult;					  
private Aisle411Error mFileError;
private boolean mRequestingBundle;
private MapBundle mBundle;
private Aisle411Error mBundleError;

@Override					  
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RasterMapBundleApi.init(this);
    checkMakeBundleRequest();
}    

@Override					  
protected void onResume() {
    super.onResume();
    // register the receiver to listen for the result
    registerReceiver(mBundleResultReceiver, mBundleResultFilter);
    // check to see if the result was missed
    checkMakeBundleRequest();
}

@Override
protected void onPause() {
    super.onPause();
    // unregister the receiver so we don't leak anything
    unregisterReceiver(mBundleResultReceiver);
}

private void handleFileResultIntent(@NonNull Intent intent) {
    if (!mRequestingFile || mFileRequestId !=
        intent.getLongExtra(VenueKitApi.REQUEST_ID_EXTRA, 0L)) {
            return;
        }
    mRequestingFile = false;
    // received the result, now unpack it
    mFileResult = intent.getParcelableExtra(VenueKitApi.RESULT_EXTRA);
    mFileError = result.getError();
    checkMakeBundleRequest();
}

private void checkMakeBundleRequest() {					  
    if (mFileResult == null || mFileError != null) {
        // we can’t load the bundle because the file was not properly loaded
        return;
    }
    if (mBundle != null || mBundleError != null) {
        // we have already loaded the bundle or tried loading the bundle and it failed	  
        return;
    }
    if (RasterMapBundleApi.isResultPending(mFileResult.getVenueId())) {
        // we have already called to parse and waiting for it to finish
        return;
    }
    if (RasterMapBundleApi.hasResult(mFileResult.getVenueId())) {
        // we have a result, call to set it
        setBundleResult(RasterMapBundleApi.getBundleResult(mFileResult.getVenueId());
        return;
    }
    // we don’t have a result, so call to start parsing one
    mRequestingBundle = true;
    RasterMapBundleApi.parseForBundle(mFileResult);			  
}

private void handleBundleResultIntent(@NonNull Intent intent) {					  
    if (!mRequestingBundle || (mFileResult != null && mFileResult.getVenueId() !=
        intent.getLongExtra(RasterMapBundleApi.VENUE_ID_EXTRA, 0L)) {
            return;
        }
    setBundleResult(RasterMapBundleApi.getBundleResult(mFileResult.getVenueId());
}

private void setBundleResult(@NonNull RasterMapBundleResult result) {
    mRequestingBundle = false;
    mBundle = result.getMapBundle();
    mBundleError = result.getError();
}

Displaying Map

Once the bundle has been successfully loaded, the MapView can be used to display it. The following code goes through how to setup the MapView provided it has been included in the layout for the relevant activity or fragment.

Sample code for setting up the MapView is now given:

private MapBundle mBundle;
private Aisle411Error mBundleError;
private MapView mMapView;

@Override
protected void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
    savedInstanceState) {
    View rootView = inflater.inflate(RESOURCE_ID, container, false);
    mMapView = rootView.findViewById(R.id.map_view);
    checkSetupMapView();
}

private void setBundleResult(@NonNull RasterMapBundleResult result) {					  
    mRequestingBundle = false;
    mBundle = result.getMapBundle();
    mBundleError = result.getError();
    checkSetupMapView();
}

private void checkSetupMapView() {
    if (mMapView == null || mMapBundle == null) {
        // we don’t have the bundle or the mapView
	return;
    }
    mMapView.setBundle(mBundle);
    mMapView.setRotationEnabled(false);
    mMapView.setBuiltinZoomControls(false);
    mMapView.getFloorSelectorController().setFloorSelectorButtonsFactory(
        new FloorSelectorButtonsFactory(getActivity()) {
	@Override
        public RadioButton getFloorButton(LevelInfo levelInfo, int numberOfLevels) {
            RadioButton button = new RadioButton(getContext()) {
	        @Override
                public int getCompoundPaddingLeft { return 0; }
            };
            button.setGravity(Gravity.CENTER);
            button.setButtonDrawable(new StateListDrawable());
            button.setId(levelInfo.getNumber());
            button.setText(levelInfo.getName());
            button.setTextColor(Color.WHITE);
            return button;  
        }
    );
}

Displaying Items

Once the bundle has been successfully loaded, the MapView can be used to display items. We make a class called InformationBarLayout also available for creating a sliding panel to display information for the locations shown on the map. The following code goes through how to setup the InformationBarLayout and MapView with items provided it has been included in the layout for the relevant activity or fragment.

Sample code for setting up the InformationBarLayout and MapView with items is now given:

private BaseVenueItem[] mItems;
private MapBundle mBundle;
private Aisle411Error mBundleError;					
private InformationBarLayout mInfoBarLayout;
private MapView mMapView;
private ProductSetOverlay mItemsOverlay;

@Override
protected void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
    savedInstanceState) {
    View rootView = inflater.inflate(RESOURCE_ID, container, false);
    mInfoBarLayout = rootView.findViewById(R.id.info_bar_layout);
    mMapView = mInfoBarLayout.getMapView();
    checkSetupInfoBarLayout();
    checkRefreshVenueItems();
}

public void setItems(BaseVenueItem[] items) {
    mItems = items;
    checkRefreshVenueItems();
}    

private void setBundleResult(@NonNull RasterMapBundleResult result) {
    mRequestingBundle = false;
    mBundle = result.getMapBundle();
    mBundleError = result.getError();
    checkSetupInfoBarLayoutView();
    checkRefreshVenueItems();
}

private void checkSetupInfoBarLayout() {
    if (mInfoBarLayout == null || mMapBundle == null) {
        // we don’t have the bundle or the mapView
	return;
    }
    mInfoBarLayout.setInformationBar(new InformationBar() {
        @Override
        public boolean isFixed(ItemizedOverlay<ProductOverlayItem> itemizedOverlay,
            ProductOverItem productOverlayItem){
            return productOverlayItem.getProducts().isEmpty();

        }
        @Override
        public CharSequence getExpandInstructions(ItemizedOverlay<ProductOverlayItem>
            itemizedOverlay, ProductOverlayItem productOverlayItem) {
            return "Slide up for items";
        }
        @Override
        public CharSequence getCollapseInstructions(ItemizedOverlay<ProductOverlayItem>
	    itemizedOverlay, ProductOverlayItem productOverlayItem) {
            return "Slide down to close";
        }
        @Override
        public CharSequence getSublocation(ItemizedOverlay<ProductOverlayItem>
           itemizedOverlay, ProductOverlayItem productOverlayItem) {
           return productOverlayItem.getTitle().toString();
        }    
        @Override
        public CharSequence getLocation(ItemizedOverlay<ProductOverlayItem>
            itemizedOverlay, ProductOverlayItem productOverlayItem) {
            Section section = getFirstSection(productOverlayItem);
            return section != null ? section.getAisle() : null;
        }
        @Override
        public ListAdapter getListAdapter(ItemizedOverlay<ProductOverlayItem>
            itemizedOverlay, ProductOverlayItem productOverlayItem) {
            return new ProductsAdapter(getActivity(), productOverlayItem.getProducts());
        }
        private Product getFirstProduct(ProductOverlayItem item) {
            return item.getProducts().isEmpty() ? null : item.getProducts().get(0);
        }
        private Section getFirstSection(ProductOverlayItem item) {
            Product product = getFirstProduct(item);
            if (product != null) {
                return product.getSections().isEmpty() ? null :
                     product.getSections().iterator().next();
            }
            return null;
        }			
    });    
    mMapView.setBundle(mBundle);
    mMapView.setRotationEnabled(false);
    mMapView.setBuiltinZoomControls(false);
    mMapView.getFloorSelectorController().setFloorSelectorButtonsFactory(
        new FloorSelectorButtonsFactory(getActivity()) {
            @Override
            public RadioButton getFloorButton(LevelInfo levelInfo, int numberOfLevels) {
                RadioButton button = new RadioButton(getContext()) {
                    @Override
		    public int getCompoundPaddingLeft { return 0; }  			
                };
                button.setGravity(Gravity.CENTER);
                button.setButtonDrawable(new StateListDrawable());
                button.setId(levelInfo.getNumber());
                button.setText(levelInfo.getName());
                button.setTextColor(Color.WHITE);
                return button;
            }
	}
    );			
}

private void checkRefreshVenueItems() {
    if (mInfoBarLayout == null || mMapBundle == null) {
        // we don’t have the bundle or the mapView
        return;
    }
    mItemsOverlay = new ProductSetOverlay(){
        @Override
	protected boolean shouldCenterOnPin(){			
            return true;
        }
        @Override
        protected boolean onTap(int index){
            return super.onTap(index);
	}	
    };
    Product[] products = RasterMapUtility.convertToRasterMap(mVenueItems,
        mRasterMapBundle);
    products = RasterMapUtility.removeProductsAndSectionsWithoutMapPoints(products);
    if (products == null) {
        // no items to show, so also hide the sliding panel
        mInformationBarLayout.hidePanel();
        return;
    }
    mItemsOverlay.setProducts(new ArraySet<>(Arrays.asList(products)));
    mItemsOverlay.setDrawable(ResourcesCompat.getDrawable(getResources(),
        R. R.drawable.item_pin, null));
    mMapView.addOverlay(mItemsOverlay);	  
}