Porting AnyList from iOS to the Web

AnyList for the Web screenshot

We recently released AnyList for the Web, which allows AnyList Complete subscribers to view and edit their lists in a web browser on their Mac or PC.

Creating a web version of AnyList is something we’ve wanted to do for a while, as it has been one of our top feature requests. However, we were concerned about the amount of time it would take to rewrite AnyList for a new platform as well as the ongoing effort required to maintain it. As a team of just two people, we must be vigilant about taking on too much at once and spreading ourselves too thin.

Luckily, we found some relatively new technologies that let us get the initial version of AnyList for the Web up and running quickly and that should also minimize the amount of time we need to spend keeping it up to date with the iOS app.

oj

We implemented the web app’s model and sync code using oj, an Objective-C inspired superset of JavaScript that was created by our friend Ricci Adams for his musictheory.net website.

Using oj allowed us to write object-oriented JavaScript with Objective-C syntax, including the use of some modern Objective-C features such as @property and @synthesize. The end result is that many of our oj classes are line-for-line equivalent to the corresponding files in our iOS app.

To get an idea of how similar our oj code looks to Objective-C, take a look at this sample from our NavigationController class written in oj. (Note: the @synthesize is only necessary because we wanted to prefix our instance variables with ‘m’ to match the AnyList iOS app.)

@implementation NavigationController : ViewController {
    id mToolbar;
}

@property Array viewControllers;
@synthesize viewControllers = mViewControllers;

@property id delegate;
@synthesize delegate = mDelegate;

- (id) initWithRootViewController:(ViewController)rootViewController {
    self = [super init];
    if (self) {
        mViewControllers = [rootViewController];
        [self addChildViewController:rootViewController];
        [rootViewController didMoveToParentViewController:self];
    }
    return self;
}

- (void) pushViewController:(ViewController)viewController animated:(Boolean)animated {
    var topViewController = [self topViewController];
    var delegate = [self delegate];
    if ([delegate respondsToSelector:@selector(navigationController:willShowViewController:animated:)]) {
        [delegate navigationController:self willShowViewController:viewController animated:animated];
    }

    [self addChildViewController:viewController];
    [viewController didMoveToParentViewController:self];

    mViewControllers.push(viewController);

    [self _didShowViewController:viewController animated:animated wasPushed:YES];
}

@end

When Ricci first told us about oj, we were admittedly a bit hesitant about using something that needed to be compiled to JavaScript (at the time, we had been looking into either writing everything ourself or using an MVC JavaScript framework such as AngularJS, Backbone.js, or Ember.js). However, after experimenting with oj, we quickly realized that we could have large parts of the code for the web app be nearly identical to the code in the iOS app, and the implications of that were hard to ignore.

First, translating our Objective-C code to oj was very straightforward, often involving straight copy/paste and making a few minor tweaks such as replacing NSArrays with native JavaScript arrays. Having such similar code between the two platforms will also be a big win as we continue to maintain the web app. When we make a change in the iOS app, we know that we need to make the same change to the web app and vice versa. In fact, just through the process of porting the iOS code over to the web app, I uncovered a few bugs and ported fixes back to the iOS app.

The biggest benefit of using oj and having such similar code on both platforms is that the web app is now built on code that is already well tested and that we are intimately familiar with. As anyone who has been following Brent Simmons and his series of Vesper Sync Diary posts knows, designing a sync system is an extremely challenging problem. With oj, instead of re-writing our sync code to work within a new framework and having potentially subtle implementation differences between the web app and iOS app, we’re essentially running the same syncing code that has been powering the AnyList iOS app for the last two years.

React

AnyList’s iOS interface is largely composed of UITableViewController subclasses and custom UITableViewCells. For the web, we followed this same basic structure and implemented our own ViewController, TableViewController, and NavigationController classes in oj.

To implement our view objects (e.g. versions of UITableView and UITableViewCell), we used React, a JavaScript library from Facebook/Instagram for creating reusable UI components. When writing our React components, we used JSX, a JavaScript XML syntax transform. This allowed us to use HTML-like snippets inside our React component render methods, which makes for very clean and concise code. This is what the render method for our split view looks like using JSX:

