What we have done at previous tutorial.

At the previous post, we created an Rhosync adapter to fetch public timeline of Twitter. Take a note of the url where you displayed the record, because you will need it when you build rhodes app.

In my case, the url is like this.

http://localhost:3000/apps/6/sources/11

“6” is id for the application(Tweeter), and 11 is id of the source(Public Timeline). The numbers very in your environment.

Setup

As specified at Rhomobile tutorial, let’s download the latest Rhodes from github.

  git clone git://github.com/rhomobile/rhodes.git rhodes

Before building new app

When I first played with SugarCRM sample which rhomobile provided, I really struggled running it on my iPhone emulator. You can see the full detail at this thread, but I will also summarize it again with some visual aid.

Unify source url into one.

You will notice that there are lots of sample application directories (Lighthouse, SugarCRM, etc) under “apps” directory. I recommend that you either remove them, move to different directory, or edit “config.rb” files of every single models under every single probjects pointing source url from “http://rhosyncdev.rhohub.com” to “http://localhost:3000” . This is because Rhodes try to connect to each url at login and fails if it fails to connect to any url specified at the config file.

  $cd rhodes
  $cd apps
  $grep source */*/config.rb
  Basecamp/People/config.rb:#Rho::RhoConfig::add_source("People", {"url"=>"http://rhosyncdev.rhohub.com/apps/1/sources/6", "source_id"=>6})
  Lighthouse/LighthouseSettings/config.rb:Rho::RhoConfig::add_source("LighthouseSettings", {"url"=>"http://rhosyncdev.rhohub.com/apps/4/sources/10", "source_id"=>10})
  Lighthouse/Milestones/config.rb:Rho::RhoConfig::add_source("Milestone", {"url"=>"http://rhosyncdev.rhohub.com/apps/4/sources/6", "source_id"=>6})
  Lighthouse/Project/config.rb:Rho::RhoConfig::add_source("Project", {"url"=>"http://rhosyncdev.rhohub.com/apps/4/sources/5", "source_id"=>5})
  Lighthouse/Ticket/config.rb:Rho::RhoConfig::add_source("Ticket", {"url"=>"http://rhosyncdev.rhohub.com/apps/4/sources/7", "source_id"=>7})

  $mv  Basecamp/ Lighthouse/ RhoSiebel/ RhoSugarCRM/ Phonebook/ ..

Do a clean build in xcode.

sqlite local db holds session information unless you completely wipe
out app from the simulator. Here are the steps to wipe out completely.
Make sure you do this every time you modified your code under rhodes directory
(not just when you are tweaking rhodes framework itself, but also when you write your code under “app” directory)

  1. Do “reset content and settings” then from the iphone simulator
    menu

  2. Remove iphone/build/Debug-iphonesimulator director from Rhodes
    project directory.

  3. “Build” => “Clear all targets” on Xcode.

  4. “Build” => “Build and Go” from xcode.

The steps are quite tedious. I’d love to know if there are any other ways to simplify the steps. This is when I miss the simplicity of scripting environment…

Log out /login from simulator.

For some reason, rhodes thinks I was logged in at iphone simulator. If
first login did not work, cancel the login page, go back to main page,
click “Logout”, then login again (This used to happen at 0.2.0, but seems fixed at 0.3.0).

Make sure port 8080 is not in use.

The iphone simulator uses http://localhost:8080 for it’s internal web server to render user interface. I was once running Tomcat at port 8080 for my work related product, and my mobile app window at the simulator was showing this message. Took a few minutes figuring out why it was showing such a message.

rhogen app

You must remember that you used “rhogen” to create an adapter at Rhosync.
You will use the same command, but now with different option.

$rhogen app Twitter
Generating with app generator:
     [ADDED]  Twitter/application.rb
     [ADDED]  Twitter/index.html

Before moving to the directory, please insert a link to the app page at “apps/index.erb” like below.

<h4>Sample Apps</h4>
    <a href="Twitter">Twitter</a></br>

rhogen model

Now is the time to create a controller which sync with the resource model you created at Rhosync.

  $cd Twitter 
  $rhogen model PublicTimeLine "http://localhost:3000/apps/6/sources/11" 11 text,user_screen_name,user_name,user_profile_image_url,source
  Generating with model generator:
  [ADDED] PublicTimeLine/config.rb
  [ADDED] PublicTimeLine/index.erb
  [ADDED] PublicTimeLine/edit.erb
  [ADDED] PublicTimeLine/new.erb
  [ADDED] PublicTimeLine/controller.rb

The syntax should be straightforward. it specify controller name (PublicTimeLine), resource url(http://localhost:3000/apps/6/sources/11), resource id(11, actually I feel this is duplicated info as resource url contains the resource id itself), and all the attributes(text,userscreenname,username,userprofileimageurl,source).
Make sure you put no space between commas.

You don’t really need to touch the controller you just created for now, as rhogen made controller with basic CRUD. Just insert link to the “PublicTimeLine/index” page at at “Twitter/index.html” like below.

  <ul id="home" selected="true" title="Twitter">
    <li>Something interesting here...</li>
    <li><a  href="PublicTimeLine"> Public Tweet</a></li>

View

Last part is a view. The below is PublicTimeLine/index.erb created by rhogen.

  <ul id="PublicTimeLines" title="PublicTimeLines">
  <%@PublicTimeLines.each do |x|%>

  <li><%=link_to "#{x.text}", "edit", x.object%></li>

  <%end%>
  <li><font color="blue"><%=link_to "New PublicTimeLine", "new"%></font></li>
  </ul>

Let’s change it to show all the info we fetched from rhosync.

  <ul id="PublicTimeLines" title="PublicTimeLines">
  <%@PublicTimeLines.each do |x|%>
    <li class ="row">
      <img src=<%= x.user_profile_image_url %> alt=<%= x.user_screen_name %> />
       <%= x.text %>
       <%= x.user_name %> <%= x.created_at %> via <%= x.source %>
    </li>
  <%end%>
  </ul>

Displaying on Emulator

Now it’s time to open the project at Xcode.

Before running “Build & Go”, I recommend that you go through the clean up process I explained earlier.

Once build is complete, iPhone simulator will popup, and Rhodes application will launch itself. Also, make sure you Rhosync Rails server is up and running.

It’s good time to open up console, as well as displaying script/server log, so you can see what’s going on at the backend.

Rhodes on iPhone Simulator

Finally we managed to display public tweet on our iphone simulator!!

The view is rendered by iui css and javascript. It will show like normal iPhone UI, as long as you specify each tweet within <li class="row">. I also made other view changes such as displaying date in time distance (eg: 3 hours ago), but I will cover that at later time.

Next

At next post, I will go through similar step we did at Part 1 and 2, but we will display your own friend feed, as well as being able to post tweet from iphone simulator.


Once you play with sample applications provided from rhomobile, now you might want to make something on your own.

I’ve seen lots of desktop client app tutorial with “Creating Sample Twitter App” examples, so I also tried something similar.

I will try to explain my code examples step by step including error messages and mistakes I made, so that you can not only experience what it is like to develop mobile app using Rhodes/Rhosync, but also debug similar problems when you make your own application.

In this tutorial, I will start from minimum functionality to display Twitter’s public timeline on iPhone simulator. I will then implement authentication and basic Read/Create actions , so that you can show your friends’s tweet, as well as post your own tweet from the iPhone simulator.

All the codes will be tested against 0.3.1 tags for both rhosync and rhodes.

Before I start, special thanks to Rhomobile support team for answering so many questions at google group.

What is Twitter API?

Twitter is one of the most post popular Ruby On Rails based web app. It is a micro blogging service where you can post your status in small text(140 chars).

Following Ruby On Rails convention, they have very clean (
REST based APIs). If you have never tried their API, click http://twitter.com/statuses/public_timeline.xml. You will see list of 20 public tweets in xml format like below.

<status>
  <created_at>Sat Feb 07 23:49:04 +0000 2009</created_at>
  <id>1187577382</id>
  <text>
    @ceetee dude! How's it like out there! Been there before?
  </text>
  <source>
    <a href="http://www.atebits.com/software/tweetie/">Tweetie</a>
  </source>
  <truncated>false</truncated>
  <in_reply_to_status_id>1187570116</in_reply_to_status_id>
  <in_reply_to_user_id>8620122</in_reply_to_user_id>
  <favorited>false</favorited>
  <user>
    <id>11686132</id>
    <name>bombayaddict</name>
    <screen_name>b50</screen_name>
    <description>Bombay's Addict</description>
    <location>Bombay. Always. </location>
    <profile_image_url>
    
    </profile_image_url>
    <url>http://www.bombayaddict.com</url>
    <protected>false</protected>
    <followers_count>462</followers_count>
  </user>
</status>

If you change file extension from .xml to .json or .rss, they will show in each format. Unlike your private tweets which I cover later, accessing public tweets does not require authentication.

There are a few Ruby Libraries to handle their API, but the APIs are so easy that you can do most of what you want to do by just using basic Ruby network library, such as net/http.

Using Rhosync

Rhosync will let you define common CRUD(Create, Read, Update, Delete) interfaces for mobile device via Rhodes.

Rhosync is built as a pure Ruby On Rails based application, so you should not have any problems if you know how to run and develop using Ruby On Rails.

Registering new source at Rhosync web form.

After you install Rhosync following the instruction, let’s startup the server, register your account, login to the server, and create an application called “Twitter”. Once application is created, click “Add Source” and create an source called “Twitter Public Timeline”

Add “http://twitter.com&#8221; as url, but you can leave login and password for now, as Public Timeline does not require authentication.

To setup adapter, you can either generate the adapter using “rhogen” command, or type source code directly from the web form. I choose generating adapter myself for better debugging purpose. If your adapter code has some bugs, any exception will point the line number which contains the bug. If you type code in the form, your code is kept at database and get evaluated dynamically when required, but exception only says your eval failed, which is harder to debug.

If you go for generating adapter, just type “PublicTimeline” at “Source Adapter Class”

rhogen

you need to create an adapter to talk to Twitter. Rhosync has some built in code generator, so creating the skeleton is very easy.

$ rhogen source PublicTimeline
Generating with source generator:
     [ADDED]  lib/public_timeline.rb

This creates long list of code defining login, query, sync, create, update, delete, and logoff.

For this very first application, all you need to worry about are “query” and “sync” methods.

query method

Here is my mimimalist code to fetch query from twitter.

def query
  uri = URI.parse(@source.url)
  res = Net::HTTP.start(uri.host, uri.port) {|http|
    http.get("/statuses/public_timeline.xml")
  }
  xml_data = XmlSimple.xml_in(res.body);
  @result = xml_data["status"]
end

I think it can’t be as simple as this. It take url from @source instance variable. @source.url is the url you added when you first created the source from web page. Then it fetches tweets and put the body of the response into XMLSimple library. Lastly I put each entry under into @result instance variable as Array.

sync method

Next is “sync” method. The code repopulated by rhogen command iterates through each entry of @result and put value into ObjectValue object. ObjectValue is a table where you can store the result of API response as key => value pair. This way, you can

The below are the difference between normal ORM(Object Relational Mapping) object table and ObjectValue table.

Normal table

ID                                    |  Industry  | Name
44e804f2-4933-4e20-271c-48fcecd9450d  | Technology | Mobio India
69ae8551-c0bd-0cb3-5c25-48ff9bae965a  | Finance    | vSpring

ObjectValue table

Object                                | Attribute | Value
44e804f2-4933-4e20-271c-48fcecd9450d    | industry  | Technology
44e804f2-4933-4e20-271c-48fcecd9450d    | Name      | Mobio India
69ae8551-c0bd-0cb3-5c25-48ff9bae965a  | industry  | Finance
69ae8551-c0bd-0cb3-5c25-48ff9bae965a  | Name      | vSpring

What I did not understand much was where “entrylist” and “namevalue_list” attributes are coming.

At the rhomobile tutorial
, @result is set like below at query method.

 result = client.get_entry_list(session_id,'Account','','',0,['name','industry'],10000,0)

And it also explains that “client” variable is available and not for REST API.

The Rhomobile tutorial page shows example using SugarCRM which uses SOAP API. Since Twitter uses REST API, I referred to Lighthouse and BaseCamp examples which are also Rails based app and uses REST API.

By examining these codes, you notice that there is a helper called RestAPIHelpers . Let’s use that. I included the helper at the top of the class like this

class PublicTimeline < SourceAdapter

  include RestAPIHelpers

They use “add_triple” method which also adds your API result into ObjectValue, so I decided to use the method to inject my Tweets.

def sync
  @result.each do |item|
    item_id = item["id"].first.to_i
    item.keys.each do |key|
      value = item[key] ? item[key][0] : ""
      add_triple(@source.id, id, key.gsub('-','_'), value)
      # convert "-" to "_" because "-" is not valid in ruby variable names
    end
  end
end

This is also simple one. @result contains array of Tweet where each tweet contains data in hash format like below.

  {
    "text"=>
    ["@ceetee dude! How's it like out there! Been there before?"],
    "id"=>["1187577382"],
    "created_at"=>["Sat Feb 07 23:49:04 +0000 2009"]
  }

I iterated through each hash key and send its key and value into “add_triple” methods

  @result.first.keys
  => ["text", "id", "created_at"]

Now it’s done. Go back to “Editing Source Adapter” page, click “Show records”, then “Refresh object-value records”. This shouldwill show all recent tweets.

Ummm, certain values are missing, such as “user”. By looking at original xml, you should see that certain attributes are nested, so just going through each key of hash won’t work. You need to iterate recursively every time value contains hash.

Final code

Here is the complete code including the change to recursively iterate through each tweets. I refactored the code by separating iteration part into “iteratekeys” and calls the method recursively if the hash value contains another hash. When you call iteratekeys, it also adds prefix, so “user” => {“screenname”} will be stored into “userscreen_name” key name at ObjectValue

  class PublicTimeline < SourceAdapter

    include RestAPIHelpers

    def initialize(source)
      super
    end

    def query
      uri = URI.parse(@source.url)
      res = Net::HTTP.start(uri.host, uri.port) {|http|
        http.get("/statuses/public_timeline.xml")
      }
      xml_data = XmlSimple.xml_in(res.body);
      @result = xml_data["status"]
    end

    def sync
      @result.each do |item|
        item_id = item["id"].first.to_i
        iterate_keys(:item => item, :item_id => item_id)
      end
    end

  private
    def iterate_keys(option)
      item = option[:item]
      item_id = option[:item_id]
      prefix = option[:prefix] || ""

      # item.keys => ["user", "favorited", "truncated"...]
      item.keys.each do |key|
        value = item[key] ? item[key][0] : ""
        if value.kind_of?(Hash) && value != {}
          # eg. :user => {:url => 'foo} becomes user_url
          iterate_keys(:prefix => (prefix + underline(key) + "_"), :item => value, :item_id => item_id)
        else
          # This method is from rest_api_helper
          add_triple(@source.id, item_id, prefix + underline(key), value)
        end
      end
    end

    def underline(key)
      key.gsub('-','_')
    end
  end

This is the complete “Show Record”, including nested attributes, such as user_id/name/url

Next

Since we now have a source adapter which loads public tweet, I will explain the code I wrote to display these tweets on iPhone simulator using Rhodes.


Hello world!

07Feb09

This is my first post at Ruby On Mobile.

I do currently work as Ruby On Rails Web developer, but got interested in mobile app development, even though I don’t own any smartphones, such as iPhone, T-mobile G1 nor Blackberry.

Since I don’t know anything about mobile development nor any of mobile app development languages such as ObjectiveC(iPhone), Java(Android, Blackberry), nor C++ (Symbian), first thing I did was to search if there were any way I could write mobile app using web technologies, like AdobeAir does for desktop app.

Embedding WebKit web browser into your mobile app locally seems interesting way to achieve mobile app development, but it seems not matured yet.

Then, I found this article.

Not only being able to use HTML/CSS/Javascript, you can use Ruby to write mobile app. That sounds great!! The Framework (Rhodes and Rhosync) are inspired by Ruby On Rails, so they use same MVC , ORM, and so on.

As soon as I read the article, I started following tutorial provided from Rhomobile, the company behind Rhodes and Rhomobile.

At this blog, I will write about my experience playing with Rhodes/Rhosync.

Since I named this blog as “Ruby On Mobile”, I will write about other Ruby related mobile projects and libraries as I find. I am also thinking about learning basic of Android, iPhone and Mobile Website development, so that I can compare which approach suits developing what type of mobile services.

Makoto