I have an odd problem that I’ve been beating my head into a wall over for the past few hours.
I’m working on an iPhone app in Appcelerator Titanium, and it currently wants to wait until other code has been run before getting the results of a couple of HTTPClient requests, despite the fact that I’m calling them before I try to use their results.
function getMarkers(e, miles){ //The function with the HTTPClient calls that are firing last, trimmed to have only the relevant code.
var markers = [];
Ti.API.info("Getting markers");
xhr.onload = function()
{
var data = Ti.XML.parseString(this.responseText);
var ref = data.documentElement.getElementsByTagName("reference");
for(var i =0; i < ref.length; i++){
var marker = new Object();
marker.ref = ref.item(i).text;
var request = Titanium.Network.createHTTPClient();
request.setTimeout(10000);
request.onload = function(){
var data = Ti.XML.parseString(this.responseText);
marker.address = data.documentElement.getElementsByTagName("formatted_address").item(0).text;
if(data.documentElement.getElementsByTagName("formatted_phone_number") != null){
marker.phone = data.documentElement.getElementsByTagName("formatted_phone_number").item(0).text;
} else {
marker.phone = null;
}
marker.icon = data.documentElement.getElementsByTagName("icon").item(0).text;
marker.lat = data.documentElement.getElementsByTagName("lat").item(0).text;
marker.lng = data.documentElement.getElementsByTagName("lng").item(0).text;
marker.name = data.documentElement.getElementsByTagName("name").item(0).text;
if(data.documentElement.getElementsByTagName("url") != null) {
marker.url = data.documentElement.getElementsByTagName("url").item(0).text;
} else {
marker.url = null;
}
markers.push(marker);
Ti.API.info(markers.length);
}
request.open("GET","https://maps.googleapis.com/maps/api/place/details/xml?reference=" + marker.ref + "&sensor=true&key=" + Ti.App.apiKey);
request.send();
}
};
xhr.open("GET","https://maps.googleapis.com/maps/api/place/search/xml?location=" + googleLatLng + "&radius=" + radius + "&types=" + Ti.App.types + "&sensor=true&key=" + Ti.App.apiKey);
xhr.send();
return markers;
}
// actually draw the markers on the map
function drawMap(markers, currentLoc)
{
var i;
Ti.API.info("Adding markers...");
for(i=0;i<markers.length;i++)
{
Ti.API.info("Marker " + i);
Ti.API.info(markers[i].name);
var ann = Titanium.Map.createAnnotation({
image:markers[i].icon,
animate:true,
latitude:markers[i].lat,
longitude:markers[i].lng,
title:markers[i].name,
subtitle:markers[i].address
});
if(markers[i].url != null){
ann.rightButton = markers[i].url;
}
mapview.addAnnotation(ann);
}
Ti.API.info("Markers added"); //When this block is called, markers.length == 0
}
// find the user's location and mark it on the map
function waitForLocation(e)
{
//Do stuff about finding current location and marking it on the map. This stuff works and a pin drops for the current location
drawMap(getMarkers(e), currentLoc);
}
waitForLocation gets called first, which then calls the others. Xcode outputs the following:
[INFO] Getting markers
[INFO] Adding markers...
[INFO] Markers added
[INFO] 1
[INFO] 2
[INFO] 3
[INFO] 4
This means that it’s going into the getMarkers function (first line), then leaving it (next two lines), then going back to the getMarkers function to actually get the markers (last four lines, output of markers.length as each marker is added). Knowing that
I moved the .open() call from before .onload() call based on an answer I found here, but I get the same whether .open() is before or after .onload().
I found information that the httpClient call performs its task asynchronously (an important bit of information that is lacking from the API reference). Knowing this, it makes sense that it leaves the function, but it does screw with how information is being handled, as I need the markers downloaded before trying to add them.
On talking with my iPhone developer coworker, he mentioned that he handles them using a delegate and a delegate.connectionDidFinishLoading call. Is there, perhaps, either a way to hook into this, or a Titanium implementation of this that I could use?
Is there another good way that I can make sure it doesn’t try to load the markers before the app has actually downloaded them? It only needs to work for iPhone, so iPhone-specific options are fine.
After a lot of searching and hair pulling, I finally managed to get it working the way I needed.
.open()has a third, boolean, parameter that forces the HTTPClient to run synchronously (another tidbit not mentioned in the documentation). Setting it tofalsewill make it run synchronously. Doing that allowed me to test the code in the order I expected it to run.I also found that I couldn’t make an array of all the markers and load them at once, so I adjusted my
addMarker()function to only take one marker, and called it inside the loop that gets the marker data. Once I got that working, I was able to make the HTTPClient calls asynchronous again.