#199 Mobile Devices
Feb 01, 2010 | 13 minutes | Views, Controllers, Plugins
Change the look and behavior of a Rails app on mobile devices. Also use jQTouch to build a native-looking interface.
- source codeProject Files in Zip (198 KB)
- mp4Full Size H.264 Video (22.4 MB)
- m4vSmaller H.264 Video (14.4 MB)
- webmFull Size VP8 Video (38.2 MB)
- ogvFull Size Theora Video (28.5 MB)
Thanks Ryan! I am always a fan of your succinct presentation of useful information. On a side note, I have been playing around with http://www.appcelerator.com/ for making iPhone apps quickly and (dare I say it) easily. Let me know if you've used it. I'd love to hear your thoughts!
Ryan, you timing could not have been better. Customizing one my apps for mobile devices is my project of the week and I did not not about JQTouch so that's great.
The rest of the screencast is very helpful too, I will use your mobile_device? method instead of messing with the cumbersome WURFL file and its conversion to MySQL.
Thanks again and have a great week.
That screencast was totally awesome. When I saw the title I thought that I would knew everything that you could possibly show in this screencast... but apparently I was wrong, I learned many things. Thank you very much again! :)
Risking repeating what others have said, this is exactly what I was looking for. Can't believe the good timing.
For the shake of completeness: I think today's post is missing the contents of the mobile.js file (even if it's just one line, for initializing jqtouch)
Thanks for the great cast! as usual.
I'd like to introduce the ActiveDevice (http://github.com/shenoudab/active_device), Our Rails Gem and Plug-in for Device Detection (Mobile, Desktop Browser, Bot). with many helper methods for checking and detect the Mobile Brand, Model, Engine and others ..
Very cool, good work as always - Brendan Lim's mobile fu is a good solution here as well:
@Adam King - agreed, Appcelerator Titanium rocks! If you're interested in doing a native app with web tech, check it out.
Can you add url to list
Automatically detect mobile requests from mobile devices in your Rails application
Great cast Ryan. I noticed that you created a mobile-format version of every view file... is there an easy way to tell Rails to automatically default to the HTML version if a mobile version of the view doesn't exist? I have tons of views in my app, and the truth is that only some sections of the app really need special mobile treatment and I don't have the time to make ".mobile.erb" files for every view. If I don't create a mobile version of a specific view, then I get a "Template is missing" error that says that "index.erb" does not exist (for example).
Great cast, note that you could also use the Android simulator to check the looks, especially since Android is growing rapidly on the web!
Emulator is available through Google: http://developer.android.com/guide/developing/tools/emulator.html
@egarcia, thanks! Added.
@Matt, good question. I looked into this briefly but did not have enough time to fully investigate it. The register_alias method has a 3rd argument called extension_synonyms which I was hoping would do this but I could not get it to work. If you do find a solution please let me know!
Wow great stuff Ryan!
The web app (instead of native iphone app) looks like a good options for a few reasons:
1. you can keep all your code under one project (easier to manage over time as you add/remove features)
2. Don't have to go through Apple's review process
Does that seem accurate in your experience? Thanks for your great work!
you can change your useragent in firefox too, using this plugin: https://addons.mozilla.org/en-US/firefox/addon/967
although this is not as convenient as the drop-down list in safari, it still works :)
As a mobile device and desktop user, I'm a bit concerned about your approach - you're basically returning different content for the same URL, depending on whether I come from a mobile phone or a desktop, aren't you?
Wouldn't it be better to redirect mobile users to a different URL, and serve consistently cachable content from the same URL?
Mapping URL:content on a 1:1 basis is the RESTful way of doing things. That way I could access the "desktop" version of the site from a if I so wished, or the mobile version from the desktop - all without changing the way the site behaves for me normally.
@Brian, yeah, those are two great benefits to building a web app over a native app. And it is fairly easy to port across mobile platforms.
Just be aware of the negatives too. User must be online and app has limited access to hardware (camera, accelerometer, multi-touch, etc.). I have seen ways to do offline caching with jQTouch but haven't tested it.
@Alex, that's a great point. I was considering doing this with a subdomain but it was too much to cover in this episode. I do recommend using a subdomain though. The nice thing is, if you build your site around the "mobile_devices?" method, it is very easy to swap out the behavior depending on subdomain, user settings, etc.
Ryan, how do you manage page caching with this technique?
Wow, you never disappoint me. I watch your screen casts every monday, and I feel like each week my career is really bolstered by the knowledge I gain from them.
@Juan, for page caching I recommend using a subdomain or something in the routes to separate the mobile version from the full version. See my response to Alex earlier.
I created a blog with similar information recently:
This just uses a different application layout if the device is detected to be mobile.
Thanks for this episode (as well as all the others).
My current dev setup is:
- Mac OS X 10.6
- Rail 2.3.5
- start dev app using "script/server" which uses mongrel
This is a little modification for auto detect. If someone find useful:
MOBILE_BROWSERS = ["android", "ipod", "opera mini", "blackberry", "palm","hiptop","avantgo","plucker", "xiino","blazer","elaine", "windows ce; ppc;", "windows ce; smartphone;","windows ce; iemobile", "up.browser","up.link","mmp","symbian","smartphone", "midp","wap","vodafone","o2","pocket","kindle", "mobile","pda","psp","treo"]
agent = request.headers["HTTP_USER_AGENT"].downcase
MOBILE_BROWSERS.each do |m|
return request.format = :mobile if agent.match(m)
@Alex, @Ryan (Comments #19 & 20)
According my understanding of the RFC2616 (sections 14.14, 13.6 mostly), HTTP/1.1 allows you to serve multiple content variants of the same resource URL and have the cache layers working fine, thanks to the Content-Location and ETag headers.
Here's how I understood it works:
When serving a resource (let's say /resource/1) with a particular content variant, you specify this chosen variant's URL (/resource/1.html for "desktop" variant and /resource/1.mobile for the mobile one) with the Content-Location header and send the corresponding ETag.
When a cache layer is requested for that resource again, it will query the server with all the variants' ETag it knows (If-Match header) and, depending on whether one of the provided ETag corresponds to the actual resource variant to be served, the server sends a "304 Not Modified" answer with the corresponding Content-Location so the cache knows what content variant to send to the client.
However Rails does not seem to be currently ready for such behaviour (no signs of a Content-Location header in the sources for instance, except in the tmail library bundled with actionmailer) so yes, the best thing to do is to either use a subdomain and/or redirect the mobile and/or the desktop client to their corresponding variant (with a ".:format" extension,) thanks to the :format option of the url_for (and alike) helper methods.
@myself (Comment #33) and everyone interested about the caching subject:
There is also the Vary header that can help to provide multiple content variants of the same resource URL.
See sections #14.44 & 13.6 of RFC 2616, or for the explanations about "Caching and Alternates" and the "Vary header" pp.401 to 403 of the "HTTP: the Definitive guide" (if you don't have this book, those pages are part of the preview available online at oreilly.com: http://oreilly.com/catalog/9781565925090/preview) Figure 17-2 illustrates the "Vary: User-Agent" mechanism.
The Vary: header method is Rails compatible out of the box (simply use "headers['Vary'] = 'User-Agent'" where needed), but it is not very efficient for the shared caches that sit between the clients and the server as they will have to keep one cache entry per existing User-Agent string...
i played around with jqtouch and i was not impressed. iwebkit (http://iwebkit.net/) is much cleaner in all concerns: markup, capabilities, ui and documentation.
Great Cast! If you are interested in building an actual iPhone App, I recommend ObjectiveResource which is an Objective-C port of Ruby on Rails' ActiveResource. check it out at http://iphoneonrails.com. I'm still looking for an Android library that does the same.
Thanks Ryan, this method just solve my idea of building mobile integration onto my project...
anyway, anyone still have the src for those old episode... especially on the sub domain episode...
Thanks for the great cast. This will really help.
I was impressed by how much jQTouch did. Have you heard of any prototype based libraries that perform similar tasks?
Great as always.
I'd like to see more about jQTouch.
Hi, mobile devices often comes in different screen dimensions and capabilities, therefore you may need to render images that are optimized for a specific screen dimensions to maximize user experience. WURFL (http://wurfl.sourceforge.net/) is a powerful library that allows you to query and determined the characteristic of a mobile device from screen dimension to features and capabilities of mobile devices. The device list is update frequently from people around the world. There is a ruby version of it.
Hello - just in case anyone else working on a mobile app ran into the same problem I did where your dev env creates asset URLs with "http://localhost:3000/" (see comment #29) - here's how I got around the issue:
I added the following to my config/environments/development.rb:
ActionController::Base.asset_host = ''
Hope that helps!
@Alex, @Ryan, @Juan, @Frederic:
I have managed to separate caches for mobile and normal browsers. I do most of the work in .htaccess fils. So you should serve your application under Apache. The solution is super easy. You can review it at
Thx for this nice screencast!
I like the JQTouch Interface Plugin with Themes. Is there a similar solution for standard web apps? I mean a comparable css theme that makes developing Web-GUIs easier by suppling nice features like buttons and so on?
Is there something you can recommend?
Hmm maybe I'll just have to stick with jQueryUI.. But jQTouch seems to do more...
How do you deal with page caching when you have mobile views???
Hey Brian, are you aware that the video controls for your videos don't work in Chrome? Just fyi.
Great info here! I've been using iUI for a while, but I do like how you have this one set up.
Has anyone found any clever CSS to handle the errorExplanation div for mobile devices? It doesn't seem to stand out as much to me. I'll have to play a bit more with it. Any best practices when it comes to displaying errors in a mobile web app with rails?
if request.format == :js
request.format = :mobilejs
request.format = :mobile
Anyone had a issue using jqtouch whereby your mobile views (as created above) continuously reload in an infinite loop? I tracked it down to the setting of location.hash in jqtouch.js on line 189 (beta 2), but I'm not sure of the underlying reason.
@LW, yep, I've run into the same issue.
I also ran into an issue on Rails 3 where the AJAX requests load the full HTML page, but don't replace the whole page. So you end up with
Which is obviously wrong. So looks like this screencast might be a bit out of date or didn't take that into account :-(
You have to set the top-level DIV id attribute to "home" (just giving it class of "current" is not enough). This is valid for the Beta 2 version of jqtouch. I saw the infinite loop issue on Jaunty with FF 3.0.x... that was painful to track down given that there are very few good examples for jqtouch (even the oreilly iphone book with the jqtouch example doesn't show the id attribute).
Great introduction into developing for mobile Ryan. I had been looking for a tutorial like this one for a while! Except I am running into some issues.
I am trying to replicate your tutorial on an existing rails app. Everything looks fine until you try to load the show.mobile.erb. Instead of being loaded into the current page in the mobile layout, it just loads in as is. Any idea why it would be disregarding the layout?
Thanks in advance for any help you can offer.
how do you set the id of linked pages loaded in by jqtouch via ajax? for each transition, jqtouch loads in the page successively as:
<div class="current slide in " id="page-3">
<div class="current slide in " id="page-8">
<div class="current slide in " id="page-13">
Great screencast, but having an issue.
Using this setup, ALL major browsers work just fine, switching between mobile and full without issue, except IE8, which will shows the full site on the home page, but switches to the mobile site for all other pages, clicking the "fullsite" button does not help.
Debugging shows IE was passing the following as the request.format:
#<Mime::Type:0x103699f48 @synonyms=, @symbol=nil, @string="image/jpeg">
added this to a before filter that runs before prepare_for_mobile and all is good...but feels lame.
Anyone else have this issue with IE8 and this setup
forgot...this is the "this" in my post:
request.format = :html if request.format == "image/jpeg"
Check the Android simulator to check the looks, especially since Android is growing rapidly on the web!
This is a great post!
I tried the first part of this (excluding the JS part) and followed the instructions to the 'T'. I added the new MIME type and created a index.mobile.erb view. When I tried to load up that page on a mobile device, I got an error saying "TypeError: Can't convert symbol to a string" and it points to the use of 'format' in the helper method 'prepare_for_mobile'.
Ryan, I'm wondering if you know why this is happening?
Try to reload your server.
how to define mimetypes right in case of dynamic generating js and css (not only html )
Started GET "/" for 192.168.1.4 at 2011-02-19 13:26:13 +0300
Processing by WelcomeController#carousel as HTML
Rendered welcome/carousel.mobile.erb within layouts/application (47.3ms)
Completed 200 OK in 54ms (Views: 52.9ms | ActiveRecord: 0.0ms)
Started GET "/netzke/touch.css" for 192.168.1.4 at 2011-02-19 13:26:14 +0300
Processing by NetzkeController#touch as CSS
Completed 406 Not Acceptable in 1ms
Started GET "/netzke/touch.js" for 192.168.1.4 at 2011-02-19 13:26:14 +0300
Processing by NetzkeController#touch as JS
Completed 406 Not Acceptable in 1ms
i got 406 error on other types!
The 406 occurs when you don't set the
respond_tofor the given format on controller.
will wait for jquerymobile railscasts!
I am using JQtouch for creating my application works for mobile.
I am already having an web application,now I just want application works on mobile.
Any one having solution for this
Roy - did you restart your server after creating the new Mime type?
If I'm using Nifty Authentication to handle Logins, Registrations, Logouts (etc) does anyone know how to make this work with jqTouch?
As it seems the AJAX form submitting is breaking the application, and when I click log in, it fades to a black background?
You probably want to add something like this if you don't want to break xml or json output, (with
Hello, I'm trying to delete a record with the code
but the link does not work, should we declare it otherwise? Thank you.
This seems to work perfectly when testing through browser with mobile user-agent locally, but when I push it live, first request renders full site, and second one from there only takes me to mobile page.
This doesn't happen always, but very often. Anyone else having this problem? Any idea why?
Has anyone got jQTouch working with Rails 3.1+ and the asset pipeline?
I loved the mobile MIME type solution but was wondering why I might be getting a 406 error when I attempt to access content with UJS in a mobile device. I'm a beginner and I haven't been able to find a solution for it yet, though it seems a few others have similar problems.
Could you perhaps shed some light or point us towards a solution?
Try the @Jeremy solution:
If you want to detect if mobile or not, an easier way to do that is define this method in your controller:
Thereafter, you have to call this method with :
if request.user_agent =~ /Android/i
session[:mobile] = true
elsif request.user_agent =~ /BlackBerry/i
session[:mobile] = true
elsif request.user_agent =~ /iPhone|iPad|iPod/i
session[:mobile] = true
elsif request.user_agent =~ /IEMobile/i
session[:mobile] = true
session[:mobile] = false
thereafter your session[:mobile] will have true if you are in mobile and false if you not.
I hope its helpful for you.
You could just:
Rather than going through each individually and you can ignore case.
Really a great technique. Rather than using a querystring param, I choose to use the URL extension. It just felt better than a querystring since Rails already responds to that to determine the correct MIME format:
Anyone else have the issue where the session persists? I can load the mobile view normally with a mobile user-agent, and switch to Full Site once. After that, clicking the link or changing the param has no effect until I've cleared cache.
delicious....... yes I believe that is the correct adjetive for this episode.
for Rails 4.0, I think the turbolinks get in the way of allowing the header to refresh in some browsers. i.e. following this railscast when I clicked on the mobile/full site link, the css would not change unless I would do a page refresh in internet explorer and firefox (no problem with ipad though...). so I put in the body html tag data-no-turbolink in views/layouts/application.html.erb, and works great now. yepi.
Perhaps more experienced Rails users know this... After adding the
Mime::Type.register_alias "text/html", :mobileI got
undefined methodref' for nil:NilClass` when trying to go to the mobile site. When I restarted my local rails server, the error went away.