How to build a location-based hybrid mobile app with reverse geocoding

Over the weekend I released a new app on the App Store and Google Play and I wanted to share how I handled the geolocation features and GMaps integration.

The app is location-based wine rating. You can track what wine you’ve consumed at restaurants by taking photos of labels and rating them. I hate going back to a restaurant and not knowing what we ordered last time – so I built this app! Check it out:

To build the app I used HTML, CSS, JavaScript and the Trigger.io platform to add native features and package it for the app stores, with these libraries:

You can signup for a Google Maps API key here. In the render function, we asynchronously load the map by embedding the script tag for it. This can then be loaded in the background and initialized when ready. The div with id ‘map_container’ is set to have width and height 100%, but is hidden until the show() function is called from the router. The callback function wine.util.initMap simply references the initMap function on the map view object.

Placing markers and controlling navigation

Here’s how we initialize the map with markers once the GMaps script has loaded:

This snippet shows how we center the map on, and set a custom marker icon for, the current position. We’ll use the default icon to plot the other locations (where we’ve previously taken photos of wine labels). Here’s what happens when we call :

Once the map is in place, you can see how simple it is to place markers for all the objects you wish to plot and then to control navigation when those markers are clicked using the call to :

1

google.maps.event.addListener

Showing the map with all items or a specific item

It only remains for us to display the map when the user navigates there, either to show the locations of all the items relative to the current position, or to focus on one item in particular. To do that, here is the show function:

The idx parameter is optionally passed in – this refers to the index of the item in our model that we want to focus on. The code branches depending on whether it is passed in. If it is not, the map centers on the current location, otherwise it centers on the location of that item.

There’s no guarantee that the map has loaded already when the view is shown since the Google Maps script is loaded asynchronously. This is why we also need to check for the maps existence and display ‘Loading…’ text as a stop-gap.

Getting the current location and reverse geocoding lookup

That’s pretty much it for the map view, but there’s a couple more pieces to making the app work well with location.

In the map view functions, you’ll have seen references to the current location and it’s worth highlighting how we get that. In the initMap function, we used this call:

1

forge.geolocation.getCurrentPosition

We could use HTML5 geolocation instead, but the problem with that is it throws up an ugly warning to the user with the full path to the index.html file. Using the forge.geolocation.getCurrentPosition API we access true native geolocation and the warning message is much friendler:

The final piece is reverse geocoding: extracting an address from location data, because we want to show a list of the locations where we’ve taken photos of wine labels as well just plotting them on a map. To do that is simple with the v3 Google Maps API:

But the remaining aspect I’d like to highlight is just how easy it is to configure a native tabbar to handle navigation in conjunction with backbone. Check-out this snippet from main.js:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

forge.tabbar.addButton({

text:"Rate Wine",

icon:"img/star.png",

index:0

},function(button){

state.set('rateButton',button);

button.onPressed.addListener(function(){

wine.router.navigate('rateTab',{trigger:true});

});

});

forge.tabbar.addButton({

text:"Wine List",

icon:"img/bottle.png",

index:1

},function(button){

state.set('listButton',button);

button.onPressed.addListener(function(){

wine.router.navigate('listTab',{trigger:true});

});

});

forge.tabbar.addButton({

text:"Wine Map",

icon:"img/map.png",

index:2

},function(button){

state.set('mapButton',button);

button.onPressed.addListener(function(){

wine.router.navigate('mapTab',{trigger:true});

});

});

Conclusion

Using the Forge API and build/test cycle, backbone and other great libraries you can develop powerful, native mobile applications with relatively few lines of code. You can use native geolocation and the Google Maps API to add location-based features fast.