I’ve been always curious to know what other Rhodes developers background and their interest on Rhodes and Mobile App development in general. Since Rhomobile google group reached its 100 members, now is the good time to ask questions. Let me know if you have other questions, so that I can add more polls.


On April Fool day, Stanford university announced something too good to be true, they are going to take video of their iPhone developer lecture and open it up on iTune for free.

The course schedule is here, and they are going to put the video a few days after each lecture.

I looked at the iTune store and the first lecture is already up.

I just bought “iPhone in Action” book and started reading it.

The book covers both iPhone Web app development and iPhone SDK development. It’s actually great book to learn when you have to understand core concepts, but it is a bit difficult when it comes to using IDE when it’s explaining like “click 3rd left button from bottom”.

One interesting thing is their sample application. They are going to create an app called “Presense” in first 4 weeks, which is basically Twitter client application.

If you  followed my tutorial of creating Twitter client app in Rhodes, it would be interesting to actually try doing the same thing using iPhone SDK & Objective C and see the difference.

I will definitely follow this lecture and may write an article about comparing development in Rhodes vs it’s native development toolkit.


Previously

At the previous post, we created MyTimeline to display your friends tweet as well as posting your own tweet from Rhosync server.

Today’s tutorial to show and post tweet from iPhone simulator through Rhodes. This is similar to Part2, but I will also cover how to change and debug views.

rhogen model

We already created an app called Twitter, so we can go straight into making model. If you haven’t done so, go back to Part2

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

As you did at Part2, let’s insert link to the “MyTimeLine/index” and “MyTimeLine/new” pages at at “Twitter/index.erb” like below.

  <ul id="home" selected="true" title="Twitter">
    <li>Something interesting here...</li>
    <li><a  href="MyTimeLine"> My Tweet</a></li>
    <li><a  href="MyTimeLine/new"> New Tweet</a></li>

View

Next part is a view. This is index page. Rhodes uses iui javascript/css library which shows UI just like iPhone. This is great to get instant iPhone like look & feel. As I look further into the css of iUi, I learnt that iUi offers very basic layout and does not have anything extra to adjust font size and so on. As you saw at Part2, all tweet messages are in big bold font. I added extra css to make date, user name and source field smaller.

/apps/Twitter/MyTimeline/index.erb

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

<li class ="row">
  <div class ="photo">
    <img src=<%= x.user_profile_image_url %> alt=<%= x.user_screen_name %> />
  </div>
  <div class ="status">
    <div class ="text">
     <%= x.text %>
    </div>
    <div class ="user">
     <%= x.user_name %> <%= x.created_at %> via <%= x.source %>
    </div>
  </div>
</li>
<%end%>
</ul>

/apps/Twitter/shared/css/common.css

  .photo{
    float: left;
  }
  .status{
    margin-top: 5px;
    margin-left: 10px;
    width: 290px;

  /*  float : left; */
  }
  .text{}
  .user {
  /*  float: left;*/
    color:#999999;
    font-size:13px;
    font-weight:normal;
  }

You can put the above css under /apps/shared/css or combine with /apps/shared/js/iui/iui.css. I personally prefer to put css into my directory (Twitter), so that I can put only Twitter directory into versioning system, but it’s up to you. If you follow the same path as mine, don’t forget to import the file you just created at /apps/Twitter/layout.erb

  <style type="text/css" media="screen">
    @import "/shared/js/iui/iui.css"; 
    @import "/shared/css/rho.css";
    @import "/Twitter/shared/css/common.css"; 
  </style>

/apps/Twitter/MyTimeline/new.erb

At new page, I got rid of all input besides “text”, as all other attributes are going to be created by Twitter when the post is created.

  <form title="New MyTimeLine" 
      class="panel"
      id="MyTimeLine_new_form" 
      method="POST" 
      action="<%=url_for(:action => :create)%>">
    <fieldset>
        <input type="hidden" name="id" value="<%=@MyTimeLine.object%>"/>

          <div class="row">
              <label>Text: </label>
              <input type="text" name="MyTimeLine[text]"/>
          </div>

    </fieldset>
    <input type="submit" value="Create"/>
  </form>

Displaying on Emulator

Now it’s time to open the project at Xcode and do the build. As we did at Part2, make sure you reset iPhone simulator and Xcode, so that you can do clean build.

Top page

Show page

Create page

New Tweet on Twitter

Debugging view.

When I first added some styling, I kept doing “reset iPhone, clean Xcode target, Build & Go”, just to see minor view change. This drove me crazy!!

Then I copied view logic from erb to normal html and started debugging using Firefox & Firebug. After I completed my view change, I discovered that I can see the exactly same view from my browser just hitting http://localhost:8080

Next

