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:

I’ll mainly focus on the Google Maps integration part and assume existing knowledge of Backbone. But I’ll also try to highlight some other interesting snippets. We will cover how to:

  • render the map at app startup for a fast user experience when the user clicks to view
  • load Google Maps asynchronously and place markers
  • get the current location and do a reverse geocoding lookup
  • control navigation between backbone views from the native tabbar

There is a lot going on in the app so we’ll only look at snippets. For the full picture, you can see the code on GitHub.

Using the Forge build / test cycle

Throughout the development of this app, I used Trigger.io’s Forge platform to run fast build / test cycles in the device emulators so I could develop the flow with the native device features and the Forge API already incorporated. Each time I change the code it was just a couple of seconds and two commands to see the changes:

forge build

forge run ios

If you want to try building the code as native apps yourself as you follow along this post, just signup for free and download the lightweight command-line tools to get started.

I also used Catalyst – a WebKit like debugger for mobile webviews to see the logging output and debug the presentation and logic.

Basic Structure

The first step was to include all my JavaScript and HTML templates in my index.html here. Then I described the paths through the app in router.js:

// Router
wine.types.Router = Backbone.Router.extend({
  routes: {
    "rateTab": "rateTab",
    "listTab": "listTab",
    "mapTab": "mapTab",
    "mapTab/:idx": "mapTab",
    "picture": "picture",
    "rate": "rate",
    "detail/:idx": "detail"
  },
  rateTab: function() {
    state.get('rateButton').setActive();
    forge.topbar.setTitle("Rate Wine");
    if (!state.get('currentPhoto')) {
      wine.router.navigate('picture', { trigger: true });
    } else {
      wine.router.navigate('rate', { trigger: true });
    }
  },
  listTab: function() {
    state.get('listButton').setActive();
    forge.topbar.setTitle("Wine List");
    state.get('list').show();
  },
  mapTab: function(idx) {
    state.get('mapButton').setActive();
    forge.topbar.setTitle("Wine Map");
    state.get('map').show(idx);
  },
  picture: function () {
    state.set('currentPhoto', null);
    var page = new wine.views.Picture();
    page.render().show();
  },
  rate: function() {
    var page = new wine.views.Rate();
    page.render().show();
  },
  detail: function(idx) {
    forge.logging.log('... Showing detail for index: '+idx);
    var page = new wine.views.Detail();
    page.render(idx).show();
  }
});
wine.router = new wine.types.Router();

You can see that the mapTab is one of the routes and we can optionally pass it an index of a particular item to plot (when we don’t, all of the items will be plotted). Lets focus in on that map view.

Pre-loading Google Maps view

When we navigate to the mapTab in the router, we actually show a pre-existing map rather than initializing it there:

mapTab: function(idx) {
  state.get('mapButton').setActive();
  forge.topbar.setTitle("Wine Map");
  state.get('map').show(idx);
}

This is for a smoother experience since when the user clicks the map tab we’ve actually preloaded the map in the initialization code in wine.js:

state.set('map', new wine.views.Map());
state.get('map').render();
forge.logging.log('Pre-rendered map');

In views.js, the render function of the wine.views.Map prototype looks like this:

render: function() {
  var el = this.el;
  var script = document.createElement("script");
  script.type = "text/javascript";
  script.src = "http://maps.googleapis.com/maps/api/js?key=<YOUR_API_KEY>&sensor=true&callback=wine.util.initMap";
  document.body.appendChild(script);
  $('#map_container').append(el);
  return this;
}

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:

