Titanium Mobile Synchronous Network Request Workaround

While using Appcelerator’s Titanium Mobile for a Mobile App I am developing in partnership with a local company in Tulsa, OK I found that there is no support for synchronous network requests.  This can be trouble some if you are wanting to follow DRY (Don’t Repeat Yourself) coding and using a JavaScript based framework/library in your app.  I found a solution that works for the app I am developing and wanted to share with other developers.

Note: This code is written for Titanium Mobile 0.9.x.

What is the issue?

Here is an example of a typical XHR request based on the KitchenSink example application:

1
2
3
4
5
6
7
8
var xhr = Ti.Network.createHTTPClient()
xhr.onload = function () { 
       Ti.API.log('I should be first'); 
};
// xhr.open(METHOD, URL, ASYNC_Request(BOOL));
xhr.open('POST', "http:example.com/getData", false);
xhr.send();
Ti.API.log('I should be second')

The result in Titanium’s Log is:

'I should be second'
'I should be first'

Notice that the “I should be second” is called before the “I should be first” is called. This is due to the asynchronous request not stopping the script from running while a remote request is being processed. This stops the ability to create a general app-wide function that processes your Network request since it will finish and return the function before the XHR request data has been returned.

This requires a lot of copy and pasting of your XHR code for every request you make which if you have a change to make you are going to be spending quite a bit of time searching through your code to fix a problem.

What are our needs?

For our app we have a “common.js” file that has all regularly used functions in it. This allows us to follow the DRY method and limit the lines of code written. Since all data for our app is coming from a remote API call for every function in the app we will be making a remote call, processing the data and displaying the results to the user.

When making a XHR request we have the following needs for each request made:

  1. Set the current API URL
  2. Set the headers to get JSON data from the API
  3. Set the headers for Authorization
  4. If an error occurs either force a login or display error notification to the user
  5. Process the data – returned in JSON string so it needs to be converted to JavaScript Object
  6. Return processed data to the original js function call

What is the workaround?

Simply put: Callback function.

First step create a common.js file that contains the wrapper function for XHR requests. Note: we ran into issues including this in the app.js so a separate include file seems to do the trick best. I included some extra functions we use to show the portability we have been trying to achieve.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
var apiServerUrl        = "http://example.com/api/";
var apiKey              = "&apikey=demoApp";
 
/**
*  Create new Object (mainly for name spacing purposes
*/
var myFunctions = {}
 
/**
*  create a full URL with server, path and API key
*/
myFunctions.prepUrl = function(path) {
    return apiServerUrl+path+apiKey;
}
 
/**
* reusable XHR function
* method: GET / POST
* path: local/ads
* data: object
* callBack: function to call when request is complete
* failMsg: array to pass to myFunctions.notice when request fails for Alert
*/
myFunctions.remoteRequest = function(method,path,data,callBack,failMsg)
{
    json = false;
 
    // create new Network Client
    xhr = Ti.Network.createHTTPClient();
 
    // get the full url to API server
    url = myFunctions.prepUrl(path);
 
    xhr.onload = function(e)
    {
        json = myFunctions.jsonParse(e.responseText);
 
        // 
       // this is the function to call in the main javascript file
       //
        callBack(json);
        return true;
    };
 
    xhr.onerror = function(e)
    {
        myFunctions.networkError(xhr.status, url, failMsg);
        return false;
    };
 
    xhr.open(method,url);
 
    xhr.setRequestHeader("contentType","application/json; charset=utf-8");
 
     // set authentication
        username = Ti.App.Properties.getString('username');
        password = Ti.App.Properties.getString('password');
        xhr.setRequestHeader('Authorization','Basic '+Ti.Utils.base64encode(username+':'+password));
 
    if (data == undefined) {
        data = false;
    }
 
    xhr.send(data);
}
 
/**
*  Parse JSON data - could be replaced with JSON2.js library
*/
myFunctions.jsonParse = function(json) 
{
    return eval('(' + json + ')');
}
 
