iPhone application development, step by step

At AjaxWorld tomorrow, we'll be giving a talk about how we built an iPhone application in OpenLaszlo at iPhoneDevCamp a few months ago.

The iPhone application is NEWSMATCH. It's a game that presents items from a Yahoo! News RSS feed, with images and titles. The challenge is to match an image with the headline it illustrates. To play, you click(or tap) on an image, then click (or tap) on a headline. If they match, you're given the opportunity to read the story.

We wrote this app from scratch in two days, using OpenLaszlo. It ran on the iPhone, with very few iPhone-specific modifications, from the very first versions. In this article, we'll walk you through the progression of NEWSMATCH from an empty application to (almost) the finished product, highlighting how OpenLaszlo facilitates rapid development of iPhone apps.

OpenLaszlo is an XML and JavaScript platform for creating runtime-independent rich internet applications. An OpenLaszlo application can be compiled to run in Flash Player, or in browser-native DHTML, in most modern browsers. Yes, even IE6. More importantly for this article, OpenLaszlo applications run just fine in Safari. Which means that, in theory, they should more-or-less work on the iPhone.

When Apple announced that the SDK for iPhone application development was going to be (just) Ajax, we were very excited to build an OpenLaszlo application for it. We went to the boot camp with every hope that building an iPhone application would be as easy as building any other OpenLaszlo application, but we were aprehensive too--what if it didn't work? It did work, as you'll see-- proving that our strategy of producing high-performance standards-compliant DHTML was succeeding.