initMap: function() {
  forge.logging.log('... Initializing map');
  forge.geolocation.getCurrentPosition(function(position) {
    $(this.el).empty();
    state.set('currentCoords', position.coords);
    forge.logging.log('Set current position:');
    forge.logging.log(position.coords);
    var latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude, true);
    var myOptions = {
      zoom: 15,
      center: latLng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    forge.logging.log('... Create map');
    state.get('map').gmap = new google.maps.Map(document.getElementById('map'), myOptions);
    forge.tools.getURL('img/blue-pin.png', function(src) {
      forge.logging.log('... Add position marker');
      state.set('currentMarker', new google.maps.Marker({
        position: latLng,
        title: "Current Position",
        icon: src,
        map: state.get('map').gmap,
        zIndex: -1
      }));
    });
    wine.photos.each(state.get('map').add);
    forge.logging.log('Created map ...');
  });
}

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 :

wine.photos.each(state.get('map').add);
// ...
add: function(item) {
  var latLng = new google.maps.LatLng(item.get('position').latitude, item.get('position').longitude, true);
  var marker = new google.maps.Marker({
    position: latLng,
    map: state.get('map').gmap,
    zIndex: 1
  });
  var idx = wine.photos.indexOf(item)
  google.maps.event.addListener(marker, 'click', function() {
    wine.router.navigate('detail/'+idx, { trigger: true });
    $('#map_container').hide();
  });
}

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 :

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:

show: function(idx) {
  $('#map_container').show();
  wine.util.resetCurrentView(this);
  if (state.get('map').gmap) {
    google.maps.event.trigger(state.get('map').gmap, 'resize');
    var currentLatLng = new google.maps.LatLng(state.get('currentCoords').latitude, state.get('currentCoords').longitude, true);
    if (idx) {
      var item = wine.photos.at(idx);
      var latLng = new google.maps.LatLng(item.get('position').latitude, item.get('position').longitude, true);
      state.get('map').gmap.setCenter(latLng);
    } else {
      state.get('map').gmap.setCenter(currentLatLng);
    }
    state.get('currentMarker').setPosition(currentLatLng);
  } else {
    this.initMap();
    $('#map').html('<div class="title">Loading...</div>');
  }
  if (idx) {
    forge.topbar.addButton({
      text: 'Back',
      position: 'left',
      type: 'back'
    }, function() {
      $('#map_container').hide();
    });
  }
}

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:

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:

getLocation: function(coords, timestamp) {
  forge.request.ajax({
    url: "http://maps.googleapis.com/maps/api/geocode/json?latlng="+coords.latitude+","+coords.longitude+"&sensor=true",
    dataType: "json",
    success: function(response) {
      try {
        var photo = wine.photos.filter(function(item) {
          return item.get('timestamp') == timestamp;
        })[0];
        if (photo) {
          photo.set('location', response.results[0].formatted_address);
          $('#_'+timestamp+' .title').html(photo.get('location'));
        } else {
          forge.logging.log('No photo with timestamp: '+timestamp);
        }
      } catch(e) {
        forge.logging.log('--- Exception getting location --- ');
        forge.logging.log(e);
        forge.logging.log('--- Photo:');
        forge.logging.log(photo);
        forge.logging.log('--- Response:');
        forge.logging.log(response);
      }
    },
    error: function(response) {
      forge.logging.log('--- Error getting location, response:');
      forge.logging.log(response);
    }
  });
}

Controlling navigation from the native tabbar

That’s it for the location part of the app, but if you look at the code, you can see a lot more going on, such as use of Mustache templates, the native camera using forge.file.getImage, and a star rating input element (thanks to the Yahoo! User Interface Blog for the snippets).

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:

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.

We hope this has whetted your appetite to try developing hybrid mobile apps incorporating Google Maps.

Questions, comments? Let us know at support@trigger.io. We’d love to hear from you and help with your app plans. Sign up now to get started.

New features: video capture, network status APIs available, WebSQL support, improved modal view

Our development team organizes themselves in two week sprints. We’ve just completed one and added some major new features to the platform. This sprint we added:

  • Improved Modal View with styling options
  • Video Capture API
  • Network Status API
  • WebSQL Support

Improved Modal View

forge.tabs.openWithOptions now allows you to customize the appearance of modal views on mobile.

