By Zane Claes
Earlier this year we embarked on a six month project to refresh the mobile app, to coincide with the launch of our updated brand. The Airbnb product teams continued to ship new features for our mobile apps even during the months it took to implement the new brand. Logistically, this is the equivalent of repainting a plane while it is in flight — without any of the passengers knowing it. When it landed, we also wanted it to be a surprise.
When we embarked upon the brand evolution we chose to use small and nimble engineering teams to prevent blocking each other. When the spec for a project is changing it is easy to have too many cooks in the kitchen as engineers begin to move faster than the shifting designs. Until the last month the team consisted of one engineer on each mobile application and three on the website. Part of the logic behind this decision was also an acknowledgement that design is an iterative process: no PSD file survives first contact with the user. A small engineering team is better able to adapt, with fewer places for communication to breakdown.
The design team had already been hard at work for months creating the new brand, but they brought the engineers into the room early to start helping to scope the project. At this point, we were considering:
- Design language: what would our “toolkit” look like (buttons, switches, etc.?)
- How could we improve the usability of the application without moving beyond the scope of a reskin?
- How could we do all this without disrupting other teams?
Even thinking within the confines of reskinning the application using a small team, it became apparent that there were some quick usability wins to be had. For example, we changed the background color of our side-drawer (Airnav) to be dark, which greatly improved readability. For contrast, here’s the before and after shots of our navigation system:
We made the tabs on the Reservation screen more obviously buttons, which improved the ability of users to discover functionality. It was tempting to say “yes” to every great idea dreamed up in these design/engineering meetings, but the constraint of small engineering teams meant that we were hyper aware of our limited resources.
We had two options for how to structure our workflow. We could have constrained the branding code to a long-lived Git branch, or we could have kept merging to master and split the XCode targets. We decided to pursue the latter approach for a number of reasons. Most notably, we decided that even though there was only one engineer responsible for the brand evolution itself, team members would be responsible for ensuring that new features were compatible with the brand evolution.
After duplicating our target and swapping out the app icon, we now were faced with a new set of challenges. First, we needed to make sure no assets were leaked by accidental inclusion in the distribution target. We meticulously examined every .ipa which was built for leaks, and even went so far to use code words as symbol names in the code to prevent leakage through reverse engineering the binary.
Next, we needed to decide how to branch the code. On the surface this was simple: we just added a preprocessor macro to the new target called STYLE_PRETZEL. Then, we could use #if defined(STYLE_PRETZEL) in our code and rest assured that anything in the block would not be compiled into the existing deployment target. There were definitely drawbacks to this method, such as:
- Code complexity: the codebase was quickly riddled with branching logic
- No header changes: due to the multi-project nature of our XCode workspace, we could not be guaranteed that the macro would be available in a header file, so we could not use the #if defined in .h files
- (Almost) no new classes: for both of the above reasons, it was generally best to not add any new classes, but rather to work with existing classes
In some ways, these restrictions actually helped us: it forced us to think about the minimum viable code change necessary to accomplish the task. In other words: if you’re writing lots of code, you’ve probably moved beyond reskinning.
Our internal styling library, Nitrogen, allowed us to swap out colors, fonts, and basic component (“toolkit”) styles. This wholesale replacement was then followed by multiple progressive refinement passes across all the screens. We liked to think of it like the progressive rendering of images downloaded over a slow internet connection:
After the toolkit came the “first-cut”, at which point the app definitely looked new, but was still nowhere near Airbnb’s standard of design perfection; we essentially eyeballed the changes:
Next was “redlining.” The production team took the PSDs provided and used software to call out the exact fonts, colors, margins, etc.:
With nested views, subclassed controllers, and other pitfalls it can sometimes be hard to know that the design is actually implemented to-spec. To check our work, we used Reveal to compare the correct values to the actual implementation:
All Hands on Deck
With about a month left until the big unveiling, we deleted the old XCode target and removed the preprocessor macro and #if defined statements, locking us into deploying the next version of the app with the brand evolution. Not only were all mobile team members now working with the reskinned app, but it was also distributed to employees of the company to start testing.
One of our mantras at Airbnb is that “every frame matters,” and now was the time to prove it. Designers began going through the app with a fine-tooth comb, and engineers’ queues filled with bug reports. It cannot be stressed enough how important these last few weeks were. This is the point at which we were moving text by one pixel here and one pixel there, yet all these pixels added up to the difference between a good app and a clean app.
The biggest oversight we made was to not think about localization earlier. Airbnb is an exceptionally global brand, and it is paramount that the app look stunning no matter what language it is shown in. This speaks to a greater need to “design for the dynamic.” We as engineers need to do a better job communicating with the design team about how they imagine the implementation changing. Not just for different sizes of text, but for animations, screen sizes, etc.
The early decision we made to split the XCode targets rather than creating a Git branch proved to be a good one. The result was that the rest of the team was at least superficially familiar with the new code, and when we called for All Hands On Deck there was minimum friction.
Overall, the process went exceptionally smoothly. Teams across the company continued to work on their own projects until just a few weeks before launch. On the big day, all of the teams pressed the “launch” button at the same time, and the world saw the new brand across all channels.