(From this point on, if you want to follow along, get a recent nightly build of OpenLaszlo 4, after revision 6568. (As of this writing, the nightly build isn't available yet.) Nightly builds are available here. The NEWSMATCH code is available in our source code repository.

For the purposes of this article, we've rewritten the progression to be cleaner and easier to follow, but kept the same development approach and patterns. If you would like to see the actual hour-by-hour development as it happened, check the svn log from Ben's sandbox.)

Hello, iPhone (source). (live)

To get started, we wrote a simple, four-line application to prove to ourselves that we could get something running on the iPhone very quickly. To run it on the iPhone, we invoke it:

http://localhost:8080/trunk/demos/newsmatch-labs/hello/hello_iphone.lzx?lzr=dhtml&lzt=html

Notice the query arguments:lzr=dhtml means that the Laszlo Runtime is to be dhtml -- this tells the compiler which output format to translate the source to. lzt=html means that the "wapper" for the application should be simple HTML.

When you're developing, you generally want the wrapper to include the Developer's Console. The Developer's Console is a bar at the bottom of an OpenLaszlo application page with checkboxes for runtime, debug, and other options. It's the default wrapper, so if you don't include an lzt query argument, that's what you get.

"Tap to begin" (source). (live)

The next thing we did was to create a startup experience. We knew the iPhone had pretty slow data transfer when running over EDGE, so we wanted to give the user something to look at right away. So, we started NEWSMATCH with a kind of cover (the view named "cover") in front of where the actual content would go. When the program loads it shows the OpenLaszlo spinner splash.

When the app is fully instantiated, it executes the script at the bottom of the main lzx file:

     canvas.cover.tapToBegin.setVisible(true);

...which shows the text object that says "tap to begin." This little dance means that the user doesn't see "tap to begin" until the application is actually ready to interact.

When the user eventually does hit "tap to begin", the cover view catches the click, and starts the coverslide_anm. This is what we call an animator, a nifty sort of controller for visual transitions. The animator slides the two pieces of the cover offscreen, revealing the application content underneath. That animator, in turn, hides the "tap to begin" text when it starts animating, and hides the entire cover when it's done animating.

That's a bit of a complex process just to get started, but it is really just a few lines of code that creates a "lively" feeling.

Feed me! Bind me! (source). (live)

One dataset to rule them all, one datapath to find them, one constraint to select it all and using XPath, bind them.

The next thing we did was to find some data, and show it. OpenLaszlo makes this sooo easy and tasty; it's one of our favorite things to do. We grabbed an RSS feed from Yahoo! News; this one has photos for every item. (For development, we grabbed this feed once, saved it locally as an xml file, then served it up statically; there's no reason to abuse Yahoo's servers.) Then we created a tiny class to visually represent an item in the rss feed:

    <class name="rssitem" bgcolor="0x2d4263" width="70" height="30"> <!-- height, width, color so we can see it -->
        <view name="thumbnail" bgcolor="0x888888" stretches="both" width="${parent.width}" height="${parent.height}" />
        <text fgcolor="white" datapath="title[1]/text()" x="2" y="2" />
    </class>

The best part of that little class is datapath="title[1]/text()". That is an XPath query that says, "Give me the text from the first "title" node in the data associated with this rssitem." Which rss item? Well, we create an instance of one of them by binding it to another XPath query. The simplest way to do this in lzx is by binding a datapath to an object

        <rssitem  datapath="rss:/rss/channel/item[1]" />

which means "Create an instance of the rssitem class, and associate it with the first item in the rss dataset."

Now here, a subtle problem arises. Since we have not seen the data, we don't know how many records will match our xpath query, which means that we don't know how many rssitems we have. It could be zero, one, or several. Not knowing what will come, how do we plan for it?

OpenLaszlo's implicit replication shines here. We want to make an instance of rssitem for each of the first 12 items in the rss feed, so, we just make my XPath query ask for the first 12 items:

        <rssitem  datapath="rss:/rss/channel/item[1-12]" />

We throw in a layout to arrange these twelve items in a column:

        <simplelayout spacing="3" axis="y" />

and we've got the second stage in our application development.

Pretty pictures, please? (source). (live)

Next, we wanted to display the nice images that Yahoo provided, associated with each item. We've already seen how data-binding can do XPath queries for us; we now apply the same pattern to create a thumbnail and bind it to the url specified in the feed:

        <view name="thumbnail" bgcolor="0x888888" source="$path{'content/@url'}"/>

Tell me more... (source). (live)

We wanted to show some more information about these items, more than just a few words from their title, so we create a view to display details about one item at a time. We gave it an id, gDetails, so that we could address it from any scope. We used datapaths to bind it to a particular item in the rss feed. For now, we just choose the third item.

    <!-- view to hold details about the item of interest -->
    <view id="gDetails" y="150" bgcolor="0xFFFFFF" width="$once{parent.width}" height="120"
        datapath="rss:/rss/channel/item[3]" >
        <!-- the image associated with this news item -->
        <view name="img" x="10" y="10" width="${parent.width-20}" height="100" clip="true"
            source="$path{'content/@url'}" />
        <!-- the title of this news item -->
        <text fgcolor="0x19195B" x="100" y="10" width="$once{parent.width-102}" multiline="true" fontstyle="bold"
            text="$path{'title[1]/text()'}" />
        <!-- the description of this news item -->
        <text fgcolor="0x19195B" text="$path{'description/text()'}"
            x="100" y="32"  multiline="true" width="$once{parent.width-102}"/>
    </view>

Less ugly, please. (source). (live)

Around this point in iPhoneDevCamp, we took a break and went off in search of coffee. (This was when we discovered Philz Coffee.) We started to make things look nicer, by writing a custom layout, picking colors, sizing fonts, that sort of thing. (The code you're seeing here is somewhat simplified, to be easier to follow. At the iPhone camp we wrote a new layout with animation, and arranged everything pixel-perfect. That's another cool thing about LZX; you can create your own layouts of arbitrary complexity. For this article, we've approximated our process.) To make arrangement easier, we broke up the rssitem class into a rssimage class and a rsslabel class.

Also at this stage, we added a bit of interaction: if you click on a thumbnail image, the gDetails view shows the title and description and a larger image from that item. That was another cool trick here; we added an onclick handler to the rssimage:

        <class name="rssimage" >
            ...
            <method event="onclick">
                gDetails.show(this.datapath.p);
            </method>
        </class>                

        <view id="gDetails" ... >
            <method name="show" args="v">
                // Make the details node point to a different element in the dataset
                this.datapath.setPointer(v);
            </method>
        </view>                  

When the user clicks on an rssimage, it sets the datapath of the gDetails view to the datapath of the rssimage clicked on. This automagically makes the gDetails img and title and description views update their datamapped attributes. Wild, huh? This dynamic do-what-I-mean interaction between datapaths and user events and view attributes is a big part of making OpenLaszlo such a fun platform for developing mashups.

Notice, also, how we have been writing methods and creating attributes on objects or classes, or even in the global scope. In a strict class-based language, you'd have to create a new class in order to have a singleton gDetails view, and you'd have to do some tricks to make sure that you only ever had one instance of it. With the lzx class model, you can just reach in and add methods whereever you want. You can also turn an instance into a class very easily, (most of the time, just by wrapping the instance in a class tag) if you decide you want more than one details view.

We have also been very loose about typing. We actually have no idea what the type of this.datapath.p, which we pass as an argument to gDetails.show(), but we don't have to know; it just has to do the right thing when we pass it as a parameter to this.datapath.setPointer(). The ruby folks call this "duck typing." We just call it convenient.

Swoosh, swoop (source). (live)

It's time for some swooshing. The seventh iteration (We're skipping the sixth) adds a floating image that "slides" the selected thumbnail down to the gDetails view. When it arrives, the description in the details view is updated. We manage this with an animator (note how two animators are run simultaneously for an interesting visual effect), a partially-transparent view (gFloatImage), and some mapping from the canvas coordinate space to the view's coordinate space:

       <view id="gFloatImage" opacity="0.5"
      y="200" x="20" width="100" height="100" clickable="false">
        <method name="setTarget" args="v" >
            this.movingView = v;
            this.setX( v.getAttributeRelative('x',canvas) );
            this.setY( v.getAttributeRelative('y',canvas) );
            this.setWidth(v.width);
            this.setHeight(v.height);
            this.setSource(v.getAttribute("sourceUrl"));
            this.setVisible( true );
            animSlide.doStart();
        </method>
        <animatorgroup name="animSlide" start="false" duration="1500" process="simultaneous" >
            <animator attribute="y" to="$once{gDetails.y + gDetails.img.y}" motion="easein" />
            <animator attribute="x" to="$once{gDetails.x + gDetails.img.x}" motion="easeout" />
            <method event="onstop">
                gDetails.show(gFloatImage.movingView.datapath.p);
                gFloatImage.setVisible(false);
            </method>
        </animatorgroup>
    </view>

Also in this iteration, the loading of the thumbnails is defered until after the opening animation has finished. The rssimage class has an empty view until loadThumbnail is called on it; only then does the app start loading the image resource:

    <class name="rssimage" >
        <attribute name="sourceUrl" value="$path{'content/@url'}" type="string" />
        <view name="thumbnail">
            ...
        </view>
        ...
        <method name="loadThumbnail">
            this.thumbnail.setSource(this.sourceUrl);
        </method>
    </class>        

This way, the application starts to appear while the images are still loading. This is another tweak to improve the user experience. On a slow connection, you can see the images pop in one by one.

Application Logic

Didn't we say something about a matching game? Yep. The final code shows how we did this, by taking advantage of the guid (global unique identifier) that the rss feed provides for each item. We bind the guid to both the rssimage and the rsslabel. To see if a title matches an image, we just compare their guid's. (This code snippet includes gResult, which we haven't talked about yet. It's another singleton object which shows the result of the match.)

    <view id="gDetails" ... >
       <method name="checkForMatch"><![CDATA[
        // only check for a match if both a headline and an image have been selected
        if ((("currentTitleSelection" in this) && ("currentImageSelection" in this))
            && (this.currentTitleSelection != null) && (this.currentImageSelection != null)) {
            var titleGuid = this.currentTitleSelection.guid;
            var imageGuid = this.currentImageSelection.guid;
            if (titleGuid == imageGuid) {
              gResult.showResult(true);
            } else {
              gResult.showResult(false);
            }
        }
        ]]></method>
    </view>

Birds in Flight (xslt source)

EDGE data transfer can be slower than a laden African swallow, so you have to be very careful with your bloated feeds. The rss feed served up by yahoo is much bigger than we needed for this application, and it has lots of duplicate information; it started out at around 150k. So, we made a little xslt filter that pulls out just the information needed for NEWSMATCH. This got the feed info down to around 11k. If we were deploying NEWSMATCH live, we would insert this filter into the data flow from the rss provider to newsmatch; as is, we just run it by hand offline when we want some new data.

Custom Wrappers

If you run the final version of newsmatch you'll notice that the words NEWS/MATCH appear on the screen pretty fast, even on the iPhone over EDGE, and that the "tap to begin" instruction doesn't appear for a while. This is more sleight-of-hand. An OpenLaszlo application, even a DHTML application, runs inside a standard html page generated by the OpenLaszlo server. This "embed" page does some browser-sniffing and loads in a "loading" splash, which is replaced by the actual OL application when it is ready. (See the OpenLaszlo Developer's Guide on Browser Integration.)

To do this trick, you have to do a SOLO deployment. (Please see the OpenLaszlo Developer's Guide for a description of SOLO deployment.) For this application, we edited the html wrapper so that the wrapper's splash exactly matches the lzx startup screen. This was just plain old HTML work; we found the div "lzsplash" in the wrapper page, and replaced the entire lzsplash div with this:

    <div id="lzsplash" style="z-index: 10000000; top: 0; left: 0; width: 320px; height: 356px; position: fixed; display: table"><p style="display: table-cell; vertical-align: middle;">

                <div style="width:100%; height:50%; position:absolute; top:0px; left:0px; background-color:#000000;"><img src="resources/cover-news.png" style="display: block; position:absolute; bottom:0%; left:55px"></div>
                <div style="width:100%; height:50%; position:absolute; top:50%; left:0px; background-color:#FFFFFF;"><img src="resources/cover-match.png" style="display: block; position:relative; top:0px; left:55px"></div>
                <div style="width:100%; height:50%; position:absolute; top:277px; left:64px; background-color:#FFFFFF;"><img src="lps/includes/spinner.gif" style="display: block; position:relative; top:0px; left:0"></div>
    </p></div>

Go code!

Now go write some iPhone apps in OpenLaszlo! Let us know what you come up with. We're sure you'll impress us; the OpenLaszlo developer community always does . --Ben Shine and Bret Simister, Laszlo Systems.

3 Responses to “iPhone application development, step by step”

  1. Hardik Says:

    Awesome !!! Ready to go for iPhone app. Great stuff

    Thanks a lot,

    Regards,
    Hardik

  2. napyfab:blog» Blog Archive » links for 2007-10-01 Says:

    [...] OpenLaszlo Project Blog » iPhone application development, step by step (tags: iphone flash laszlo openlaszlo mobile phone web2.0 development webdev web) [...]

  3. The Healthcare IT Guy » Guest Article: Rich Internet Applications for Improved Healthcare App User Experience Says:

    [...] Different frameworks are available to develop these applications; currently at my company we are exploring OpenLaszlo. We’ve use it to create Flash applications but it can also be used to create “lighter? applications (DHTML) for less powerful devices. OpenLaszlo can seamlessly generate applications supporting a wide array of browsers (including the safari browser on the iPhone!). [...]


Copyright © 2005-2010 Laszlo Systems, Inc.