forge.tabs.openWithOptions({
	url: 'https://trigger.io',
	title: 'TRIGGER.IO Homepage',
	tint: [41, 41, 41, 255],
	buttonText: 'Exit',
	buttonTint: [218, 174, 56,255]
});

Video Capture API

New API forge.file.getVideo allows you to develop apps that include video capture where the device supports that.

file.getVideo([params], successerror)

Arguments:

  • params (object) — object optional parameters.
  • success (function(file)) — callback to be invoked when no errors occur (argument is the returned file)
  • error (function(content)) — called with details of any error which may occur

Network Status API

New API methods forge.is.connection.connected and forge.is.connection.wifi let you change your apps’ behavior according to the connection status.

is.connection.connected()

Return boolean:

  Returns true if a mobile device has an active internet connection.
is.connection.wifi()

Return boolean:

  Returns true if a mobile device is connected via wifi.

WebSQL support

The normal web database API is now available: http://www.w3.org/TR/webdatabase/

Note: not all browsers support Web SQL http://caniuse.com/#feat=sql-storage

For example, one of the tests we run is similar to this:

var db = openDatabase('mydb', '1.0', 'example database', 2 * 1024 * 1024);
db.transaction(function (tx) {
    tx.executeSql('CREATE TABLE IF NOT EXISTS foo (id unique, text)');
    tx.executeSql('INSERT INTO foo (id, text) VALUES (1, "foobar")');
});

db.transaction(function (tx) {
    tx.executeSql('DROP TABLE foo');

    // known to fail - so should rollback the DROP statement
    tx.executeSql('INSERT INTO foo (id, text) VALUES (1, "foobar")');
    forge.logging.error("INSERT into non-existent table succeeded!");
}, function (err) {
    forge.logging.info("error callback invoked, as expected");
});

db.transaction(function (tx) {
    tx.executeSql('SELECT * FROM foo', [], function (tx, results) {
        forge.logging.info("row: "+results);
    });
});

Using Geolocation and GMaps with Trigger.io, Appnovation gets first-time App Store approval

We’ve been supporting enterprise development shop Appnovation in building their first Forge app. The app – for NeedAnAccountant.org – lets (Canadian) users find their nearest accountants, using our framework to wrap their app to native (packaged for iPhone and Android), handle cross-domain requests and native geolocation features through our API.

Google Maps and Geolocation API integration

Under the hood, the map is integrated with simple HTML and JavaScript with the standard Google Maps API, with calls to Trigger’s Forge handling geolocation:

google.maps.Map.prototype.setCurrentLocation = function(fn) {
	forge.geolocation.getCurrentPosition(function(position) {
		map.currentLocation = {
			"lat": position.coords.latitude,
			"lng": position.coords.longitude
		}
		fn();
	}, function() {
		// Some dummy data if geolocation failed
		map.currentLocation = {
			"lat":undefined,
			"lng":undefined
		}
	});
	fn();
}

Though it can be done with HTML5, geolocation is one feature that’s handled better through a bridge to native – using forge.geolocation. For one thing, this way we don’t push ugly pop-up confirmations to the user on every run.

Forge also handled cross-domain requests within the app – giving us a lot more freedom than a straightforward HTML5 mobile-app:

forge.request.ajax({
	url: requestUrl,
	dataType: 'json',
	timeout: 5000,
	success: function(data, status) {
		forge.logging.info('[search-nearme] found '+status.length+' results near user');
		$("#loader").hide();
		displaySearchResultsMap(data);
	},
	error: function(status, errorThrown){
		forge.logging.error('[search-nearme] '+status);
		$("#loader").hide();
		$("#error").show();
	}
});

First-time App Store approval

Best of all, the app was accepted into the iOS Store on its first submission – we’re proud to see our framework is strict, neat and tidy enough to pass through Apple’s approval process without any setbacks.