Next, I will try utilizaing mobile system’s GPS capability. I will fetch geo location and list tweets around your area. A few function to achieve this is not implemented at 0.3, so I will use 1.0 which is still at unstable release.


Just after the news about Jruby running on Android, here comes news about Ruby & RubyCocoa running on iPhone.

f:id:takuma104:20090226022028p:image:w200

According to the blog of the author who created this project (Takuma Mori ), he started this experiment after inspired by the fact that several iPhone applications are now written in C#.

The current work is not based on the latest RubyCocoa, but 0.4.2. This is because one of the library(libffi) does not work on iPhone, and he had to use the version which does not rely on libffi. However, JSCocoa also has same problem and they are trying to make it work, so it may be supported on more recent RubyCocoa once the problem is solved.

Mori san translated his initial Japanese blog post into English at his Wiki page. However, his Japanese slide seems not translated, so I added a few extra info here.

  • Dynamic linking does not work on iPhone, so it’s all statically linked
  • Sumbmited IRB app to appstore(there is a screen capture at p49 of his slide), but got rejected. Mori san thinks it’s probably get accepted if he submits another Ruby app(not like iRB) which code code is pre-compiled prior to the submission as C# got away.

When I first looked at Ruby mobile environment, Rhodes was pretty much the only choice. Even though Jruby on Android and RubyCocoa on iPhone are far from production use at this moment, this will bring lots of Rubyiest’s attention to mobile app development, encourage competition among different implementations, and I hope it accelerates lots of innovation in this field.


I quickly tried instruction on Charles Nutter’s blog and managed to do “Hello World”, though I got error when I tried to use IRB.

I will post more detail and gotchas once I fully managed to run it on my environment.


Previously

At the previous 2 posts, we went through the very basic of how you write code in Rhosync and Rhodes.

This time, we go through the similar steps,but additional functionalities to read and create your own tweet.

Creating New source at Rhosync

Let’s first create new source, just in the same way as you created the public timeline at Part 1, but new name as “MyTimeline”

  $ rhogen source MyTimeline
  Generating with source generator:
       [ADDED]  lib/my_timeline.rb

Authentication and My Tweet.

I initially thought I have to implement some authentication logic at “login” method, but realized that none of REST based examples (Basecamp and Lighthouse) have logic at login method.

Instead, it uses Net::HTTP’s authentication mechanism within query method.
Here is an example of Basecamp adapter.

  def login
    # intentionally left blank
  end

  def query
    uri = URI.parse(@source.url+"/projects.xml")
    req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password
    response = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end
    xml_data = XmlSimple.xml_in(response.body); 
    @result = xml_data["project"]
  end

According to Net::HTTP rdoc, you can call “basic_auth” instance method with login and password parameter.

The below is query method for MyTimeline. It’s pretty much identical. Only the differences are url and response parameter.

Do you remember that you can access url info you added at Rhosync Web interface through @source? Both login and password are also available through it, so you don’t need to hardcode at the source code.

The rest (sync and other private methods) are exactly same as PublicTimeline

  def query
    url = "/statuses/friends_timeline.xml"
    uri = URI.parse(@source.url+url)

    req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password

    res = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end

    xml_data = XmlSimple.xml_in(res.body); 
    @result = xml_data["status"]
  end

  def sync
    log "#{self.class} sync, with #{@result.length} results"
    @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

Let’s start up the server and add the source as you did for PublicTimeline, plus actual login and password of your twitter account. If you click “Show Records”, you should be able to see your friend’s tweets.

Creating Tweet

Create method does not have a lot of examples at sample apps. Most of REST adapters (BaseCamp and Lighthouse) do not have create method for some reason.

I finally found that “lighthouse_tickets.rb” has some examples.

  def create(name_value_list)
    log "LighthouseTickets create"

    get_params(name_value_list)
    xml_str = xml_template(params)

    uri = URI.parse(base_url)
    Net::HTTP.start(uri.host) do |http|
      http.set_debug_output $stderr
      request = Net::HTTP::Post.new(uri.path + "/projects/#{params['project_id']}/tickets.xml", {'Content-type' => 'application/xml'})
      request.body = xml_str
      request.basic_auth @source.credential.token, "x"
      response = http.request(request)
      # log response.body

      # case response
      # when Net::HTTPSuccess, Net::HTTPRedirection
      #   # OK
      # else
      #   raise "Failed to create  ticket"
      # end
    end
  end

What they are doing here is passing name_value_list into requrest.body in xml format. Looks get_params method does all conversion work. Let’s look at the actual method within RestAPIHelpers

  module RestAPIHelpers
    # convert name_value_list to a params hash
    # name_value_list example, "[{'name' => 'title', 'value' => 'testing'},{'name' => 'state', 'value' => 'new'}]"
    # => params['title'] = 'testing', etc.
    def get_params(name_value_list)
      @params = {}
      name_value_list.each do |pair| 
        @params.merge!(Hash[pair['name'], pair['value']])
      end
      log @params.inspect
    end

