Building the Derby App in Titanium ! 297
Once this is set you are ready to begin building your application.
BUILDING THE DERBY APP IN TITANIUMThe same patterns explained in the native application chapters are used to develop the Derby Names application, only this time in Titanium. You create the UI, using some of the device features (GPS, Accelerometer), and communicating with the web service you created in Chapter 3.
Common UI PatternsThis section discusses the basic patterns in mobile applications: standard ways to represent data, make selections, navigation patterns, and display quick alerts to the user.
TablesThe Table UI element is the standard way to display data.
You can bind JSON objects directly to tables (as long as they have a title element):
var data = [{title:”Row 1”},{title:”Row 2”}];var table = Titanium.UI.createTableView({data:data});win.add(table);
Or you can bind arrays of TableViewRow objects. When you create a TableViewRow object you can assign other UI elements to the row as well:
function BindTeamsToTable(dataFromService){var dataToBind = [];Ti.API.info(JSON.stringify(dataFromService));for (var i=0; i<dataFromService.length; i++){ var leagueName = dataFromService[i].LeagueName; var rowToAdd = Ti.UI.createTableViewRow( { title: leagueName, //main field to be bound and rendered hasChild: true //Show child arrow });rowToAdd.addEventListener(‘click’, function(){ teamToSearch = this.title; derbyservice.getRoster(BindRosterForTeam, teamToSearch); tabGroup.setActiveTab(0); });dataToBind.push(rowToAdd);}var table = Ti.UI.createTableView({height: 368, top: 0, data: dataToBind});win2.add(table);}
c10.indd 297c10.indd 297 28/07/12 6:07 PM28/07/12 6:07 PM
298 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
If you have large sets of data to bind, this table element will be your go-to UI to display the data to your users.
The Table UI element is signifi cantly different from the HTML table element, so please don’t confuse one with the other. Whereas modern web development frowns upon the use of tables for layout, using tables to display data in mobile applications is commonplace.
You can fi nd more examples in the Kitchen Sink; try running both the iOS version and the Android version simultaneously to see these differences.
PickersThe picker UI object illustrates some of the differences between how Titanium generates UI elements for iOS versus Android.
By default, the iOS element represented by this picker is a spinner (a date picker in iOS), whereas in Android the element represented by this picker is, by default, a drop-down. Titanium handles this by adding the usespinner property to the create method for this picker, but here you run the risk of maintaining parity between look and feel inside your app versus look and feel consistent with the OS on which your app is running. Additionally, although you can add custom views to your rows of the picker in Titanium, they will not be rendered on the Android version.
var picker = Titanium.UI.createPicker();var dataToBind = [];dataToBind[0]=Titanium.UI.createPickerRow({title:’Nose’});dataToBind[1]=Titanium.UI.createPickerRow({title:’Friends’});//dataToBind[2]=Titanium.UI.createPickerRow({title:’Friend’s Nose’});//You can’t pick this.picker.add(dataToBind);
Using a picker provides users a uniform way to enter data quickly.
Navigation (Back Stack) and Tab GroupsIn iOS you need to have a back button on a child view. It is not only standard in the OS, it is expected. As stated in the Apple Human Interface Guidelines (http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/MobileHIG/
UIElementGuidelines/UIElementGuidelines.html):
c10.indd 298c10.indd 298 28/07/12 6:07 PM28/07/12 6:07 PM
Building the Derby App in Titanium ! 299
You can use a navigation bar to enable navigation among different views, or provide controls that manage the items in a view.
Use the title of the current view as the title of the navigation bar. When the user navigates to a new level, two things should happen:
" The bar title should change to the new level’s title.
" A back button should appear to the left of the title, and it should be labeled with the previous level’s title.
If users can navigate down a path, developers need to provide a simple way of going back up the stack. Additionally, navigation back up the stack may involve persisting state as well, so be sure to account for whether a given view’s state needs to be persisted when retracing a user’s steps through the navigation stack. In Android, the hardware back button and the OS persist state and the “back stack” for you. That being said, if you have a back button in your UI, make sure that it is not displayed when on an Android device, because it will be considered unnecessary and sloppy.
When using tab groups the standard UI layout differs between iOS and Android. iOS displays tabs inside apps on the bottom. There is generally a black background with some see-through icons, and the tab highlights blue when selected (either the background of the tab or the icon on top). In Android, the tab navigation is almost always rendered on the top, with black and gray being the colors used to represent active and inactive. This only goes to further demonstrate the need for the Android and iPhone project subdirectories, to distinguish device-based and OS-specifi c layouts.
var win1 = Titanium.UI.createWindow({ title:’Roster’, backgroundColor:’#fff’});var tab1 = Titanium.UI.createTab({ icon:’KS_nav_views.png’, title:’Roster’, window:win1});
var win2 = Titanium.UI.createWindow({ title:’Derby Team Names’, backgroundColor:’#fff’});var tab2 = Titanium.UI.createTab({ icon:’KS_nav_ui.png’, title:’Team Names’, window:win2});
tabGroup.addTab(tab1); tabGroup.addTab(tab2);
tabGroup.open();
c10.indd 299c10.indd 299 28/07/12 6:07 PM28/07/12 6:07 PM
300 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
First you create your two basic windows, and bind them to tab elements. Once bound, you add those tabs to the group of tabs. This builds your clickable UI for you.
Modal FormsModal forms are most commonly used to break away from your UI while communicating with a third-party source. This could be authenticating with OAuth or OpenID, publishing content to a social network, or anytime you want to lock the UI for the user.
var modal = Titanium.UI.createWindow();modal.open({modal: true, //Set it as modalnavBarHidden: true, //Hide the UI Chromefullscreen: false //Make sure that it isn’t rendered full screen.})
The preceding code creates a new modal window. It will show up over top of the current window, and not display any of the standard UI for a window element.
AlertsThere is a lot to be said for the value of a simple alert modal. Calling one is simple. A straight Javascript:alert(‘message’); renders out as an OS native message. That being said, it is best not to have this as the only way to communicate data to app users, because alerts block the UI and prevent things from happening behind the scenes. And queuing multiple, successive alerts can potentially put your UI in a very unmanageable state. Tread with caution.
var message = “Greetings Program!”;alert(message);
You are also afforded the OptionDialog view for displaying alerts that require a response. The following code snippet shows how to create an alert that requires a response. Setting the cancel property to -1 denotes that there is no cancel action in the list of potential options:
var dialog = Titanium.UI.createOptionDialog({ title: ‘Trick Question – Did you walk to school, or buy your lunch?’, options: [‘Walked to School’,’Bought my Lunch’], cancel:-1});dialog.show();dialog.addEventListener(‘click’, new function(e){//e.index = index of option selected.});
Once you have built your user interface, you will need to bind data to it. The following options show the ways you can get data onto the device.
c10.indd 300c10.indd 300 28/07/12 6:07 PM28/07/12 6:07 PM
Building the Derby App in Titanium ! 301
O! ine StorageOffl ine storage refers to any data that you will persist on the device. It can be as simple as a property bag, storing key-value pairs, or updating resource fi les, or it can be as complex as a full SQLite database.
SQLiteTitanium provides the developer with an interface to store data. This is exposed through the Titanium.Database namespace. You can create a database programmatically, but I would recommend installing one from a SQLite script in your resources folder:
var db = Ti.Database.install(‘../derbygirls.sqlite’,’derbyGirls’);
Once the database is on the device, make standard CRUD calls using the SQLite syntax:
var teamName = ‘Lansing Derby Vixens’; var rows = db.execute(‘SELECT * FROM DerbyNames WHERE TeamName=”’ + teamName + ‘”’); var data = [ {title:’’ + rows.fieldByName(‘Name’) + ‘ – ‘ + rows.fieldByName(‘JerseyNumber’) + ‘’}]; var derbyNameTable = Ti.UI.createTableView({ data:data }); var currentWindow = Ti.UI.currentWindow;currentWindow.add(derbyNameTable);
If you do not need to store relational data sets, but still want to store large pieces of information or content, the option afforded to you is Isolated Storage.
Isolated StorageIn most mobile SDKs you are allowed a sandboxed area of the fi lesystem most commonly known as isolated storage. Titanium exposes this functionality through its Titanium.Filesystem namespace.
Reasons to use isolated storage would be to store resources downloaded remotely or saving large data sets outside of a database environment. The following code looks on the fi lesystem and if it fi nds it adds it to the specifi ed window.
for (var i = 0; i < derbyTeams.Length; i++){ var teamLogo = Titanium.Filesystem.getFile(derbyTeams[i].TeamId + ‘.jpg’); if (!teamLogo.exists()) { Ti.API.error(“We have not loaded a logo for this team yet.”); return;
c10.indd 301c10.indd 301 28/07/12 6:07 PM28/07/12 6:07 PM
302 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
} else{ var logoItem = Ti.UI.createImageView( { image: teamLogo, height: auto, width: auto });
win1.add(logoItem); }}
Preferences and SettingsUsing the Property Bag (Preferences and Settings) is the simplest form of offl ine storage available. It is mostly used for storing authentication tokens and default display parameters. With the Titanium.App.Properties namespace, you can persist these properties between application runs. The following code shows how to save and retrieve properties from the property bag.
//Setting the UserNameTi.App.Properties.setString(“username”,”derbyfan419”);//Getting the Hashed Password from Property Bagvar hashedPassword = Ti.App.Properties.getString(“password”);
Storing lots of information on your device can be time-consuming and diffi cult to maintain, so often applications query data from a remote location. Web services provide an easy way to retrieve these sets of data.
Web ServiceThis section shows examples to query from the web service created in Chapter 3, discusses formatting the data to be read by Titanium, and describes some of the “gotchas” that occur in platform-specifi c calls to a web service.
JSON Is Your FriendChapter 3 discussed the technology used to create the web service, but — platforms aside — what you really need to wrap your head around is JavaScript Object Notation (JSON). JSON is a simplifi ed way to store your data in a serializable way over the wire, while still following the structure of the initial object. The service you use outputs JSON to parse in the app. A great resource to see how a JSON object is outlined is the Json Parser Online (http://json.parser.online.fr/), as shown in Figure 10-14.
c10.indd 302c10.indd 302 28/07/12 6:07 PM28/07/12 6:07 PM
Building the Derby App in Titanium ! 303
The JSON parser gives you a nice visualization of the object, and shows any parsing errors. Effectively an array of dictionaries (key-value pairs), a JSON object can provide you with an entire object graph with little overhead.
Something to note at this point: when building up URLs to send in an XHR request in Titanium for Android (at least as of Titanium version 1.7), you have to do some postprocessing to it before passing it to be sent. If you are building for iPhone, you don’t have to worry. The following code shows the call necessary to format these request strings for Android.
if (Titanium.Platform.name == ‘android’) { requestString = requestString.replace(/s/g, ‘%20’);}
What follows is the odata object that holds the getData function. When retrieving data from the service, pass the parameters to it, parse the response as JSON, and then pass it to the successFunction callback to bind the data to the window:
function odata(){this.getData = function (requestString, successFunction){ if (Titanium.Platform.name == ‘android’) { requestString = requestString.replace(/s/g, ‘%20’); }var xhr = Titanium.Network.createHTTPClient();xhr.onload = function () { var response = JSON.parse(this.responseText); var result = response.d.results;
FIGURE 10"14: Json Parser Online
c10.indd 303c10.indd 303 28/07/12 6:07 PM28/07/12 6:07 PM
304 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
//for some reason oData will return it both ways, in .d and .d.results if (result == null) { result = response.d; }
var gotData = new Date(); successFunction(result);};
xhr.onerror = function (e) { Titanium.UI.createAlertDialog({ title: ‘Error retrieving data’, message:‘An error occurred retrieving data. Please try later.’ }).show(); Titanium.API.error(requestString);}; xhr.open(‘GET’, requestString); xhr.setRequestHeader(‘Accept’, ‘application/json’); var send = new Date(); xhr.send(); }
This class is included in app.js so that all other windows can call into it statically if necessary:
Titanium.include(‘network/odata.js’);var odata = new odata();
Next is the derbyservice class, which is also included in app.js:
Titanium.include(‘network/derbyservice.js’);var derbyservice = new derbyservice();
It provides the method calls in the views to get data to bind:
function derbyservice(){ var baseServiceUrl = “http://derbynames.gravityworksdesign.com/DerbyNamesService.svc/”; this.getAllNames = function (successFunction) { var serviceString = baseServiceUrl + “DerbyNames”; odata.getData(serviceString, successFunction); }
this.getTeamNames = function(successFunction) { var serviceString = baseServiceUrl + “Leagues”; odata.getData(serviceString, successFunction); }
this.getRoster = function (successFunction, leagueName) { var serviceString = baseServiceUrl +
c10.indd 304c10.indd 304 28/07/12 6:07 PM28/07/12 6:07 PM
Building the Derby App in Titanium ! 305
“DerbyNames?$filter=League eq ‘” + leagueName + “’”; odata.getData(serviceString, successFunction); }}
Now that you have a way to get data, this section discusses using location to pare down or request data.
GPSAs more and more people get smartphones with built-in GPS devices, the call for location-based content, information, games, social media integration, and driving directions is expected. Titanium does provide access to the metal for talking with the GPS. The methods to access GPS are available in the Titanium.Geolocation namespace within Titanium Mobile.
Depending on your usage, the geolocation API allows you to set the accuracy of the GPS data returned. You are afforded multiple options: Best, Nearest Ten Meters, Hundred Meters, Kilometer, and Three Kilometers. It is best practice to set your accuracy to one of these options prior to accessing the GPS:
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST;
The GPS also has an understanding of how far the device has moved, and triggers an update event when the threshold of the distance fi lter has been crossed. Distance Filter is a double, and it is in meters. Default, if nothing, is set to 0, which means GPS update events are continuously fi red.
Titanium.Geolocation.distanceFilter = 15.24; //50 Feet
To get the position as reported by the GPS, you must call the Geolocation function getCurrentPosition. It provides object location with the following properties: latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed, and timestamp. The location property contains the following subproperties: magneticHeading, trueHeading, accuracy, x, y, z, and timestamp. The following code shows how to query the current position of the device and check with the web service to see what teams are close to your location:
var cityToQueryBy = ‘Lansing’;
Titanium.Geolocation.getCurrentPosition(function(e){ var latitude = e.coords.latitude; var longitude = e.coords.longitude; var altitude = e.coords.altitude; var accuracy = e.coords.accuracy; var altitudeAccuracy = e.coords.altitudeAccuracy; var heading = e.coords.heading; var speed = e.coords.speed; var timestamp = e.coords.timestamp;
//This turns your location into a human readable object Titanium.Geolocation.reverseGeocoder(latitude, longitude, geolocationCallback);
c10.indd 305c10.indd 305 28/07/12 6:07 PM28/07/12 6:07 PM
306 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
});
function geolocationCallback(data){ var places = data.places; if (places.length > 0) { cityToQueryBy = places[0].city; }
derbyService.getTeamsByCity(cityToQueryBy, bindDataCallback);}
function bindDataCallback(data){ if (data.length > 0) { //There were teams based on your search criteria bind them to a UI element. }}
//This would live in derbyservice.jsthis.getAllNames = function (queryVal, successFunction){ //OData Filter to look for Teams by City By Name var serviceString = baseServiceUrl + “DerbyNames?$filter=League like ‘%” + queryVal + “%’”; odata.getData(serviceString, successFunction);}
You can use a headingfilter as opposed to a distancefilter to track only where the user is going, versus information about where the user is. Difference in use cases would be a location-based application (foursquare), versus a heading-based app (compass).
Now that you understand location-based events, the next section discusses interactions with your device.
AccelerometerBoth the iPhone and Android devices have a built-in accelerometer and Titanium has an API for accessing it. The Titanium.Accelerometer namespace provides access for adding an event listener to read the coordinates of the accelerometer data, but the Appcelerator documentation recommends that you remove the event listener when not in use.
Common uses for accelerometer data include triggering aspect changes (portrait to landscape), triggering media (turn the ringer off when the phone headset is set down), and randomizing navigation (spin the wheel, or shake to refresh).
c10.indd 306c10.indd 306 28/07/12 6:07 PM28/07/12 6:07 PM
Building the Derby App in Titanium ! 307
Here is a basic example of checking your accelerometer data on the x-axis:
var shakeCount = 5;var xShakes = [];
Titanium.Accelerometer.addEventListener(‘update’,function(e) { if (shakeCount > 0) { Ti.API.debug(“accelerometer – x:”+e.x+”,y:”+e.y+”,z:”+e.z); xShakes.push(e.x); shakeCount–; } else { Ti.Accelerometer.removeEventListener(‘update’); WhipItGood(xShakes[0], xShakes[3]);}});
var shakeThreshold = 1.5;function WhipItGood(int x1, int x2) { if((x1 – x2) >= Math.abs(shakeThreshold)) { Ti.API.info(“It can be said that on the x axis this device has been Whipped well.”); GetRandomDerbyPlayer(); }}
function GetRandomDerbyPlayer(){ //Get a random Number (at last count our record count was around 25k var randomNumber=Math.floor(Math.random()*25001) derbyService.getDerbyPlayerById(randomNumber, randomDerbyCallback);}
function randomDerbyCallback(data){ if (data.length > 0) { //You received a random derby player. }}
//This would live in derbyservice.jsthis.getDerbyPlayerById = function (queryVal, successFunction){ //OData Filter to look for Teams BY City BY Name var serviceString = baseServiceUrl + “DerbyNames?$filter=DerbyNameId eq ‘” + queryVal + “’”; odata.getData(serviceString, successFunction);}
c10.indd 307c10.indd 307 28/07/12 6:07 PM28/07/12 6:07 PM
308 ! CHAPTER 10 GETTING STARTED WITH APPCELERATOR TITANIUM
SUMMARYTitanium is not a magic bullet. It is a solid framework for developing a single codebase to deploy to multiple platforms. In addition, it allows developers to use a language they are more familiar with to create apps in a domain outside of their knowledge. Titanium is not an exact match to native languages. Not all features of the mobile platforms are exposed (or can necessarily be exposed) in its API. With the addition of Titanium Studio, developing in the framework has grown by leaps and bounds. The team at Appcelerator works to pack as much functionality into their framework as possible. Titanium is an excellent tool to learn mobile device programming, and for many projects can provide the necessary functionality to deliver a fi nished product.
c10.indd 308c10.indd 308 28/07/12 6:07 PM28/07/12 6:07 PM