var ALSplitView = React.createClass({
  render: function() {
      return (
        <div className="ALSplitView">
          <div className="ALSplitView-MasterContainer">
            {this.props.masterViewBuilder.build()}
          </div>
          <div className="ALSplitView-Separator" />
          <div className="ALSplitView-DetailContainer">
            {this.props.detailViewBuilder.build()}
          </div>
        </div>
      );
  }
});

Each oj ViewController has a React component associated with it (just like a UIViewController has a UIView associated with it). When a ViewController receives a notification from the model that something has changed, the ViewController simply tells its React component to re-render. Internally, React uses a virtual DOM and with each rendering pass determines what changes need to be made to the real DOM (it does this really, really quickly).

Once we had our base ViewController classes in oj and our UI components in React, we were able to pretty quickly port all of our UITableViewController subclasses over to oj. Often, the oj class needed even less code than its iOS counterpart. For example, the oj TableViewController subclasses did not need a heightForRowAtIndexPath: method.

After the core interface was implemented, we were also able to add some basic animations to the app by taking advantage of React’s CSS animation hooks. For example, we added a sliding animation when pushing/popping views in our navigation controller classes as well as when presenting a modal view controller.

Conclusion

The combination of oj and React has worked out really well for us. We were able to make the initial version of AnyList for the Web available to our users with just a few months of work and since the code structure is so similar to the iOS app, we expect maintaining it and keeping it up to date with the iOS app to be a relatively easy and straightforward process.


Rampant Abuse of Push Notifications Is Ruining Them For All Developers

The abuse of push notifications is spreading across the App Store. As a result, users are starting to reflexively reject app requests to send push notifications.

I always allow apps to send me push notifications, just so I can see what other app developers are doing. Here is a collection of valueless, invasive, and annoying push notifications that I’ve received recently:

  • It's my lucky day!

  • I have never watched Lost, but this message definitely needs to appear on my lock screen.

  • They must be very proud.

  • No!

  • Alright, it's a Zynga app, so it's hard to act like I didn't know this kind of junk was coming.

  • ZipList sends these every. single. day.

As far as I can tell, many of these notifications are in violation of section 5.6 of the App Store Review Guidelines, although its exact meaning is up for debate:

“5.6  Apps cannot use Push Notifications to send advertising, promotions, or direct marketing of any kind”

Compounding the problem is that although there are options to control push notifications on an app by app basis in the iOS Settings app, most non-technical users do not know that these settings exist, or don’t understand the settings if they do happen to stumble across them. Furthermore, there is no way to report spammy notifications or turn notifications off when a notification is received, so for many users, the initial prompt asking for permission to send push notifications is effectively the only shot they’ve got at controlling push notifications. Once they’ve been burned by an abusive app, from there on out, they reject push notifications from all apps.

With AnyList, we strive to use push notifications usefully, so it’s not uncommon for us to receive support requests from people who wonder why the people they are sharing a list with are receiving notifications when a list is modified, but they aren’t. Then we explain to them that they must’ve rejected push notifications when they first used the app, but it’s possible to turn them back on in the Settings app.

I’d like to see Apple make the following changes to push notifications:

  1. Provide a feedback mechanism that allows users to report spammy notifications, and crack down on abusive apps.

  2. Provide a quick way to disable push notifications from an app when a push notification from that app is received.

  3. Let developers provide a usage description string for the push permission dialog, so they can let the user know exactly why they want to send push notifications. This sort of functionality is already available for the location, calendars, and contacts permission dialogs. (See the documentation for NSLocationUsageDescription, NSCalendarsUsageDescription, and NSContactsUsageDescription.)

  4. Allow developers to show the notification settings for an app within the app. I realize that Apple can’t give programatic access to the notification settings, because developers would abuse that, but a way to show a system-provided view controller with the notification settings for my app would be nice. Sticking the notification settings for all apps into a giant list in the iOS Settings app isn’t very discoverable, nor is it very useable when you have many apps installed. When users want to change the behavior of an app, they expect to do it in that app, not in iOS Settings. (This is why the noble experiment that was iOS Settings Bundles has been a failure, and why very few developers use them anymore.)


Being Featured on the App Store

On Thursday May 17th, v1.0 of our new app, AnyList, had been available in the App Store for one week. Our plan with v1.0 was to make sure we’d get through the app review process and get our friends and family to start using the app. We had 215 registered users.

