Almost 2 years ago I wrote about our approach to building our first in-house native application. This week, we shipped version 2.0 of Basecamp for iPhone, which now shares a codebase with Basecamp for iPad. I’m proud to say this is our best iOS release yet. Give it a shot!

What’s changed? Everything. For starters, we’ve:

  • Rewritten the app in Objective-C
  • Banked on our hybrid architecture
  • Shared a universal codebase between iPhone and iPad
  • Reinforced our testing infrastructure

I’m happy to share the details on how we built and shipped the app. Let’s dig in.

Buckling down

The new iPhone app is 100% Objective-C. Why did we move away from RubyMotion? Quite honestly: we needed to buckle down and learn the platform as a team.

Basecamp for iPhone 1.x was my first iOS application, and it showed. Updating it to work on iOS8 was painful, and there’s many completely non-standard patterns used in the app. One particularly bad example: NSNotificationCenter is used for passing data around the app almost exclusively.

None of these things are RubyMotion’s fault, but I felt that after a year of working with iOS, I didn’t know the platform. That’s not something to be proud of. Avoiding Xcode may have been a badge of honor to get started, but it’s a shame to ignore the tools and affordances it provides.

I’m still convinced I wouldn’t have gotten this far without RubyMotion, and as a learning tool its value is immeasurable. It got us started and hooked on iOS development, and has been doing that for not only us but for many others. There’s plenty of examples of the wide impact RubyMotion is having. One of my favorite games I’ve played this year, A Dark Room, was written in RubyMotion. This certainly isn’t the end of RubyMotion’s influence on the iOS or Ruby communities. Once again, huge thanks to Laurent and the entire HipByte team for making a great product.

Hybrid sweetness

Our hybrid approach to native applications with embedded web content continues with this release. We’ve taken it a step further: any interaction with todos is now native.

It’s funny to me that a good “first app” for a mobile developer is a todo app. Todos aren’t simple, especially our todos in Basecamp. There’s due dates to lock down. People to assign. Comments to complain. Dragging to prioritize. There’s a lot to worry about for our todos, and it’s important enough to do right natively.

The “sweet spot” we’ve sought out is flexible: if it makes more sense to write a feature natively, we’ll try it out. We’ve done several bake-offs at this point of native vs web features. Sometimes web wins since we might need to update content, or it’s hard to render certain types of content natively. Sometimes native wins since the feel of a native control is just so obviously better, or it’s just more natural to the platform.

If you’re still considering a hybrid approach to your app, by all means: please do! But when it makes sense, challenge your assumptions.

One codebase, two apps

After shipping Basecamp for iPad this summer, we wanted to bring the same experience to iPhone. We wrestled with the upgrade path for our users on both devices with their existing apps for months. Every time we brought it up it would kill at least an hour or two collectively of our time: what if we made one app? What if we took the existing iPhone app out of sale, and replaced it with the iPad version? I went so far as writing down 5 different plans for the upgrade and giving them codenames so we could compare them in a concrete way. Just writing them down instead of talking about the tiny details of each individual decision helped crystallize the problem.

We ended up deciding to keep the apps separate in the App Store (codenamed: Vanilla Pepsi), which allowed automatic updates to give everyone the new app without having to go download a new one. This caused more pain for us inside of Xcode, as we had to now maintain two separate targets and bundle identifiers, but it ended up being simple for our customers. The upgrade numbers prove this now: we wouldn’t have seen a 70%+ uptake rate on the app a day after releasing it if we had a separate install:

If you’re faced with a decision like this, your customers’ time is more valuable than your own. Make it easy for them.

Continuously integrating

Each member of our team (2 programmers, 1 designer, 2 QA) had Xcode running and working with the latest builds. We’d ship builds via Apple’s TestFlight fairly regularly, but it was easier to just get everyone working with the simulators and the latest codebase. Essential to this process was Xcode’s CI server. It had our backs against regressions and build failures throughout the entire shipping process. If you’re writing an iOS app, you should be running Xcode CI. Getting that feedback and seeing improvement (and fixes!) over time is not only reassuring, but inspiring.

We’re still fighting with the best way to write tests for iOS. Our approach this time was to mostly test some of the messier logic, and avoid integration testing. After trying and mostly failing to maintain a set of KIF tests for the iPad release, we gave up on having our Bots tap around the apps. Our fantastic QA team hammered away at the app and found things we’d never thought of looking for. I’m hoping to play with snapshot testing, as it seems much easier to maintain and debug than traditional integration tests.

Swiftly shipping

Our next target to learn is Swift, and we’ve already started playing with it. I just can’t get enough of optionals. Map/filter lends itself to idiomatic patterns that I wish Ruby was expressive enough to match. We’re also trying to figure out how to write things in a “Swift-like” way, and it doesn’t seem like anyone knows how yet. There’s a few styleguides out there, but so far it’s a wild west of possibilities.

Basecamp on iOS is even better now, and going forward to iOS9 (and iOS10?) we’ll be ready to adapt even faster. If you’ve used the new iPhone app or any of the great features now available on both devices, let us know how they’ve been working for you!