Ah, here is a useful comment.
The comment says that get_params iterates through each array and converts {‘name’ => ‘title’, ‘value’ => ‘testing’} hash into {‘title’ => ‘testing’} hash and set the result into @params instance variable. If you see further bottom of the RestAPIHelpers, you can see “params” method which simply returns @params

  def params
    @params
  end

The below is how I implemented the create method for MyTimeline.

  def create(name_value_list)
    url = "/statuses/update.xml"
    uri = URI.parse(@source.url+url)

    get_params(name_value_list)

    req = Net::HTTP::Post.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password

    req.set_form_data('status'=> params["text"])

    res = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end
  end

This is very similar to “query” method, but here are the few differences.

  • Specified url as “update.xml”
  • Net::HTTP::Post.new. Note this is Post class, not Get class.
  • Passed text value using set_form_data method in Hash format. This way, you don’t have to convert your data into xml format and you can post to either “update.xml” or “update.json” url

Final Ryoshnc code

  class MyTimeline < SourceAdapter

    include RestAPIHelpers

    def initialize(source, credential = nil)
      super
    end

    # Looks not in use
    # def login
    #   
    # end

    def query
      url = "/statuses/friends_timeline.xml"
      uri = URI.parse(@source.url+url)

      # Login authentication. The logic is taken from basecamp_projects.rb
      req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
      req.basic_auth @source.login, @source.password

      res = Net::HTTP.start(uri.host,uri.port) do |http|
        http.request(req)
      end

      xml_data = XmlSimple.xml_in(res.body); 
      @result = xml_data["status"]
    end

    def sync
      log "#{self.class} sync, with #{@result.length} results"
      @result.each do |item|
        item_id = item["id"].first.to_i
        iterate_keys(:item => item, :item_id => item_id)
      end
    end

    def create(name_value_list)
      url = "/statuses/update.xml"
      uri = URI.parse(@source.url+url)

      get_params(name_value_list)

      req = Net::HTTP::Post.new(uri.path, 'Accept' => 'application/xml')
      req.basic_auth @source.login, @source.password

      req.set_form_data('status'=> params["text"])

      res = Net::HTTP.start(uri.host,uri.port) do |http|
        http.request(req)
      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, @source.current_user.id)
        end
      end
    end

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

Demo

Let’s go back to Rhosync server. You should have left it when you displayed your friends tweet. Click “Create object-value record”, and add “text” attribute and your own tweet message like below.

Once you click “Create Object Value”, then you should be redirected to show page with “Created Object” flash message.

Now you go to your Twitter website. Congratulations!! Your message from Rhosync is successfully updated into Twitter itself!!

Next

You can finally post your own post from iPhone simulator. I will also go through some technics to tidy up your view pages.


It’s not ruby related, but I thought this is interesting news. The google translation of this Japanese article is actually not bad, but I will summarize key points.

  • Aplix is a technology solution company to provide Java development platform to Japanese mobilie network providers, such as NTT Docomo, KDDI and Softbank mobile (Used to be J -phone and vodafone).
  • i-appli is Java development platform for NTT Docomo. More detail is here 
  • Aplix developed a tool to convert binary files of i-appli to native application of target platforms.
  • It automatically support touch panel, view rotation. Also supports flick operation for iPhone only.
  • Aplix is showing the demo at WMC right now.

After a bit more research on this topic, I found interesting note from “ITPro“(Japanese)

  • Aplix business model is commission based to software vendor per sales basis(up to 5~6 %)

Looks their business model is similar to Rhodes, though they do not seem open source.

When I checked Aplix’s website, there was press release only in Japanese

There are not much technical detail on the press release, but it’s worth noting that there are lots of comment from Japanese Game development company such as Capcon, Sega, Bandai and so on.

Looks their main target is Japanese game development companies. Since iPhone game applications are getting so popular, there could be huge business opportunities for Japanese game companies to get into all smartphone market.

But what will be the benefit for NTT docomo which offered technical advice to Aplix?

At a first glance, letting all game companies to provide a way to develop iphone/android apps rather than iAppli sounds silly move , as they can no longer “lock in” all vendors. However, I saw one Japanese blog speculating that that’s the strategy of NTT docomo to take the standard of mobile application development using their Java platform, so that they can protect game vendors to switch their platform out of iAppli.

If you are attending WMC in Barcelona, please visit their booth and let me know how smooth their conversion program is.

It seems Adobe is also making progress for their Flash support to smartphone (iPhone is not to be supported though).

It is interesting that many companies are now trying to support cross mobile development platform. 

What would you choose?

 




Follow

Get every new post delivered to your Inbox.