/**
*  display alert to user
*/
myFunctions.notice = function (msg,title)
{
    Ti.UI.createAlertDialog({
        title: title,
        message: msg
    }).show();
}

The key to the above is the callBack function parameter that is passed to the networkRequest function and call in line 41.

Now for the login.js file that Titanium is using to display the current window modal view to login a user.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
Ti.include("common.js");
 
var win = Ti.UI.currentWindow;
 
win.setBackgroundColor('#fff');
win.title = "Login";
 
var actInd = Ti.UI.createActivityIndicator({
    bottom:10,
    height:50,
    width:10,
    style:Ti.UI.iPhone.ActivityIndicatorStyle.BIG
});
 
var u = Ti.UI.createTextField({
    id:'username',
    value:'',
    color:'#336699',
    returnKeyType:Ti.UI.RETURNKEY_NEXT,
    keyboardType:Ti.UI.KEYBOARD_ASCII,
    hintText:'Email Address',
    height:40,
    clearOnEdit:false,
    fontSize:20,
    borderStyle:Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
    clearButtonMode:Ti.UI.INPUT_BUTTONMODE_ALWAYS,
    top:40,
    width:250
});
 
win.add(u);
 
u.addEventListener('return',function(e)
{
    p.focus();
});
 
 
var p = Ti.UI.createTextField({
    id:'password',
    value:'',
    color:'#336699',
    returnKeyType:Ti.UI.RETURNKEY_GO,
    keyboardType:Ti.UI.KEYBOARD_ASCII,
    hintText:'Password',
    height:40,
    passwordMask: true,
    clearOnEdit:true,
    fontSize:20,
    borderStyle:Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
    clearButtonMode:Ti.UI.INPUT_BUTTONMODE_ALWAYS,
    top:90,
    width:250
});
 
win.add(p);
 
var submit_button = Ti.UI.createButton({
    id:'submit_button',
    title:'Login',
    color:'#336699',
    height:32,
    width:100,
    fontSize:12,
    fontWeight:'bold',
    top:150
});
 
win.add(submit_button);
 
var navActInd = Titanium.UI.createActivityIndicator();
win.setRightNavButton(navActInd);
 
var label = Ti.UI.createLabel({
    top:10,
    color:'#777',
    height:'auto',
    width:300,
    font:{
        fontSize:15
    }
});
win.add(label);
 
/**
* This is where the networkRequest function is going to be used.
*/
submit_button.addEventListener('click',function(e)
{
    u.blur();
    p.blur();
 
    if (p.value.length < 1 || u.value.length < 1) {
        myFunctions.notice("Please enter both username and password", "Login Error");
        return;
    }
 
    navActInd.show();
 
    failMsg = ["Username/Password Failed", "Login Error"];
 
 
    r = myFunctions.remoteRequest("GET","user/verify?",false,callBack_login,failMsg)
 
    if (!r) {
        navActInd.hide();
        return;
    }
 
});
 
/**
* Once the XHR request is complete this function will be called.  You can put all your post data display events within this function.
*/
var callBack_login = function(data) {
    Ti.App.Properties.setString('username', u.value);
    Ti.App.Properties.setString('password', p.value);
 
     navActInd.hide();
 
    Ti.UI.currentWindow.close();
    Ti.UI.close();
}

In line 103 we call the myFunctions.remoteRequest() function. Notice that the callBack_login does not include ’s.

So far this solution has been working great for our development. We hope to see Titanium Mobile support synchronous requests in the future, but our current modular development allows us make the switch quite easily.

I am open to any questions or comments that you may have about our solution.

ZendCon 2009: Day 1 Review

Yesterday was the first official day of talks for ZendCon 2009.  After last year I didn’t know if I was going to take the plunge this year and attend this year due to my distaste for ZendFrameworkCon that I experienced last year.  After one day I can say that this year’s conference has been great.  Great talks (mostly), great people and a great location.