Many App Store rejections happen due to use of undocumented APIs or failing to include interface elements such as launch images. We made sure to implement our native wrapper to follow Apple’s documentation as closely as possible, so our customers don’t need to worry about it. Appnovation were able to prepare their app, and deploy it quickly, on deadline, with no hitches – keeping their client happy.

Canadians especially should check out the NeedAnAccountant.org app on iOS and Android stores, and find out more about Appnovation’s work at Appnovation.com.

Next steps for Appnovation

When we first reached out to Appnovation, we realized that they are always interested in providing innovative solutions to their clients and we had one we figured they’d love to test out.

“Anyone who has tried to deploy an HTML5 app in native iOS/Android wrappers will tell you how much of their time went into installing, setting up and correctly configuring each native project and IDE, the cumbersome build & test cycles,” they told us. “Add code-signing certificates, and an unfamiliar XCode interface to the mix, and curve gets steeper for HTML5 developers.”

So we’re psyched to hear they’re interested in our approach and will be making sure to look at Trigger for future projects if there is a technology fit. They have a killer roster of enormous, Fortune 500 clients that demand the highest-quality apps for their businesses. We’re proud to see our framework stacking up to such high standards.

Appnovation told us:

“The great thing about Trigger is we never had to touch XCode or Eclipse. We learned a few simple shell commands: forge create, forge build, forge run, forge package. Trigger centralizes all configs in a single, intuitive JSON file, and we were off creating & testing native builds in minutes.”

Appnovation have been an awesome customer, and we thank them for all their kind words about using our framework.

Thanks, guys. Here’s to the next one.

Screencast: Trigger.io Catalyst in action

Last week we shared our first screencast of Forge in action, walking you through the build-test workflow – this week, we wanted to show you how you can use Catalyst to easily test and debug your app. Check it out:

Normally, debugging for mobile – without the same tools available as you get for web development – is a real pain. Catalyst makes it simpler, giving you a full WebKit-like debugger for mobile.

Catalyst is our hosted version of the Weinre project, and features a panel for debugging your Forge API calls, alongside areas to inspect your code and JavaScript log. You can use it with the regular mobile browser to debug mobile web pages as well as with Forge to debug HTML5 code that has been wrapped inside a native WebView.

In the video, I show how to debug the app we built in the last screencast, and test it live in the iPhone simulator. You can use Catalyst to:

  • Navigate and alter the DOM, including styling with the Elements tab
  • Query JavaScript objects and test code inside the WebView with the Console tab
  • Observe network usage and execution order
  • Log debug messages to the console and track Forge API calls when building hybrid apps  with Trigger.io Forge

Try it out and let us know how you get on – firing all your support questions to support@trigger.io, where we’ll gladly help you out.

We had a great response from last week’s video, and we’ll hope to do more of these down the line and drill a bit deeper into Forge – let us know what you’d like us to cover, and make sure you follow us on Twitter to catch the next installment!

April round-up: Doubled usage (again!), Trigger Toolkit and v1.3 released

v1.3 Platform Upgrade

We’ve launched the 1.3 version of our platform including a new visual toolkit to make it even simpler to use our cloud build service.

Amongst the many changes and improvements over the past month that are rolled into this release, we added:

To upgrade, the format of your config.json needs to be updated, but we’ve made that easy – just run:

forge migrate

Trigger Toolkit

Today we’re also releasing a new way to build and manage Forge apps, called the Trigger Toolkit.

This makes it even easier to use our cloud build service to create apps for iOS, Android and the web:

  • Visual interface to create, build and run apps
  • Console to view status logged messages
  • Simple installer for OSX and Windows
  • Built-in documentation to speed up your development
  • Ability to get in touch with our support team easily from within our tools

Oh, and the command-line tools come bundled with it, so you can still use them for all your automation needs.

We’d love your feedback, you can download it now from our website.

Questions? Let us know on our StackOverflow tag and we’ll respond straightaway.

Doubled again – 20,000 actions in April!

