Today JotBot was released. This is, to my knowledge, the first commercial desktop Ruby application. The web world has been invaded by Ruby, Rails and the crop of post-Rails web frameworks (Merb, Ramaze, Sinatra, etc.), but the desktop world remains largely untouched by Ruby’s rising star. This post mortem explores how Ruby fares for creating desktop applications and our experiences at Happy Camper Studios in developing our first commercial product, JotBot.
Project rational and tool selection
Purpose: JotBot is a desktop-based time tracking app that uses a “pull” model. That means you don’t remember to go log your time, instead JotBot comes to you at a configurable interval and asks you for a time log. (If you want to get a feel for the app, there’s a quick tour video on the JotBot home page that shows off most of the features.)
This project was started by me, as a side project because I couldn’t find anything that did quite what I wanted. The “pull” nature of JotBot was something quite important to me as I have a bad habit of getting distracted. I was doing contract work and needed to have accurate time logs for billing. At one point I used a timer that auto-reset and was on a 15 minute interval. When the timer went off I’d go log what I had been doing for those 15 minutes. This is basically what the first version of JotBot replaced. It worked well enough that we decided to pursue a commercial version as a company.
Platform and tool selection: At the time I was doing Rails development so of course I considered doing a web app initially. What I quickly found was that I didn’t want to always have my web browser open to a certain web site. Even when I did have my browser on a given site, that web app getting my attention was severely limited by the browser. If I was on another screen (at the time I was a Windows user but I still heavily used multiple desktops), you could just forget getting anything that was going to grab my attention via the browser. So I decided it was probably best to go the desktop application route.
At the time JRuby was reaching a fairly mature and stable point. I was doing a bit of Swing work for a client and was able to leverage JRuby where possible to make the job easier. This work was proving quite fruitful so I decided to go with Swing + JRuby. This eventually lead to the Monkeybars project which grew up alongside my client work and JotBot’s development. Before I get flooded with people saying I should have gone with framework X, let me state that yes, I am familiar with Tk, GTK, wxWidgets and Fox having evaluated them all at one point and none of them solved my problems as easily as JRuby + Swing promised to. So, with those tools selected my development stack became: JRuby driving the app logic calling into Swing and other Java libraries utilizing the Netbeans GUI editor for my form layout and the Monkeybars and Rawr libraries for the primary infrastructure of the application.
What went right
Over the course of the project several aspects of our chosen development setup stood out as very positive contributors to the success of the project. In no particular order
- Running on the JVM via JRuby meant that our code “just worked” on OSX, Windows and Linux. At various times we had people working on each of these platforms contributing to the code base as well as just running the app on a daily basis. Not having to ever build a platform specific dll or so was very nice and knowing that our interpreter was very stable from platform to platform also meant that time spent chasing down platform specific bugs was very minimal.
- Easy access to very cool app bundling tools. This gave rise to the Rawr project which can produce .exe or .app files for native looking distribution. More importantly you can generate all of these from any platform (Linux, Windows, OSX). Once we had our rake tasks in place, it was a simple matter of “rake installer:win” and bam, I have a versioned installer bundle I can upload to the website. The .dmg bundler was the one exception, that uses Apple provided tools and can only be run on a Mac.
- The GUI editor that comes with Netbeans make layout very easy. Being able to see our forms and tweak them out in a rapid prototyping fashion was extremely handy. Lines of Java code we wrote or maintain by hand in JotBot: zero.
- Ability to integrate with the desktop via JDIC. We decided early on that JotBot really should be an app that lives in the “system tray”. This presented some problems for Swing as it doesn’t have this ability natively (at least pre Java 6 it didn’t). Thanks to an active Swing community this problem had been solved for us via JDIC, the Java Desktop Integration Components. Their system tray support allowed us to look native on OSX, Windows, Gnome and KDE with almost zero extra work from us.
- Monkeybars matured and made Swing fade into the background. Creating a new form and adding some event handlers became so ridiculously easy I have to think hard how I even did it before. There are still rough points here and there but for the most part, Swing is tamed and the common stuff is wickedly fast.
What went wrong
Of course the project wasn’t all sunshine and roses. We ran into several late term problems that caused not insignificant delays.
- Active Record, our original ORM choice, did not play well when the Ruby files were compiled in a jar. I plummed the depths of AR for days but was unable to even find some of the meta-added methods that were failing. I’m not an AR expert and I didn’t feel like becoming one, so we decided to switch to a less “magical” ORM system. We couldn’t use DataMapper because their core Data Objects library is written in C and I was cautioned against using the existing Java conversion. So we settled on Sequel (possibly the worst named library EVAR!). We had been using Derby with Active Record which had worked flawlessly, unfortunately Sequel doesn’t support Derby. I looked into adding support but when I found out Derby doesn’t support the limit key word, the implementation of .first became significantly more complex. Faced with the choice to do non-trivial work on Sequel or pick something that could be supported, I opted for the latter. We settled on H2, a pure-Java SQLite like solution. We did have to create an H2 adapter but it was a matter of overriding 2 or so methods and was done in an afternoon (code posted here). So we went from Active Record using Derby to Sequel using H2. Very few bugs were introduced by this changeover which was incredibly encouraging.
- The Group Layout is not the best at making your forms look native with regards to spacing. Group Layout is what the Netbeans GUI designer uses, for those not intimately familiar with this stuff. We love the GUI designer, it is incredibly valuable, but the layout system leaves a bit to be desired. We designed primarily on Macs and there most things looked fantastic, but on Windows there was often a bit too much extra space. We took pain to make sure almost everything was dynamically sized, so that there was never accidental “left over” space from a component we sized on the Mac. In the end I’m reasonably happy but I often wonder how something like MiGLayout would have fared.
- Packaging up a Ruby application in a jar file is not something most Ruby libs are prepared for. We had some path issues that took time to resolve. Things like file:// getting prepended to the path to a file in a jar file when referencing __FILE__ with or without various expand_path shennanigans. We have bits of code that correct for this, but overall it’s probably one of the most “raw” parts of JRuby in terms of cleanliness. The rest is so fantastically good, paths to files in jars and such jump out all the more. Like with the compilation of Active Record classes in a jar file, these issues came up late in the dev cycle when we were preparing our installer and bundler tasks. Some of these issues have been resovled now so I would expect the next time around for this to be less of a pain point.
So would I do it differently if I was starting again? I firmly believe that JRuby is the way to go for ease of packaging and disribution, not to mention for access to libraries. Swing vs SWT is sort of a contentious topic although I think in the end Swing is available in more places and has more general support and a greater variety of 3rd party components available (we used several SwingX components such as JXLayer and JXTreeTable). Monkeybars was built on Swing but could just as easily support SWT if the demand was there. Speaking of Monkeybars, the choice to build a Ruby helper layer was definately the right choice. The amount of work needed and maintainability of our code before we switched over from the initial raw Swing calls to Monkeybars compared to afterwards was striking. Getting even just a few key pieces of Monkeybars in place dramatically reduced the code we needed to write and its complexity. I highly encourage anyone doing Java integration work to take your top 2 or 3 pain points and spend a bit of time to make them just go away. I did the initial event handling registration and routing code in Monkeybars in an evening. Those 20 lines saved us hours upon hours of work and reduced our code’s complexity significantly. Small things like that can have huge impacts on your project so get them in from the beginning.
I think in the end JotBot demonstrates that doing high quality desktop applications in Ruby is viable today. There’s still lots of apps that can’t / won’t be good web apps, why shouldn’t they be written in Ruby?
EDIT (19 Jan 09) – Added link to H2 adapter code on Rawr mailing list.