This year I have been participating in the IRC backchannel (#zendcon on freenode), Twitter and Google Wave with many of the fellow ZendCon attendees.  They make it possible for me to hear about what is happening throughout the conference and in the case of Google Wave I can view the notes taken in all of the different talks I wasn’t able to make it to.  Sometimes after reading the live notes I wanted to leave my session, but overall so far I have enjoyed the talks.

Here is the list of what I made it to yesterday:

  • Planning for Synchronization w/ Browser-Local Databases
  • Xdebug – PHP Developer’s swiss-army knife
  • Il8n with PHP 5.3
  • Enterprise-Class PHP Security
  • Seven Steps to Better OOP Code

The keynote about the cloud after the ZCE lunch was less than interesting, but thankfully the IRC chat made it exciting. I’m looking forward to the PHP Framework shootout tomorrow, which I expect will be the best Keynote of the conference.

Last night ended with going to the local bar Firehouse which is part owned by my friend from when I still lived in San Jose.  Ended up running into several friends I haven’t seen in over 3 years and catching up with them, which was great.  Still hurting a bit this morning from partying last night, but I made it to the breakfast and keynote this morning.

Now on to seizing the day!

Tagged: Seven things that probably you may not know about me

Luke Crouch tagged me with the first internet meme of 2009 so hear goes with 7 things u may not know about me:

  • In 5th grade I had a small solo singing part in a school play. (I only wanted to get out of class for an afternoon and ended up getting picked)
  • In 1998 I legally changed my last name and now carry on my mother’s maiden name.
  • In 1997 I was featured in Fast Company magazine in the article Little Hands Teach Big Hands (note: article uses my old last name).
  • For two years (‘04-’05) I taught computers at an elementary school in San Jose, California.
  • My favorite beer is James Squire Amber Ale, but I haven’t had one in over 3 years (not sold in Oklahoma).
  • I was raised as a Christian Scientist.
  • I have owned my own business, Shark Media, since I was 17 years old.

I’m calling out/tagging these peeps:

Update: I guess the undocumented rules state you can’t tag people who have already been tagged, so I’m down to 5 people.

ZendCon 2008: Day 2 Review

Yesterday I finished up my second day at ZendCon and learned a few more things:

  1. ZendCon should be renamed to ZendFrameworkCon for next year
  2. Most talks are high-level presentations and very few show the code that matters to me and presumably most programmers attending
  3. Terry Chay is still one of the best presenters I have seen (note: I did not count the # of swear words he used, but if you want to check search for #tcfc on Twitter
  4. I clearly need to learn how to use SPL and Iterators more in my code
  5. Selenium and PHPUnit are testing suites I need to work more with and properly implement
  6. I finally have a reason to move to Zend Studio 6 over Zend Studio 5.5 that I have been using for the last two years
  7. You can control a presentation using your iPhone
  8. The Ajaxian guys not only make a great website and podcast their presentations are beautiful.

Here are the sessions I attending on Wednesday:

ZendCon 2008: Day 1 Review

Yesterday I attended the first day of 2.5 days of ZendCon 2008.  This was the second year I have attended and much more relaxing knowning that I do not need to take a test this year.  Last year I became a PHP5 Zend Certified Engineer (ZCE), this year I got a shiny grey ribbon attached to my badge.

I sat in on the following sessions:

As a whole the day was much more involved and more content than last year.  The two sessions that stood out for the day were Distribution and Publication With Atom Web Services and The Knight Rider Methodology to Software Development which if you know Ben Ramsey or Eli White you know why I enjoyed their talks the most.  Eli was even able to incorporate The Hoff in his presentation, which I’m sure thrilled the German attendees. I’m looking forward to using the Atom publishing and can finally see the difference between RSS 2.0 and Atom and why the developer community has been on the Atom side.

Twitter is being used by several of us heavily and you can view all messages from attendies using the following hash: #zendcon

I have gathered lots of info and looking forward to using the information I have and will learn this week during my talk at TulsaTechFest 2008.

More tomorrow.