Before telling the press and the world at large about the app, we wanted to wait for v1.0.1 of the app to be approved, since it greatly improved the user experience on first launch. We also felt it was important to fix some architectural performance problems in our server code.

On the afternoon of the 17th, my co-founder Jason and I were sitting around and discussing how to improve our server performance when I suddenly noticed our server logs flying by much faster than usual in an open terminal window. Jason launched iTunes and discovered that AnyList v1.0 was featured on the front page of the App Store as a New and Noteworthy app.

AnyList Featured on the App Store

No one from Apple contacted us before, during, or after we were featured, so this was a total surprise. After spending about 15 minutes convincing ourselves that what we were seeing was real and celebrating our good fortune, we realized that we were completely unprepared and we’d better come up with some sort of plan.

The first thing we did was request an expedited review of AnyList v1.0.1. It had already been submitted to the App Store, and since it improved the first launch experience so much, we really wanted all of the new users we’d be getting to have it. For those that haven’t requested an expedited review, when you do it, Apple sends the following auto-reply:

“We are working hard to process submissions as quickly as we can and expect to complete the evaluation of your request within 1 to 2 business days. If your request is granted, you will be notified and your app will be submitted for an expedited review.”

Taking up to 2 business days just to evaluate the request didn’t sound particularly expedited to me, but fortunately the request was granted the following day and v1.0.1 was approved in time for us to release on Friday, the 18th.

Meanwhile, our server was getting hammered, with multiple new users signing up every minute. Although the server seemed to be handling the load alright, we weren’t sure when we’d reach the breaking point. If we reached that point and hadn’t fixed our architectural issues, there would be no way to scale the service and our sudden success would likely turn to defeat, with lots of angry users and 1 star reviews in the App Store. So we started working round-the-clock on the architectural issues, since we knew they’d take at least a couple of days to implement and we weren’t sure how much time we had.

I put out a call to the founders from our Y Combinator batch (Summer 2010), letting them know what was happening, and asking if any server experts would be able to help us in an emergency. Almost immediately I got several replies and offers to help. The benefits of YC extend way beyond the weekly dinners and Demo Day.

Interleaved with all of this were periodic checks on our App Store rankings, latest reviews, and responding to a constant stream of incoming user feedback and support requests via email (we make it really easy for our users to contact us within the app). By Sunday the 20th, we were the #1 top free productivity app in the U.S. store (higher than Gmail and Dropbox) and had cracked the top 100 overall free apps.

AnyList Top Productivity App

Being featured in New and Noteworthy lasts one week, with new selections revealed each Thursday. So we figured that on Thursday the 24th we’d no longer be featured. Fortunately, that wasn’t the case. It turns out that there is another weekly featured section, slightly less prominent, called What’s Hot. I don’t know how the What’s Hot selections are chosen, but it seems like most of the time about 90% of the What’s Hot section consists of apps that were New and Noteworthy the week before. So on the 24th, we moved to What’s Hot and continued to be featured.

Around this time, we completed the architectural changes to our server code, so we could finally sleep a little better, knowing that if we reached a breaking point, we’d be able to scale up.

During our week in New and Noteworthy, we were getting 5,000 - 8,000 new users per day. Moving to What’s Hot cut that approximately in half, as you can see in this graph of our user count during the two weeks we were featured:

AnyList Top Productivity App

By the time we were removed from What’s Hot and no longer prominently featured on the App Store, we had gone from 215 users to over 60,000 users.

This was obviously a dream scenario for us. So, how do you get your app featured in the App Store? The answer is that we honestly don’t know. We’ve had no communication from Apple since we released our app. We have no idea if we’ll ever be featured again. I’m sure luck is a large factor.

That said, I was surprised by how many emails we got from users who told us they had just purchased a new iPhone and AnyList was the first app they had ever downloaded. Given that Apple is selling tens of millions of iOS devices each quarter, and New and Noteworthy apps are what the user sees when launching the App Store for the first time, maybe I shouldn’t have been so surprised. So my best guess at what to focus on if you want to get featured is to build an app that Apple would be happy to have as the first app a new customer ever downloads.

Of course, if anyone out there knows more about how to get featured and wants to provide us with the special direct line to the App Store editorial team, please let us know, privately. :-)