Finally, thanks so much to you for your continued support. We doubled usage again in April with more than 20,000 build, test and package actions:

May is shaping up to be a fantastic month as well with many new features and fixes planned. Stay tuned on Hacker News and Twitter.

How to build hybrid mobile apps combining native UI components with HTML5

Last week we showed a screencast of Trigger.io Forge in action, this week we’ll explain how you can use a native top bar and native tab bar along with your HTML / CSS and JavaScript in a hybrid app created with Trigger.io.

We’ll add more native UI components in the future, but here’s how you can get started creating a beautiful, responsive mobile apps for iOS and Android using just web technologies.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

These are screenshots of a simple recipe app that we created using the Trigger.io Forge native UI components, and we’ll explain how we built it. Along the way we’ll show you how to:

  • Configure Trigger.io Forge to add native top bar and tab bar elements to your app
  • Style the native elements
  • Add listeners to and control the native elements from JavaScript

Please feel free to base your own projects on this – it’s a great springboard for a new project! The code is hosted on github here: https://github.com/trigger-corp/forge-template-list-and-detail

Create your app and add a top bar

You’ll need to use the Trigger.io Forge framework to be able to get native UI components using only web technologies. Get started by signing-up. There’s complete documentation on how to get setup and once you’ve done that just run:

forge create

You’ll be prompted for the app name and once the command completes, you’ll be setup with the code for a ‘Hello World’ app in the src subdirectory. Let’s start by adding a native top bar and testing that in the Android emulator.

Replace your existing src/config.json with this:

{
    "author": "amir@trigger.io",
    "config_version": "2",
    "description": "View ingredients for your favorite recipes",
    "modules": {
        "is": true,
        "logging": {
            "level": "INFO"
        },
        "prefs": true,
        "request": {
            "permissions": []
        },
        "tools": true,
        "topbar": true
    },
    "name": "Recipe list",
    "platform_version": "v1.3",
    "version": "0.1"
}

Enabling the topbar is as simple as setting: “topbar”: true in the modules configuration. Let’s also clean-up index.html until we’re ready to customize it:

<!DOCTYPE html>
<html>
	<head>
	</head>
	<body>
		<div class="content">
			Hello world!
		</div>
	</body>
</html>

You can then run and test the app so far using the following forge commands:

forge build

forge run android --android.sdk ~/Desktop/android-sdk-macosx

Any problems at this point? Check out our FAQ, ask the community on StackOverflow or contact support@trigger.io.

Configuring a tab bar

Great! It turns out that adding the tab bar at the bottom is just as easy – you simply enable the module by adding “tabbar”: true to the src/config.json:

{
    "author": "amir@trigger.io",
    "config_version": "2",
    "description": "View ingredients for your favorite recipes",
    "modules": {
        "is": true,
        "logging": {
            "level": "INFO"
        },
        "prefs": true,
        "request": {
            "permissions": []
        },
        "tools": true,
        "topbar": true,
        "tabbar": true
        },
    "name": "Recipe list",
    "platform_version": "v1.3",
    "version": "0.1"
}

But before that will look good and behave well we need to add some buttons and listeners so we can execute JavaScript to handle the page transitions when you click on each tab. To do that, let’s add a JavaScript file called src/js/main.js:

// A helper function so that when we change tab the web view scrolls to the top of the new page
var scrollTop = function () {
	setTimeout(function () {
		document.body.scrollTop = 0;
	}, 0);
}

// This is the method we are going to call when a tab button is pressed
var updateRecipes = function (search) {
	scrollTop();
	// For now just pop up a message with the tab which was pressed
	alert(search);
}

// Set a better title for the topbar
forge.topbar.setTitle("Recipe shopping list");

// Add our 3 tab buttons to the tabbar and add press listeners
var starterButton = forge.tabbar.addButton({
	text: "Starters",
	icon: "img/tomato.png",
	index: 0
}, function (button) {
	button.onPressed.addListener(function () {
		updateRecipes("starter");
	});
	// This is the default button, activate it immediately
	button.setActive();
	updateRecipes("starter");
});

