Building a better Titanium app with Backbone Events

Building a better Titanium app with Backbone Events

Building an app with Titanium gives you access to all of the speed of a native framework while giving you the flexibility that you know and love from Javascript, but exposes you to all manner of potential memory issues that traditional native developers are not familiar with. Enter Backbone.js, a not-so-well-kept secret to creating a well structured app dizzyingly quickly. Backbone allows you to define events on most any javascript object, giving you flexibility and control over how your components interact with each other.

Dependencies and upgrading

If your project is built with Alloy, good news!  An older version of Backbone ships with Alloy by default, as well as its underscore dependency.  If you’re maintaining a classic Titanium project, you can download the most recent version of Backbone and underscore from here and use them like normal commonjs libraries.  Additionally, we have provided an updated version of Backbone that has been modified to work with the current version of Alloy.  To use it in an Alloy project, just place the minified version in your lib folder and add


Backbone = require('backbone.min.js');

to alloy.js.

An aside on the topic of mixins

Backbone is an example of an awesome design pattern that Javascript encourages, namely mixins.  The way mixins work is that they define a set of functions that can work if assigned to any Javascript Object.  This allows you to eschew classical inheritance structures in favor of defining small, self-contained pieces of functionality that can be harmoniously applied to a single Object (which can have a prototype, other mixins, or both!).  They’re also a great way to achieve an effect similar to classical inheritance in cases where you can’t use normal prototype chains, like Alloy controllers.  Mixins can be defined in a number of ways, be it by creating a normal Javascript object containing a set of functions that can be copied to another object, or by declaring a function that applies all of the functions in the mixin to a parameter object.  Backbone.Events is an example of a mixin declared as a normal Javascript object.

On to the main Backbone.Event

The core advantage of building a Titanium app with Backbone is its powerful event system, and it couldn’t be easier to use.  To mix in Backbone Events to an alloy controller, just add

_.extend($, Backbone.Events);

to the beginning of your controller (note that you can substitute any javascript object for the first argument).

Now that you have backbone events mixed in to your controller, you can start listing to and triggering them.  First, let’s set up an event listener.


var eventedController = Alloy.createController("controllerWithEvents");

eventedController.on('aCoolEvent', function(){
  alert('Something neat just happened!');
});

To break down that code snippet, using the ‘on’ function from underscore you can subscribe a function to be executed every time that some event is fired.  Then, from whithin the other controller, you can use the ‘trigger’ function to notify listeners of some action.  For example, if you had some picker controller, you could trigger a ‘change’ event whenever the user makes a new selection, and then listen for that event from the parent controller.

So why not normal callbacks?

The advantage of using Backbone events over just using normal Javascript callbacks is twofold.  For one, you can easily register multiple event listeners to a single event.  Since the event is just a simple ‘broadcast’, you can hook up multiple listeners for the same event without the triggering controller having to have any awareness of its listeners.  For example, you could set up a UI controller to listen to cache update events. You could then set up your network library to just broadcast update events.  Then, if you later need to hook something in to automatically update on cache update, you can easily listen to the same event.

Secondly, Backbone exposes a handful of functions that makes it very easy to clean up event listeners en masse.  You can use ‘off’ to dismiss event listeners individually, dismiss listeners for an individual event, or tear down every event listener associated with a given object.


//make an evented object
var eventObject = _.extend({}, Backbone.Events);
//register a few listeners
function listenerOne(evt){
  // ...
};
//register a named function as a listener
eventObject.on('someEvent', listenerOne);
//register a bunch of anonymous listeners
for(var ii = 0; ii < 10000; ii++){
  eventObject.on('someOtherEvent', function(){
    // ...
  });
}

//now let's get weird.

//register a bunch of events with random names
for(var jj = 0; jj < 10000; jj++){
  //use the built in sha1 function to generate a event name from the current time 
  //(realistically, you would generate events like this based on some data object,
  //sensible example omitted for brevity) 
  var eventName = 'dynamicEvent' + require('alloy/sha1').hex_sha1(new Date());

  //now register an event listener with the dynamic event
  eventObject.on(eventName, function(){
    // ...
  });
}

//dispose of just on listener on the object, providing event name and function reference
eventObject.off('someEvent', listenerOne);

//uh oh, how are we going to get rid of those anonymous listeners?

//just don't provide the function reference to off!
eventObject.off('someOtherEvent');
//This will tear down every listener for the provided event.

//and now to tear down the random, misc events.
//we don't know the event name, so just call off to tear down everything
eventObject.off();

In addition to this system for managing events, the most recent version of Backbone provides a number of functions to allow objects to better manage their event listeners.


//index.js

//create some evented object
var eventObject = _.extend({}, Backbone.Events);

//also event the controller
_.extend($, Backbone.Events);

//set up to listen to some event exactly once
eventObject.once('someEvent', function(){
  // ...
});

//set up the controller to listen for events on the object
$.listenTo(eventObject, 'someEvent', function(){
  // ...
});
//set up the object to listen for some controller event once
eventObject.listenToOnce($, 'someControllerEvent', function(){
  // ...
});
//tear down the listener from the controller
$.stopListening(eventObject, 'someEvent');
//you have the same argument flexibility as off with stopListening
//stop listening to an entire object
$.stopListening(eventObject);
//stop listening to anything
$.stopListening();

The advantage of using the listenTo pattern is that either object can tear down an event listener shared by them. This has important implications for Alloy controllers: as long as you manage most of your cross-controller interactions through Backbone.Events, you can tear down every event listener originating from a controller with a single call. We can use this feature to set up intelligent disposal functions that let us tear down most references to a controller quickly and prevent memory issues.


//index.js

//declare a disposal function
$.dispose = function(){
  //first, trigger an event to notify other controllers that this one is being torn down.
  $.trigger('dispose');
  //then, use Underscore.defer to set up a function to be executed after the dispose event
  //has been handled.
  _.defer(function(){
    //tear down all listeners on this controller
    $.off();
    //and tear down all of the event listeners originating from this controller
    $.stopListening();
  });
};

And that’s it. From here, you can set up other controllers to listen to the dispose event and do things like remove the controller when its disposed or build out new UI on controller disposal. Then, after all of the dispose listeners have been handled, all event relationships are automatically disposed of. Assuming you clean up all of your references in dispose events, there’s nothing else you need to do for the average controller to keep memory in check. Bear in mind that things like app-wide events (such as events on Titanium.App, Titanium.Geolocation, Titanium.Gesture, etc.) can still lead to memory leaks and should be handled in your disposal listeners.

You can read more about Backbone.js at the project’s website.
For more information about Backbone’s dependency Underscore and how you can use it to code better and faster, check out our blog post!