var mainButton = forge.tabbar.addButton({
	text: "Mains",
	icon: "img/pizza.png",
	index: 1
}, function (button) {
	button.onPressed.addListener(function () {
		updateRecipes("main");
	});
});
var dessertButton = forge.tabbar.addButton({
	text: "Desserts",
	icon: "img/strawberry.png",
	index: 2
}, function (button) {
	button.onPressed.addListener(function () {
		updateRecipes("dessert");
	});
});

Here we’ve using the forge.topbbar.setTitle API call to alter the title at the top, and then used forge.tabbar.addButton to configure the tab bar buttons including adding a listener to execute JavaScript when they are pressed. We reference this file from src/index.html:

<!DOCTYPE html>
<html>
	<head>
		<script src="js/main.js"></script>
	</head>
	<body>
		<div class="content">
			Hello world!
		</div>
	</body>
</html>

You can find the images referenced in src/js/main.js in the example code in Github. You’ll also need to copy those across to your src/img directory.

Now if we build and test (this time on iOS), we see this:

Creating list views and controlling navigation

Ok, now we have the native UI elements in place, we need to create our list view and connect up the tabs so they trigger transitions.

We’ll use the lightweight zepto.js library to help us handle the DOM manipulation. We’ve blogged before on how to create fast HTML5 mobile apps using zepto.js and backbone.js. We’ll create the recipe list using HTML / CSS / JavaScript in the main part of the view since it’s simple to create good-looking lists this way.

First, let’s embed the recipe data and the zepto.js library. You can do that by downloading data.js and zepto.js from the example in Github and putting them in your src/js directory.

Then we update the updateRecipe function in src/js/main.js – this is called when a tab bar button is pressed:

var updateRecipes = function (search) {
	scrollTop();
	$('.content').html('<ul class="list"></ul>');
	$.each(data[search], function (i, recipe) {
		var el = $('<li><img src="'+recipe.thumb+'">'+recipe.title+'</li>');
		el.on('click', function () {
			scrollTop();
			$('.content').html('<div class="recipe"><h3>'+recipe.title+'</h3><img src="'+recipe.img+'"><ul class="ingredients"></ul><p><a href="#" onclick="forge.tabs.open(\''+recipe.source+'\')">View full recipe</a></p></div>');
			$.each(recipe.ingredients, function (i, ingredient) {
				$('.ingredients').append('<li>'+ingredient+'</li>');
			});
			forge.tabbar.setInactive();
		});
		$('.list').append(el);
	});
}

Now to complete the app, all we need to do is add some simple styling to the list and reference the JavaScript files in src/index.html:

<!DOCTYPE html>
<html>
	<head>
		<style>
			body, html, li, ul {
				padding: 0;
				margin: 0;
			}
			body {
				font-size: 1.2em;
			}
			.recipe {
				text-align: center;
			}
			.recipe img {
				max-width: 80%;
			}
			.recipe li {
				display: block;
				font-size: 0.9em;
				padding: 2px;
			}
			.list {
				margin: 0;
				padding: 0;
			}
			.list li {
				display: block;
				border-bottom: 1px solid #aaa;
				padding: 0;
				margin: 0;
				width: 100%;
				overflow: hidden;
				white-space: nowrap;
				text-overflow: ellipsis;
			}
			.list li img {
				height: 50px;
				width: 50px;
				padding: 2px 7px 2px 2px;
				vertical-align: middle
			}
		</style>
		<script src="js/zepto.js"></script>
		<script src="js/data.js"></script>
		<script src="js/main.js"></script>
	</head>
	<body>
		<div class="content">
		</div>
	</body>
</html>

That’s it

You should now be able to build and test the app to look like the screenshots at the top of this post. As an optional extra, you can also explore different styling for the topbar and tabbar by trying out the following API calls:

So now you can create rich hybrid apps, using real native UI components using Trigger.io! Let us know your feedback and how you get on.

Any problems at any point? Check out our FAQask the community on StackOverflow or contact support@trigger.io.

Screencast: Trigger.io Forge in action

We’ve made it simple and super fast to build fully native apps, and we wanted to show that in action with this short video screencast. Check it out:

Code

In 5 minutes, I walk you through our demo photo-sharing app (the full code of which can be found on Github). The app is simple HTML, CSS and JavaScript and put together in an everyday text editor — all very familiar, even for a new web developer.

Just a few extra Forge API methods like forge.file.getImage that we use in the app to access native device features (in this case, the native camera) across platforms.

Build

The fun part is at the command line: building your code into native mobile apps using our cloud build service. Entering

> forge build

will return the native builds (for each platform you’re developing for) to your local machine in seconds. If you’re keen to know how the magic happens behind the curtain, you can read up on it here.

Test

You can then run your builds right away using

 forge run

In the video, we run the app in the iPhone simulator and show you how the native features integrate into the app, and then how the same code can be deployed to the web, and you can use the web app as a way to drive more traffic to app-store downloads.

That’s it — no messing about with bloated IDEs or compiling, not a single line of native code, and a super fast build-and-test cycle.

Try it yourself

Let us know your thoughts on the video, we hope it helps — and if you like what you see, why not try it yourself. The initial setup gives you a bare-bones ‘hello world’ app, which you can build and run right away. Head to the docs for the nitty-gritty and to read up on more Forge API methods and new native UI components — there’s a detailed tutorial there for getting started, and we’ll be happy to help you out on our support desk if you have any trouble. We’re available around the clock at support@trigger.io.

We’ll be posting a followup screencast in the next couple of weeks on how to use Catalyst to debug your apps, so subscribe to our blog or follow us on Twitter to make sure you’re kept in the loop.

Future of Mobile: a case of HTML5 or iOS… really?

Business Insider recently published a presentation on the future of mobile.

We’ve blogged about how developers are underestimating the shift to mobile so much of this presentation echoes our views with stats to back it:

But when it comes to the eponymous future of mobile slide, they lists four possibilities:

1) Android gets its act together, OR

2) Microsoft completes Hail Mary pass, OR

3) HTML5 nukes native apps, OR

4) Apple will take over the world

What?

It’s not credible that either HTML5 or Apple will rule everything, here’s why…

Android’s share is growing

This from an earlier slide in the same presentation:

This growth is despite Android’s so-called fragmentation problems. Android’s share is growing because the carriers have flexibility in how they distribute it and end users could not care less about ‘fragmentation’.  That is a problem for developers, sure, but it is not affecting adoption by end-users.

iOS will continue to dominate the high-end consumer market, but Android’s growth means we’re not on a path to monopoly of the client-side software stack like Microsoft had with PCs. So Apple will make a lot of money, but how on earth are they going to “take over the world”?

It’s also way, way too early to rule out Microsoft – they should at least be able to get some traction in the enterprise just from taking advantage of their Sharepoint install base.

It’s not possible to create Instagram with HTML5

Instagram is used as an example in the deck itself. Thing is, Instagram kind of relies on you to be able to take photos using the camera on your mobile device, and you can’t do that in HTML5!

The fact is that on mobile devices consumers are using apps more and more than the mobile web. So again the trends are against the thesis that HTML5 will nuke native… at least in the short-term.

But what about the next 5-10 years? Won’t all these native features be incorporated into HTML6? Perhaps current features like camera access will be, but I bet that by that time there’ll be a ton of new device types and underlying features that aren’t. The standardization process is slow, and you’d have to believe that innovation in mobile devices is slowing down to think that standards are catching up.

Conclusion – competition is driving innovation and it’s a hybrid world

Google, Microsoft and Apple are competing against each other to own client-side software stack by innovating fast. This competition and innovation is absolutely fantastic for end-users but of course the web and mobile web are going to be hugely important, and will act as a check and balance against the closed stacks.

This hybrid world does of course pose a problem for developers, but you’ll be happy to hear that Trigger.io has you covered whether you’re building for one mobile platform, or many. If you need to build power native mobile apps, with no compromises, using web technologies, then please signup for our mobile engine for web devs.

We’d love to hear your views on the future of mobile, let us know in the comments, on Hacker News, or email anytime at support@trigger.io

Pricing update & Events roundup

Pricing Update

iOS, Android & Web go free: we’ve updated our pricing to better support hobbyist developers while also offering businesses the features and support they need:

  • With our Get Started plan, independent developers can now build for iOS, Android & Web free of charge! In addition, you’ll be able to deploy apps with your own branded splash screens and list them as both paid or free apps in the appstores, at no cost.
  • Our Go Pro plan is targeted at businesses who want more support and are interested in deploying to Windows Phone 7.
  • Finally, we have our Go Enterprise plan to help enterprise app developers leverage powerful CMS & security integrations as well as personalized support to produce enterprise grade apps that can be deployed within an organization as well as directly to consumers.

Contact us at support@trigger.io if you have any questions at all.

SF Beta Platform & API Edition

SF Beta brought together hundreds of developers interested in building apps: Last week we attended SF Beta as one of the 8 demo companies for their Platform & API Edition.

We were presenting next to awesome companies like Iron.io and Songza. It was incredible to see the number of developers who swarmed around our code-filled demo of our cross-platform app development framework.

“This is incredible and looks so much easier to use than other cross-platform frameworks we’ve tried in the past!” – That comment threw a smile on all of our faces!

Thanks for having us Christian @sfbeta!

SF HTML5 Meetup hosted at Trigger.io & CFA HQ

Well over 300 developers stopped by our first hosted event last week to learn about what’s new with HTML5 from guest speaker Boris Smus of Google: Last week we decided to host our very first event – SF HTML5 Meetup put on by the great folks at Kaazing.

The turnout was amazing, beer flowing, code shown. Code For America, our roommates in the Storek building in SoMa, were kind enough to allow us to use their amazing space to put on an incredible first event. We plan to be involved with many of these meetups and hopefully continue to host a few. Check out the pictures below and visit the meetup page to make sure you attend the next one!

10K app build actions in March, added native UI components

When we launched Trigger.io just a few short months ago our goal was to provide web developers with an easy way to build native iOS, Android and web apps from a single HTML5 code base. No Objective-C, Java required. No need to use a particular IDE or get setup for local compiles.

We were challenged to not only provide a great service, but a great service that stood out in a competitive space. Our goal was to do that by providing both an extremely simple development process, and genuinely native UI components. The response received in the first few months has been overwhelming:

“As a web developer wanting to create a quality mobile app this is exactly what I was looking for.”
- Jake Gold – Co-founder, get.com

“Holy crap. @triggercorp‘s Forge platform is awesome. Now if only I could find more time… screw it, I’m done sleeping.”
- Cameron Webb

“Your product is amazing, have never developed an android application more easily (don’t have to touch Java or go through annoying setup!). You guys have a great product here”
- Andy Joslin

 

Nothing is more validating than when real customers use your product and are thrilled by their experience. While we appreciate the kind words from our users, we’re equally excited by their actions. Since launching the platform in January 2012 we’ve experienced triple figure growth every single month – and it isn’t slowing down!

In March alone we’ve had over 10,000 build actions performed through our command-line tools and cloud build service.

Earlier this month we launched Build to Web so you can create websites, mobile websites and native wrapped iOS and Android applications from a single HTML5 code base. Last week, we released the first of our native UI components.

This makes Trigger.io’s Forge framework the only mobile framework available which combines a dead simple development process with real native UI components, and the ability to support all of web, Android and iOS from the same codebase.