twocentstudios

Chris Trott and iOS, Ruby, Rails

My Review of the New Facebook Login Review Process

In somewhat under-the-radar news, Facebook announced at their F8 conference in late April 2014 that there would be several changes to the way that Facebook Login works for apps. I say under-the-radar because I saw very few reactions on the interwebs. I’ll attribute this to these changes taking effect immediately only for new apps and all existing apps having a full calendar year from the announcement in order to comply.

Facebook Login Changes Summary

A quick summary: all developer apps that use Facebook Login must go through an App Store-style approval process to gain access to the majority of a Facebook user’s data. Access to this data is provided through a granular permission system. Any app can request access to a user’s primary email address, public profile (name, age range, gender, profile picture), and friends of the user that also use your app.

Notice I said “request access” because the other side of the changes announced at F8 include the ability for a user to only provide a subset of the permissions that are requested by the app. For example, an app could ask for the ability to use your photos and see your likes, and you can decide that your photos are private and deny the app access to those while still allowing your likes to be used.

There’s actually two layers to the permissions. The first is the Facebook Review Team granting your app permission to ask users for certain permissions. The second is each user actually granting each permission you’ve requested.

I’ve spent a nice chunk of the last two months dealing with these changes and the Facebook iOS SDK in general my day job. It’s a non-trivial change to the way we use Facebook data, and it’s imperative to the service we provide to have access to a user’s data.

My Sideproject

I’m a heavy Facebook Groups user. I have a couple groups I share with subsets of my closest friends from back in Chicago. We use them as a way to passively keep in touch with one another, plan events (when I’m back in town), share links, etc. I haven’t met that many others that use Facebook Groups in this fashion, and adding Facebook Groups to the Timehop app would probably not be worth the team’s effort.

To scratch my own itch, I started a side project that displays my Facebook Group data like Timehop does: it shows all the posts from this day in history going back several years. I created the Facebook app for it a few weeks after F8, and at this point didn’t realize that the user_groups permission I needed was now on heavy lockdown. I also didn’t realize that since I was creating a new app I was immediately subject to the strict review from the Facebook Review Team.

I finished the app, jumped through all the hoops of adding short descriptions, long descriptions, explanations for permissions, contact info, support URLs, a privacy policy, uploading screen shots, and even compiling a special simulator build so that the Facebook review team could verify the permissions I was using before my app was live on the App Store.

The Review Process

Let me point out that Facebook pegs review times for apps with normal permission requests at seven business days. That means that if you’re creating a new app, you’re waiting an average of two weeks for your app to go live on the App Store. And that’s only if the review process goes smoothly for Apple and Facebook. For certain special permissions, Facebook quotes the review time for your app at 14 business days. That brand new Facebook connected app that your start up is eagerly looking to launch? Better set up those marketing materials for next month. Three weeks in the Facebook queues and another week in the Apple queues. A full month of biting your fingernails and sitting on your haunches, hoping for approval.

user_groups, the only permission I need, is one of the extra special permissions. And to my dismay, just as I was preparing my newly finished app for submission, I discovered this annotation in the new Facebook permissions docs: “This permission is reserved for apps that replicate the Facebook client on platforms that don’t have a native client.” Uh oh.

Another sidebar: I went to the Facebook Login Event in New York in June. There was an hour long presentation about all the changes and how awesome they were for users. At the conclusion, the lead product manager stressed that there would be an open dialogue about the review process. He talked about how they were excited to hear about all the ways that apps use permissions that they hadn’t even thought of yet. About how all existing apps should go through the review process as early as possible so there wasn’t a mad rush in April 2015 before the review requirement for existing apps goes into effect. About how this was going to be a positive change for both users and developers.

First Submission

I submitted my side project app, spending extra time on describing why I needed the user_groups permission and explaining in several different places that the user data would be downloaded directly to the user’s device and never leave that device. It would never be uploaded to any servers. It would never be shared. And there was a big red button in the settings menu to delete it at any time.

The review came back with two standardized message prompts: “We couldn’t open the simulator build you submitted” and “Your user_groups permission has been rejected because you aren’t building an app on an unsupported platform.” Damn. Well, maybe they rejected the permission because I followed their one-size-fits-all directions on using the xcodebuild command line tool and the arguments didn’t work for me and they couldn’t open my app.

I found their special permissions-related contact email address and submitted a plea for my app. Rephrasing a lot of what I had stated before about why my app needed those permissions and why it was safe for users. No response.

Second Submission

I recompiled my app using a different set of build flags and triple checked that it worked this time. I packaged everything up again and resubmitted, hoping my tag-along email and working build would help sway the decision this time.

Rejected again with the same message.

Going Forward

Some of this was my fault. I didn’t scour the docs after F8 to notice the change in permissions. I also didn’t finish the app and realize the precarious position I was in until after the Facebook Login Event, where I at least could have talked to a real human being about my issue.

I’m obviously a little rustled. I’m planning on open sourcing my app anyway (it uses a lot of MVVM and ReactiveCocoa goodies that should be interesting to those looking to learn more about them), and adding Tumblr support, and maybe some other services in the future.

Some Thoughts

From an outsider’s perspective, Facebook obviously knows the powerful position they’re in. They’d like to protect their data at all costs, even if they have to create an entire review team to do so.

And at the same time, they’re actively trying to draw developers to the platform with programs like FBStart. If you’re a brand new start up, are all those free services worth the time your app is going to spend in the review queue instead of in the hands of your users? Is it going to be worth it when after months of development, Facebook decides they no longer want your type of app to exist? Mac and iOS developers have complained for years now about the opaque App Store review process. Is it worth it to have another third party with any number of conflicting motivations standing in the way of your app going live?

As a developer, I wish there was a way for Facebook to accommodate good user experiences without being hostile to developers. I’m not some scumbag anonymous developer asking for every permission that exists to use for nefarious purposes. I’ve just a guy with a fun side project that I want to get in some peoples’ hands. Is there some way that I could prove that to Facebook? Should I have to prove that to Facebook?

I’m hoping for some kind of resolution to this problem. But either way, side projects are supposed to be learning experiences, and I definitely learned some good lessons with this one.

On MVVM, and Architecture Questions

This post is something like a mini-walkthrough/tutorial, but it stops about half way from being complete. The goal is to elicit some discussion about the architecture of iOS apps from those experienced with both MVVM and MVC patterns.

There have been several converging iOS topics I’ve been interested in as of late. Each of these topics has influenced my approach to what I would consider a grand refactor of the Timehop app.

A Bit of Background

Our architecture has remained more or less unchanged in the 1.5 years the app has been available on the App Store. The app is primarily backed by Core Data behind a legacy version of RestKit. We also use standard serialized files as well as NSUserDefaults in various modules of the app.

As Core Data often demands (via NSFetchedResultsController) our view controllers have classically been highly coupled with our data source layer. We often use UIImageView+Networking type categories to do fetching directly from the view layer. We use various techniques (within and without Rest Kit) to serialize, fetch, and map data. It’s a mess.

But at the end of the day, this architecture has allowed us to move fast and try any number of features and enhancements in every corner of the app, and it’s got us to the point where we are today: growing.

With millions of daily opens, our goal is an architecture that is performant, crash-free, and very light on maintenance.

New Techniques

In order to achieve our architecture goals, we’ve been evaluating new techniques outside the mainstream iOS realm. The rest of this post will detail how we’ve attempted to incorporate these techniques into the app.

ReactiveCocoa

I’ve been experimenting with ReactiveCocoa on a few past projects, and even used it to implement a recent Timehop experiment called “Throwbacks”. ReactiveCocoa is awesome. Its benefits deserve their own post, but suffice to say the team here is becoming comfortable enough with ReactiveCocoa techniques that it will play a major role in whatever the next version of Timehop becomes.

MVVM

ReactiveCocoa goes hand in hand with the MVVM architecture pattern. My only exposure to MVVM has been through the ReactiveCocoa ecosystem. MVVM is a difficult pattern without the aid of the concise binding framework like ReactiveCocoa to synchronize the view and view model layers.

Testing & Dependency Injection

And the third component I’ve been dabbling in is automated testing. We haven’t chosen a particular library yet, but most of the options are similar enough to fulfill our requirements of ensuring stability and future refactorability. Going along with testing, I’ve been reading about dependency injection as a way to ensure testability and keep components as modular as possible.

Fitting the Pieces Together

So far, my friend and co-worker Bruno and I have written a couple components of our architecture from scratch with the goal of slowly replacing the tightly coupled components of our current app. Specifically, we’ve started with the Timehop settings screen. The settings screen primarily holds the logic for connecting and disconnecting the various social services from which we import data. There are also several various preference and contact screens.

This is where I’ll start asking questions and positing solutions for how to architect an MVVM module that is testable and doesn’t trip over its own layers of indirection.

My Understanding of MVVM

MVVM is often introduced in this simple diagram:

View => View Model => Model
     <-            <-

Where => represents some combination of ownership, strong references, direct observation, and events. <- represents the flow of data (but not direct references, weak or strong).

In Cocoa-land/objc-world:

  • The view layer is comprised primarily of UIViews and UIViewControllers.
  • The view model layer is comprised of plain NSObjects.
  • The model layer is comprised of what I’ll actually call controllers, but could also be known as clients, data sources, etc. The roles are more important to keep in mind than the names. Controllers are also usually NSObject subclasses.
  • Model objects are the fourth role and raw models are the fifth.
    • We’ll define model objects as simple dumb stores of data in properties on NSObjects.
    • We’ll define raw models as a representation of data in a non-native format (e.g. JSON string, NSDictionary, NSManagedObject, etc.).

Rules

I’ve introduced the rules of each role in order to clarify the separation of concerns. Below are the rules that separate the concerns of each of the previous roles. Without context, the rules are somewhat abstract, so I’ll introduce examples immediately afterwards.

Views

  • Views are allowed to access views and view models.
  • Views are not allowed to access controllers or model objects.
  • Views bind their display properties directly to view model properties.
  • Views pass user events to view models via RACCommands/RACActions, or alternatively by calling methods on the view models.

View Models

  • View models are allowed to access view models, controllers, and model objects.
  • View models are not allowed to access views or raw models.
  • View models convert model objects from controllers into observable properties on self, or other view models.
  • View models accept inputs from views or other view models which trigger actions on self, other view models, or controllers.

Controllers

  • Controllers are allowed to access other controllers, model objects, and raw models.
  • Controllers are not allowed to access view models or views.
  • Controllers coordinate model object access from other controllers or directly from system level raw data stores (network, file system, database, etc.).
  • Controllers vend asynchronous (or maybe better put time-agnostic) data (via RACSignals) to view models or other controllers.
  • Have to stress again that these are not view controllers!

The MVVM Diagram Again

Let’s make a more detailed version of that MVVM diagram for Cocoa specifically.

View ========> View Model ========> Controller ========> Data Store
  |                |                    |
View           View Model           Controller

To clarify, ===> represents an ownership as stated above. | also represents an ownership of the bottom object by the top object. A view could spawn one or more subviews, present other view controllers, and also bind to a view model. Similarly, a view model could keep a collection of view models for its owning view to distribute to that view’s subviews. That view model can also have a controller and connect controllers to its sub-view models.

Secondly, here is the flow of objects between the roles.

View <-------- View Model <-------- Controller <-------- Data Store
    (view model)        (model object)        (raw model)

Notice from the first chart that all relationships are unidirectional. Thus there is only direct coupling at one interface and in one direction. It’s now possible to replace our view layer with a testing apparatus and test the interface between the view and view model directly. It’s also possible to test the interface between the view model and controller layer.

Notice from the second chart that each role transforms one class of objects into another class. Our role graph starts to look like a pipeline for transforming data from right to left, and pipeline for transforming user intentions from left to right.

An Aside About Synchronous/Asynchronous

In most apps, there’s an implicit distinction between methods that do synchronous work versus those that do asynchronous work. One best-practice is to write synchronous methods that are wrapped by asynchronous methods.

With ReactiveCocoa, synchronous and asynchronous are both treated as asynchronous. By treating everything as asynchronous, you would normally be committing your project upfront to an unnecessary burden of delegate or block callbacks strewn about the calling object. However, using a system with chainable operations, sane processing semantics (including built-in thread routing operations), and concise bindings makes it significantly easier to work with asynchronous data. Thus, treating all operations as asynchronous becomes a win when synchronous and asynchronous operations can be processed in the same ways (and combined). It is also a win because consumers of operations no longer require unnecessary knowledge of how expensive an operation might be.

A Simple Example

Let’s start with a simple example that will quickly spiral out of control. Imagine a view that represents a user’s profile. It should show a photo of the user, a label with the user’s name, a label with the number of friends the user has, and a refresh button because the user’s friend count changes a lot in this example.

This code was not written in an IDE, so please bear with typos.

View

The view is pretty simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 @class HOPProfileViewModel
  
  @interface HOPProfileView : UIView
  
  @property (nonatomic, strong) HOPProfileViewModel *viewModel;
  
  @end
  
  @inteface ProfileView ()
  
  @property (nonatomic, strong) UIImageView *avatarView;
  @property (nonatomic, strong) UILabel *nameLabel;
  @property (nonatomic, strong) UILabel *friendCountLabel;
  @property (nonatomic, strong) UIButton *refreshButton;
  
  @end
  
  @implementation ProfileView
  
  - (instancetype)initWithFrame:(CGRect)frame {
      self = [super initWithFrame:frame];
      if (!self) return nil;
      
      _avatarView = [[UIImageView alloc] init];
      [self addSubview:_avatarView];
      
      // ... create and add the other views as subviews
      
      RAC(self.avatarView, image) = RACObserve(self, viewModel.avatarImage);
      RAC(self.nameLabel, text) = RACObserve(self, viewModel.nameString);
      RAC(self.friendCountLabel, text) = RACObserve(self, viewModel.friendCountString);
      RAC(self.refreshButton, rac_command) = RACObserve(self, viewModel.refreshCommand);
      
      return self;
  }
  
  - (void)layoutSubviews { /* ... */ }
  
  @end

A few things going on here:

  • The view is not bound to a view model for its entire lifecycle. This case is more rare. Most views should be bound to a particular view model for their entire lifecycle. Less mutability reduces view complexity greatly. You would normally require the view model be passed into the receiver on init. However, in this case we’re allowing the view model to be swapped out during this view’s life, and we therefore must reconfigure its data properly. You’d typically see this pattern in reusable views such as UITableViewCells.
  • We’re creating a one way binding using ReactiveCocoa from our view model properties to view properties. Notice there is no data transformation at this stage.
  • The ReactiveCocoa will ensure self.avatarView.image is set with the current image in the self.viewModel.avatarImage property. It will ensure this even if the viewModel object itself changes during this view’s lifecycle. If our view was initialized with a view model, we could write RAC(self.avatarView, image) = RACObserve(self.viewModel, avatarImage) instead and only the avatarImage property will be observed.
  • The label properties work the same way as the imageView’s.
  • RACCommand is a somewhat magical object that transparently manages state between an action and its asynchronous results. The important part to notice here is that the view model owns and configures the RACCommand object in question. Behind the scenes, the rac_command helper category on UIButton performs three tasks (heavily simplified):
    • Calls -[execute:] on the view model’s RACCommand on the touchUpInside action.
    • Disables itself while the RACCommand is executing.
    • Re-enables itself when the RACCommand finishes executing.
  • Imagine that there are standard layoutSubviews and sizeThatFits: methods.

You may be asking what this buys us so far over the typical pattern of passing in a model object to our view via a setter like -[setData:(HOPUser *)].

  • It’s straightforward to add functionality to this view/view model pair. Need to load a low res cached image after a placeholder image followed by a high res network image? The view layer doesn’t change. It will automatically adopt whatever image is currently stored by the view model. There will never be a sprawl of callbacks originating from views hitting the network.
  • Our friend count is stored in our model as an NSNumber but our label needs a formatted NSString. The view layer isn’t bothered with the conversion, whether it be a simple @25 –> “25” or @25 –> “This user has 25 friends”.
  • We can test the view model directly by allowing the test bench to compare UIImages and NSStrings.
  • In a more proper version of this view, a superview would bind a view model to a more generic subview, and thus enable a set of ultra-reusable content blocks to be used throughout the app with one or more various view models. The glue code is simple one-to-one bindings.

In short, we’ve separated the data manipulation stage from the presentation.

View Model

Now let’s tackle the view model. The interface should look pretty familiar.

1
2
3
4
5
6
7
8
9
10
11
12
 @class HOPUser;
  
  @interface HOPProfileViewModel : NSObject
  
  @property (nonatomic, strong, readonly) UIImage *avatarImage;
  @property (nonatomic, copy, readonly) NSString *nameString;
  @property (nonatomic, copy, readonly) NSString *friendCountString;
  @property (nonatomic, strong, readonly) RACCommand *refreshCommand;
  
  - (instancetype)initWithUser:(HOPUser *)user;
  
  @end

Notice all these properties are readonly. The view is free to observe all these properties and call execute on the RACCommand. The view model obscures all its internal operations and provides a limited window into its state to its observers (its view).

There’s a designated initializer that accepts a HOPUser model object. For now, assume that another view model created this HOPProfileViewModel with a model object before it was bound to its view (I’ll come back this as my most glaring questions about MVVM).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 @interface HOPProfileViewModel ()
      
  @property (nonatomic, strong) UIImage *avatarImage;
  @property (nonatomic, copy) NSString *nameString;
  @property (nonatomic, copy) NSString *friendCountString;
  @property (nonatomic, strong) RACCommand *refreshCommand;
  
  @end
  
  @implementation HOPProfileViewModel
  
  - (instancetype)initWithUser:(HOPUser *)user {
      self = [super init]
      if (!self) return nil;
              
      RAC(self, avatarImage) =
          [[[[[RACObserve(self, user.avatarURL)
              ignore:nil]
              flattenMap:(RACSignal *)^(NSURL *avatarURL) {
                  return [[HOPImageController sharedController] imageSignalForURL:avatarURL];
              }]
              startWith:[UIImage imageNamed:@"avatar-placeholder"]]
              deliverOn:[RACScheduler mainThreadScheduler]];
      
      RAC(self, nameString) =
          [[RACObserve(self, user.name)
              ignore:nil]
              map:(NSString *)^(NSString *name) {
                  return [name uppercaseString];
              }];
      
      RAC(self, friendCountString) =
          [[RACObserve(self, user.friendCount)
              ignore:nil]
              map:(NSString *)^(NSNumber *friendCount) {
                  return [NSString stringWithFormat:@"This user has %@ friends", friendCount];
              }];
              
      @weakify(self);
      _refreshCommand = [[RACCommand alloc] initWithSignalBlock:(RACSignal *)^(id _) {
          @strongify(self);
          return [[HOPNetworkController sharedController] fetchUserWithId:self.user.userId];
      }
      
      RAC(self, user) =
          [[[_refreshCommand executionSignals]
              switchToLatest]
              startWith:user];
  }
  
  @end

Alright, there’s a lot more going on in this view model than there was the view. And that’s a good thing. There’s some slightly advanced ReactiveCocoa, but don’t get hung up on it. The goal is to understand the relationship between the view, view model, and controllers.

  • First, we redeclare our outward-facing properties as readwrite internally.
  • Our first property binding is the avatarImage. We see that our image is represented as a URL in the HOPUser model. We first observe the avatarURL property on whatever the view model’s current user model is. Each time it changes, we take that URL and feed it into our singleton HOPImageController. The image controller is responsible for caching thumbnails, full images, and also fetching images from the network. This signal will send up to three different images which will eventually be assigned to self.avatarImage. The images may be fetched on background thread, so we make sure they’re delivered to their eventual destination imageView on the main thread.
  • The next property binding is nameString. We’re only performing one mapping operation on this string: uppercasing.
  • We map the friend count to a human-readable string.
  • The refreshCommand is created from scratch. It subscribes to the signal block each time the command is executed (in our case, when the button is pressed). The command automatically keeps track of the state of our signal and will not execute again until the inner signal has completed. In this case, we’re assuming our data comes from a shared HOPNetworkController which sends a HOPUser object and completes.
  • The self.user mapping first assigns the user object passed into the init method, then takes the latest result from the command’s execution.

There was a lot to digest in that example. Things to notice:

  • All of the code was incredibly declarative. We stated exactly what each of our properties should be at any given time. They’re all only set from one place.
  • We have a lot of flexibility changing the operations on our model object’s properties in response to product changes.
  • It’s incredibly easy to mock this object for our view. It only has four external properties. For example, our fake implementation could map our self.avatarImage property to [[[RACSignal return:[UIImage imageNamed:@"final"]] delay:4] startWith:[UIImage imageNamed:@"placeholder"]]; which would simulate a placeholder image, a four second fake network delay, and a final image.
  • I’ll leave error handling for another post, but as a quick summary is the RACSignal contract makes it almost trivial to bubble up errors to the view model layer and present them in the proper way.

Controller

I have more questions than answers when it comes to the controller layer. I’ll present the header files for the two classes we used above and we’ll go from there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 @interface HOPImageController : NSObject
  
  // The shared instance of this class.
  // Inside it has three functions:
  // * It maintains a separate network client for fetching raw image data.
  // * It maintains a key/value store of imageURLs and images on disk.
  // * It adds images from the network to the cache.
  + (instancetype)sharedController;
  
  // The returned signal sends an image from the cache if available,
  // then an image from the network, then completes.
  // The signal sends an error if there was a network error.
  - (RACSignal *)imageSignalForURL:(NSURL *)URL;
  
  @end
  
  
  @interface HOPNetworkController : NSObject
  
  // The shared instance of this class.
  // Inside it manages a network session.
  + (instancetype)sharedController;
  
  // The returned signal sends a HOPUser, then completes.
  // The signal sends an error if there was a network error.
  - (RACSignal *)fetchUserWithUserId:(NSNumber *)userId;
  
  @end

Questions

I tried to include some non-trivial aspects to this view/view model/controller set up, but at the end of the day this set of objects has to exist in a much broader application.

In something run-of-the-mill like a UITableView system, there could quickly be a large graph of view models that each held arrays of view models which then have to be mapped to sections and reusable cells, and it can quickly become a mess of mapping view model class names to views, all while handling changing intermediary objects, refreshing, and errors at the individual cell level.

I left a lot of hanging questions in the system I’ve presented above (only somewhat purposefully). I’m hoping someone with more experience in MVVM can shed some light on these.

Where is the top of the object graph?

I stared off talking about testing, but by the end I was embedding singleton controllers deep within my view model implementation. In the interest of dependency injection, they should be specified at initialization. Being available as parameters for init is great for testing, but in the actual app, which view or view model should be responsible for creating the view model in question along with knowing exactly which controllers to provide?

At a certain point, some object is going to have to connect all the dots and assemble the entire object graph. And at that point it may well be creating objects of all three roles (views, view models, and controllers). On one hand, it seems very offputting to allow one god object to have the entire map of the application. But on the other hand, that’s sort of like an extreme form of composition: all lower level objects are very dumb with very specific inputs and outputs.

Is this what the router is in Rails? It starts stateless and uses its request input parameters to assemble the object graph, produce a response, then tear it down.

Are there other examples or patterns in other languages? I’m curious if this would all be more clear if I was more versed in Haskell, or enterprise Java, or any other number of languages.

Is the right answer for testing to have designated “testing” initializer that accepts a HOPImageController instance and a HOPNetworkController instance that can be mocks, while the application version is initialized with no parameters and configures its own controllers?

When should controllers be singletons?

Is there a hard and fast rule in MVVM for when a controller should be a singleton? When a resource starts storing state amongst disparate objects is that cause for being a singleton? Maybe the goal is actually on the opposite end: every controller should be a singleton to keep all services completely autonomous and interchangeable.

My first hunch on this was that controllers that sat adjacent to system raw object producers (e.g. the network interface, an SQLite db, the file system, NSUserDefaults, etc.) would be singletons. But I also saw, for example, a controller that reads a single file should be configurable with a file URL by a parent object. Maybe it just depends on where you draw the line between needing lots of helper controllers and doing all the fetching directly from the view model.

What are a view controller’s responsibilities?

Don’t get me wrong, doing some OS X development for the first time gave me a deep appreciation for UIViewController. But there’s still a lot of API cruft that’s developed on UIViewController that makes certain things difficult.

When you’re trying to express your app as declaratively as possible, it’s sometimes easy to get lost in what the view controller hierarchy looks like, and how the imperative view controller changes can really put a stick in your tires.

I don’t have as many examples yet since I’m still sort of getting a lay of the land with MVVM, but maybe I’m wondering whether there’s a two-tiered view system: the bottom tier is very dumb and just gets bound to view models, and then the top tier which does all the object graph assembly (and dependency injection) for the lower layers. Or is it a two-tiered view and view model system?

How should we treat the current user and the user’s session?

In iOS apps, it’s taken for granted that we only have to handle one user session at at time. In the Timehop app, we use the currentUser object on almost every screen.

Would it be The Right Way™ to pass this user object into a view model from the top of the object graph down to all the other view models/controllers that need it? Would this be a case where a singleton user session controller makes sense to store the currently logged in user? In either case, how can we react to a user logging out without depending on the way the view hierarchy is laid out?

Maybe the user session would be stored at the top level of the application, and then changes to the current user would be pushed directly to the top level view models and these view models would react accordingly. This would seemingly become quite unwieldy if a large number of sub view models had already been spawned from the top level view model. The top level view model would either have to distribute the current user to every other object directly and keep pushing new current users, or it would distribute the current user once and treat it as immutable on sub view models from then on.

Relatedly, what about the current user’s auth token? In our example application, we have a network controller that requires the user’s auth token to be sent in the header of nearly every request. Should the network controller be a singleton with a mutable authToken parameter maintained by the application? Should one network controller be created at the top level and passed directly from view model to view model? How do we propagate changes in the auth token? What does not using a singleton buy us in this situation?

My initial solution to this problem was to have a userSessionController singleton that holds the currentUser object. This singleton creates a new immutable instance of the network controller, database controller, user defaults controller, etc. whenever the currentUser object changes. Almost all requests from other controllers or view models go through the userSessionController singleton. The user session quickly becomes another god object distinctively separate from the app delegate, and now almost every view model and controller is bound directly to the userSessionController singleton.

I’ve talked myself in circles with this one. I can sort of see the pros and cons with each, and maybe the technique used is completely dependent on the individual product requirements for each app.

Summary

I tried to explain MVVM at a high level the way I currently understand it. I wrote a flat example with a component from each role. I then explored several questions that arose from this exercise and a few other situations.

I would greatly appreciate any feedback on this post. In particular, I’d really like to flesh out my understanding of MVVM in large architectures that can scale to multiple data sources, hundreds of views, and millions of users all while staying snappy and crash-free.

Fall 2013 Project Wrap Up

At the end of every quarter or at least biannually I try to wrap up all the little projects I worked on during that time period that weren’t large enough to warrant their own blog post. Here’s a short summary of all the little projects I’ve worked on since I moved to New York to work at Timehop.

Timehop

My full-time job, but worth mentioning that we’ve shipped a lot of features since I started working at Timehop.

Combined Share Flow

We combined sharing to social networks and dark channels (email/sms) into a two panel pop-up behind a single share button on the home screen. I spent most of my time on the email/sms screen. The tricky parts were dealing with using a field that both showed the contacts you had already selected as well as allowing you to enter search mode. There ended up being several screen states with animations between each.

Twitter @replies

A short project to allow users to see the full thread of a conversation they had on Twitter. Originally planned to be much more detailed, we decided to scope down the project to push out to Twitter’s web version until we understood how much use the feature would get.

In-App Private Sharing

There were two internal prototypes of a feature to share Timehop content within the app before the final released version.

The first featured a drawer that had a tabular list of people or groups that you shared Timehop content to in a long running thread of content and comments.

The second was a two column collection view of Timehop content shared to you. Each piece of content was obscured so that tapping it revealed it in a flip animation.

The released version materialized as a quick share panel of recent contacts or groups that with one tap, you could share Timehop content to. A notification table provided a link to each piece of content, with the content having a detail view with comments.

During this time, I also got to rewire a lot of the message passing that happened within the codebase. And right before shipping, I also refactored the start up, log in, sign up, and welcome flow, and cleaned up the app delegate.

Nearby

This feature was a one-week project that aggregated Timehop content that happened near the location you opened the app and allowed you to explore the day it happened. There were a lot of cool animations I got to play around with for this feature.

Journaling

We ran a few beta experiments with gathering new content to make next year’s Timehop better.

The first version showed photos from today’s camera roll and allowed you to upload them to Timehop.

Another version presented a different daily screenshot challenge to the user.

Another version showed a front-facing camera window in the bottom of the Timehop day. When you scrolled the window into view it would begin a 3..2..1 countdown and snap a picture of the user. The user could then either upload the photo to see next year or retake it.

Vinylogue

Status: On Hold

After ignoring it since its April 2013 release, I did a little bit of house cleaning and updating this app for iOS 7. That part was actually pretty easy since the style was pretty stark to begin with.

I started working on a new feature to schedule local notifications to alert the user when their weekly charts have refreshed. In theory, all users are on the exact same schedule of being refreshed on Sunday night, but I wanted to use the actual data I get from Last.fm. There were a lot of other decisions as far as how many local notifications I should schedule in advance (I think I ended up at four), if I should do the scheduling on every app open (by canceling all active notifications and rescheduling them), and if I should do it in primary controller when I fetch year data or in a separate call (I ended up doing it doing it in a separate place).

Unfortunately, I haven’t got around to finishing the feature yet. I’m pretty sure all that’s left is testing. My user base isn’t huge and there hasn’t been much outreach on it, so although it could bring retention numbers up, it hasn’t been at the top of my to do list.

Another feature I’ve wanted to do is some sort of sharing for the album detail view. The only thing I have to get that going is to minorly refactor the view hierarchy.

SocketParty

Status: On Hold

One night after work I decided I wanted to play around with websockets on iOS. So I started a new project, pod installed socket rocket, and started reading the docs. Somehow reading the docs wormholed me into making a game based on colors and the accelerometer (and nothing to do with websockets).

Start screen
Start screen
Playing the game
Playing the game

(Ignore the ugliness, I planned on tightening up the design after I finished the mechanics).

Sort of hard to show in screenshots, but tilting your phone along any of the three axes changes the background color. The goal of the game is to match the randomly selected color on the top by tilting your phone. The player ends up looking like they’re doing a weird dance.

In theory it’s a nominally fun game. In practice, I ran into some development trouble. I’m still unsure of what the range of the raw accelerometer data is. There is very little info in Apple’s docs. It doesn’t help that I have a 4S and I assume they’ve improved the accuracy of the accelerometer a bit in the newer iPhone models.

I somewhat solved the problem of showing “fun” colors. If you use RGB and map floats from 0 to 1, you end up with a lot of ugly grayscale. Not something I initially anticipated. I actually ended up switching to HSV, and the colors are now bright and primary by mapping to a reduced scale of saturation and brightness. Much more fun.

In the current version it’s actually next to impossible to get a match. I thought it would be easy to set a define for the “closeness” that a color match had to be. Something about having three variables and scaling the values along the way makes this not as straightforward as I anticipated.

One interesting part of this project for me was trying AutoLayout for the first time in a real project. Granted, I’m not using it raw via IB or code. I decided to use the wrapper library UIView-Autolayout. As a taster, here’s some of the layout code:

in updateViewConstraints
1
2
3
4
5
6
  const CGFloat bottomLabelOffset = 10.0f;
  const CGFloat bottomInterLabelOffset = 2.0f;
  [self.matchesLeftBottomLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.matchesLeftBottomLabel.superview withOffset:-bottomLabelOffset];
  [self.matchesLeftBottomLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:self.matchesLeftBottomLabel.superview withOffset:bottomLabelOffset];
  [self.matchesLeftTopLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:self.matchesLeftTopLabel.superview withOffset:bottomLabelOffset];
  [self.matchesLeftTopLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:self.matchesLeftBottomLabel withOffset:-bottomInterLabelOffset];

It’s actually somewhat similar in API to POViewFrameBuilder which I use often at work. But I remember getting hung up a couple times. It works, but at this point I’m still a little skeptical of what AutoLayout buys you.

The best part of this project is the unrelated name. If I ever decide to finish it, I’ll have to apply a better moniker. I lost interest for now, but maybe when I get a new iPhone I’ll be more inclined to give it another shot.

TimeSnapHopChat

Status: Dead

I mentioned above that our team at Timehop was experimenting with ways to get people to generate more content for their Timehop days next year. I had the idea over Thanksgiving break to create a Snapchat clone with a Timehop twist.

The idea was that this would be a separate app from Timehop. The interface would be very similar to Snapchat.

List of sent and received snaps
List of sent and received snaps
Create a new snap
Create a new snap
Pick who you want to send it to
Pick who you want to send it to
Preview what your Snap will look like to the recipient today and in the future
Preview what your Snap will look like to the recipient today and in the future

The twist to this app would be that the recipient could only see the Snap for a few seconds like Snapchat, but both the sender and the recipient would see the Snap in their Timehop day the next year. It combines the lightweight communication of Snapchat with the idea that photos get more valuable with time (Timehop!).

I wrote the prototype in about a day in a half. I used some existing Timehop endpoints, but it didn’t have anything as far as user log in or any styling.

In the end, we decided to go a different direction with the journaling idea. But I enjoyed getting a chance to do more quick prototyping, and to play around with figuring out how to code up the Snapchat mechanics.

TimeStop

Status: Dead?

TimeStop was another Timehop journaling prototype. I can attribute the original idea to my co-worker Kevin’s brother Tom.

The idea is that you may be out at a restaurant or a rock show and you want to “stop time” for yourself – attempt to gather as much information as possible about your current status so that you can accurately remember this exact moment later. This might include things like the last 5 posts in your Twitter feed, the Wikipedia article on the concert hall you’re at, the top headline of the New York Times, a few photos tagged at that location from the public Instagram feed, or a million other things.

It was difficult to explain my vision for the user interface for this feature, so I prototyped it.

Screenshots don’t do justice for this one either, but you can imagine the user pressing and holding the “STOP” button, and a bunch of photos and articles flying in from the outside of the screen getting sucked into the button. At the same time, the screen fills up blue and increases the amount of time you want to look back to gather data.

While pressing and holding "STOP"
While pressing and holding “STOP”
Representative views fly in from the sides and the blue fills up from bottom to top
Representative views fly in from the sides and the blue fills up from bottom to top

I threw together this demo after work one night (it really deserves a GIF or a video, my bad). I didn’t code up the next step. The server would begin aggregating content based on the user’s location and then deliver it in some sort of list to the device. The user would then do a quick sort through of the compiled data and delete anything they wouldn’t consider relevant. The server then would create a package of data that could be viewed in next year’s Timehop, or maybe shared at that current moment.

As far as implementation, I simply schedule a random amount of animations at random locations, at random intervals, and with random images (there are only two right now). A cool thing about this project was that I used ReactiveCocoa in some critical places that made things a lot easier.

Here’s a small onslaught of the view controller code:

TCSTimeStopDemoViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
- (void)btnTouchDown:(UIButton *)btn {
  self.progressView.frame = self.view.bounds;
  self.progressView.top = self.view.bottom;
  self.progressLabel.text = @"STARTING UP...";
  [self.progressLabel sizeToFit];
  self.progressLabel.width = self.progressView.width;
  self.progressLabel.left = self.progressView.left;
  self.progressLabel.top = 10;

  // Blue progress view animates up a slow pace
  [UIView animateWithDuration:10 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
    self.progressView.top = self.view.top;
  } completion:^(BOOL finished) {  }];

  // Super hacky way of changing the progress text that has to match up with the above animation
  self.progressLabel.tag = 0;
  RACDisposable *progressLabelDisposable = [[RACScheduler mainThreadScheduler] after:[NSDate date] repeatingEvery:1 withLeeway:0 schedule:^{
    if (self.progressLabel.tag == 0) {
      self.progressLabel.text = @"STARTING UP...";
    } else if (self.progressLabel.tag == 1) {
      self.progressLabel.text = @"2 MINUTES";
    } else {  // some code removed here
      self.progressLabel.text = @"24 HOURS";
    }
    self.progressLabel.tag = self.progressLabel.tag + 1;
  }];
  [self.viewMakers addObject:progressLabelDisposable];

  // A bunch of scaled y-position making
  for (int yPos = -50; yPos < 600; yPos=yPos+200) {

    // Schedule 3 x-position animations at each for-loop y-position.
    // The interval at which the three animations are repeated is random.
    RACDisposable *viewMaker = [[RACScheduler mainThreadScheduler] after:[NSDate date] repeatingEvery:(((double)arc4random_uniform(100)+1)/100.0) withLeeway:0 schedule:^{
      CGFloat leftPos = ((CGFloat)arc4random_uniform(200))-200;
      CGFloat rightPos = ((CGFloat)arc4random_uniform(200))+320;
      CGFloat centerPos = arc4random_uniform(320);
      CGFloat sidePercentage = (((CGFloat)arc4random_uniform(40))+60)/100.0;
      UIImageView *viewLeft = [[UIImageView alloc] initWithFrame:CGRectMake(leftPos, yPos, 0, 0)];
      UIImageView *viewRight = [[UIImageView alloc] initWithFrame:CGRectMake(rightPos, yPos, 0, 0)];
      UIImageView *viewCenter = [[UIImageView alloc] initWithFrame:CGRectMake(centerPos, -30, 0, 0)];
      viewLeft.image = arc4random_uniform(2) ? [UIImage imageNamed:@"newspaper"] : [UIImage imageNamed:@"poloroid"];
      viewRight.image = arc4random_uniform(2) ? [UIImage imageNamed:@"newspaper"] : [UIImage imageNamed:@"poloroid"];
      viewCenter.image = arc4random_uniform(2) ? [UIImage imageNamed:@"newspaper"] : [UIImage imageNamed:@"poloroid"];
      [self.animationCanvasView addSubview:viewLeft];
      [self.animationCanvasView addSubview:viewRight];
      [self.animationCanvasView addSubview:viewCenter];
      [viewLeft sizeToFit];
      viewLeft.width *= sidePercentage;
      viewLeft.height *= sidePercentage;
      [viewRight sizeToFit];
      viewRight.width *= sidePercentage;
      viewRight.height *= sidePercentage;
      [viewCenter sizeToFit];
      viewCenter.width *= sidePercentage;
      viewCenter.height *= sidePercentage;
      [UIView animateWithDuration:((double)arc4random_uniform(100)/100.0)+0.1 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        viewLeft.frame = CGRectMake(self.timeStopButton.x, self.timeStopButton.y, 30, 30);
        viewRight.frame = CGRectMake(self.timeStopButton.x, self.timeStopButton.y, 30, 30);
        viewCenter.frame = CGRectMake(self.timeStopButton.x, self.timeStopButton.y, 30, 30);
      } completion:^(BOOL finished) {
        [viewLeft removeFromSuperview];
        [viewRight removeFromSuperview];
        [viewCenter removeFromSuperview];
      }];
    }];

    // Keep track of all the disposables so that we can cancel the repeating animations when the button is released/
    [self.viewMakers addObject:viewMaker];
  }
}

- (void)btnTouchRelease:(UIButton *)btn {
  [self.progressView.layer removeAllAnimations];
  self.progressView.top = self.view.bottom;

  // Dispose of all animations (thanks, ReactiveCocoa!)
  [self.viewMakers makeObjectsPerformSelector:@selector(dispose)];
  [self.viewMakers removeAllObjects];
}

Pretty hacky, but much more elegant thanks to ReactiveCocoa. If you’ve ever worked with raw NSTimers, you understand how ugly that API is.

I don’t think we’re planning on revisiting the TimeStop idea at least in the near future. This demo is definitely fun to show though.

Technicolor TV

Status: Under Infrequent Development

This project is a little off the beaten path for me, but very dear to my heart in fulfilling an active need. It requires a bit of backstory though.

One prong in my multi-pronged approach of staying in touch with my Chicago friends is what we call simulcasting/liveblogging TV shows. My friends Bobby and Brian and I have “Wonder Years Wednesdays” in which we each watch an episode of the classic late-80’s tv show “The Wonder Years” (we’re currently nearing the end of season 3).

We each watch the episode at different times of the day, so our solution to sharing our thoughts and jokes about the week’s episode was for one person to write an email with the episode timestamps of each comment. The next person that watched would reply to that email and fill in their comments the same way, but in a different font color. We did this for several weeks, and I started to get tired of the awkwardness that is line-by-line email replies.

A very small excerpt of an email thread
A very small excerpt of an email thread

I hadn’t done a Rails project in a while, and I was imagining the perfect webapp to automate a lot of what we were doing with the email threads. So I did a quick wireframe.

Quick whiteboard wireframe
Quick whiteboard wireframe

While thinking through the requirements, I realized that there were times where the times that we watched the episode might conflict. And I know I wouldn’t want to have to deal with refreshing the page if this happened. I put websockets back on my “to consider” list.

With just my very rough episode page wireframe, I started development. I decided to use Rails 4, because hey, Rails moves kind of fast. I actually did a bunch of research into making this my first foray into making a fully client-side app in Ember. I was mostly grasping the ideas, but bailed after realizing how much of the user authentication code I would have to write. I was much more interested in developing the chat part of the app than I was copying and pasting code from an Ember tutorial. I’m definitely still interested in Ember though. Maybe next time.

I always start out by making a markdown doc with my app’s models and routes, as well as solutions to any gotchas I think I’ll run into later. I’ve removed a bunch of the models so this post doesn’t stretch on forever.

planning.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
### Program

* integer :id
* string :name
* has_many :episodes

### Episode

* integer :id
* string :name
* integer :season
* integer :number
* belongs_to :program
* has_many :rooms

### Room
### Timestamp
### Comment
### User
### RoomsUsers

## Routes

GET /dashboard => shows all rooms for a user
GET /room/:id => shows a room
POST /room/:id/timestamp => creates timestamp and comment objects

From here, I’ll add my standard gem set. Then write my initial database migrations. Then generate models and controllers.

I’m still in the bad habit of not writing tests for everything. If this project gets serious, it would definitely be a good idea to fill in those blanks.

I’ll add a view at a time, and use those to knock out any bugs with my models and controllers. I used to use Haml, but I’ve actually switched back to standard erbs because all the extra time I’d spend debugging non-standard line constructs would usually eclipse the amount of extra time I spend writing closing tags.

The app really only has two screens (besides all the auth screens). The entry point is the Dashboard. It shows a list of all the Rooms the current user is a member of. A room is a place where several users discuss an episode of a program.

The current user
The current user’s Dashboard

The room detail screen shows the contents of the room.

A room
A room

The layout of the room screen is essentially threaded comments by timestamp. Creating a new timestamp and commment is the same action for the user. Typing in a timestamp like 12:41 followed by the comment text will create the timestamp thread if it doesn’t exist, or if it does it will add the comment to the end of the thread. If no timestamp is entered, it’s interpreted as 0:00, which is the defacto general thoughts thread for the room.

Each user has an avatar and their own text color to help easily scan for a user’s comments.

I also added some extra text box features. Hitting enter submits the comment. Shift+enter creates a new line in the same comment. Pressing the up key scrolls to the top of the page. Pressing the down key scrolls to the bottom.

Getting into the more technical details now. It took a few false starts, but I figured out how to incorporate websockets into the room. A websocket channel is opened for user when they load the room for the first time. At that time, the initial representation of the page is rendered by the server and returned. Any time a comment is created on the server, an HTML partial for the comment is rendered by the server and broadcast to the room’s websocket channel. JS on the client-side receives the comment and takes care of adding it to the DOM in the correct spot.

I could have also had the client send new comments via the websocket channel, but it was conceptually simpler for the server to be the main arbiter and broadcaster of data, instead of treating it as a peer on the channel. It also makes sense that if the server somehow fails to store the comment, the other clients shouldn’t have a copy of that comment.

Following that logic, adding a comment is a standard AJAX POST to the server. The server receives the comment, stores it, broadcasts it on the room’s websocket channel, and all subscribed clients parse the partial and add it to the DOM. This includes the client that created the comment. It actually saves me a step of having to deal with adding a comment to the DOM that has no server assigned ID, and then updating it once the request from the server is successful.

It works the same way with deleting comments. I have yet to implement editing (deleting and resubmitting the comment is the workaround for now).

Working with websockets was pretty magical. I had a bit of a scare though. I had written and tested all the websocket features locally, but didn’t realize that Heroku didn’t support them! Luckily, at almost exactly the time I was ready to deploy to production, Heroku released a websockets beta feature that I could quickly enable.

I learned a rough lesson that sometimes even when you custom build a solution to your problem, there are nuances you can’t easily conquer. More specifically, my friend Bobby spends a lot of time on his phone, and email is most convenient for him. The app is responsive out of the box enough to read on a mobile device, but I haven’t spent the time to make sure the comment box appears correctly. The app also requires a log in step, which email does not. And email also has built in notifications, and Technicolor doesn’t send out any type of email notifications or push notifications yet when another room member comments.

None of these feature requests are impossible. But each requires another significant time investment that’s hard to justify for a user base of three. There are several lessons here:

  • Sometimes it takes significant investment to beat the hack solution to a problem.
  • Hack solutions often get a lot of features for free that your custom solution needs a custom feature to equal.
  • It’s important to really know your users. My users are my best friends and even then I didn’t understand their use cases well enough to make the right solution on the first shot.

The future of Technicolor is unclear. My friends and I are still planning on using it for Wonder Years Wednesdays and probably some other shows soon (Brian and I used it simultaneously for the last couple episodes of Homeland and it was a lot of fun).

I’d really like to turn it into a real product, but I doubt this is a common behavior, or that I could convince people how much fun it is. Maybe some day I’ll cobble together an iPhone app (although Brian is an Android user so it would only be two of us that could use it). I could also do email notifications, even if they started out as manually triggered.

Still a plenty to do, but again, I had a lot of fun writing this (it took a few weekends), and it was great to dive back into Rails and learn a bunch of new stuff about websockets.

Insurance App

I committed to a freelance iOS project back in January of last year (wow, time flies) for a small insurance broker in Chicago. My good friend and expert designer CJ handled the product and design for the app, and after a bit of a hiatus we started working on it again in late November.

Here’s an App Store link if you want to download it or see more screenshots.

There were actually a lot of interesting constraints to this project.

The home screen
The home screen

On the front layer of the app there were a few informational screens that I used a template view controller to coordinate. All four view controllers subclassed the base view controller and overrode class methods that returned static data for each. The base controller would gather text or image data from these methods and layout the views the same way.

An informational screen
An informational screen

I began the project by attempting to put a figurative firewall between the raw content and the layout. I put all the content in a plist and created a framework around drawing that data into the specific controllers. I eventually abandoned that method because it actually makes things a lot more complicated in an app with a very simple scope. It would have been nice to be able to fetch a plist from S3 when the client wanted to update the content of the app, but that was hardly a requirement, and at this point would be over-optimization.

The bulk of the app was a data collection utility for customers to input and submit data about a car accident.

The table of contents for the accident utility
The table of contents for the accident utility

Our goal was to make the flow very predictable, even if all the data sources were very different.

Choosing a location for the accident
Choosing a location for the accident

Each screen has its own development quirks. UIKit can be frustrating sometimes. iOS 7 bugs bit me a lot especially with view controller transitions. But by the end, I think it came together well.

For the backend of the app, I used another Team Github library called Mantle which I highly recommend for non-database-backed apps. The requirements for my model were that the accident report needed to be saved between uses and deleted after being submitted to the insurance company.

The app also had a unconventional saving structure on sub-screens. In where most Mac/iOS apps, it’s customary to save immediately on changing an attribute, this app requires a specific user action in order to execute a save (tapping the save button). Therefore, I had to craft the memory and delegation model to keep a temporary copy of an object in the detail controller, and then pass back that object on save to replace the old one. There was actually two layers of this before the attribute was saved to disk.

Once an accident report is submitted, I save the file as an archived file with the date and delete the current file. That way, a user always has a copy of their submitted reports. Even though we didn’t have the budget to build a section to browse previously submitted reports, in the current structure, it would be trivial to implement if requested later. As of now, the user could reload a past accident report using the iTunes interface.

The last unique part of this app was the actual submission from user to the insurance company. If I submitted the data and images using a normal POST, we would immediately need to write a backend webapp to receive and store the data, and an admin interface for the insurance company to access the reports. We would also need to notify the insurance company when the new report was submitted. And to add to the complexity, we would also need to create additional fields for capturing the user name, or possibly even have user authentication. There wasn’t any budget for this additional functionality, so we solved the problem by using good ol’ MFMailComposeController. I formatted the text in an email, adding images as attachments, and let the user send it. This gives the insurance company immediate notification, a CMS that everyone understands (email program), a database with search (email program), user identification (email address and name), and lead generation for users that aren’t currently customers.

Overall, I think the app turned out pretty well, especially for a smaller budget project.

Photo Sharing App

Status: In Active Development

Don’t ask me why, but I’ve decided to write a photo sharing iOS app. This particular idea was spawned from another photo sharing app idea I had a couple weeks ago.

Last week I made some visual mockups (it’s only seven screens so far, not including authentication or onboarding screens). I’ve spent a couple days writing code.

The new things I’m focusing on for this project are using Parse for the backend and leveling up with ReactiveCocoa. This is my first time doing things the NoSQL way, so that’s been enlightening. So far Parse has been surprisingly refreshing to use. The API is very clean, and although I’ve run into a couple snags so far, they’re not kidding about being able to get a prototype off the ground extremely quick.

The other unexpected awesomeness about using Parse and ReactiveCocoa is using the ReactiveExtensions. These are simple RACSignal producing wrappers for parse saving/fetching/deleting/etc. methods that are usually block or delegate based. They make life a lot easier, and allow a lot of elegant chaining operations.

I will most likely write up an entire post on this project or maybe even open source the code once I’ve hit V1. In the mean time, message me on twitter if you’re interested in beta testing.

What’s Next?

It’s always good to throw out some general and specific goals for the next quarter.

  • My top priority is getting a beta out of my untitled photo sharing app and seeing if it’s any fun.
  • Circle back to the backlog of Vinylogue, SocketParty, and Technicolor.
  • I still think that someday I’ll think of a project I can work on to learn Haskell.
  • I’d like to open source some sort of generalized iOS component.
  • Rewrite the backend of the Timehop app once I figure out what strategy I should use (and find the time).
  • Write a blog post with a little to a lot of sample code at the end of every sprint.
  • Run the backend stack frequently and start contributing to the backend codebase at Timehop.
  • Get quicker and more efficient working with git and managing branches.
  • Learn vim and/or get faster editing with Xcode (is that possible?).
  • Learn more about OAuth, TLS, Facebook and Twitter login, and general app security.

That seems like a healthy set of things to do.

Until next time.

Six Months at Timehop

It was about six months ago I started contracting at Timehop. After six weeks of remote work from Chicago, I decided to move to New York City to join the team full time.

In late June, I packed up a small U-Haul truck with most of my belongings. My friend Sergio and I drove that truck 14 hours across the mid-west.

I’ve learned a lot in the past six months, most of it from my co-workers, but some of it from banging my head against a wall until late into the evening. I’ve pushed some solid code and some buggy code to the App Store.

I’ve had the chance to write features that hundreds of thousands of people will interact with every day. I’ve also had the chance to prototype features and tweak them to get them just right.

I’ve been to a few meetups, and I’ve met some really cool developers. I’ve met some new friends, and I’ve stayed as close as I can with those back in Chicago (I’m not sure how we’d do it without FaceTime and Google Hangouts).

It would take me the rest of the year to finish this post if I tried to get anymore specific about all the things I’ve done in the past six months. I think it suffices to say that the adventure has just begun. I’m really excited about what’s ahead.

How I Wrote Vinylogue for iOS With ReactiveCocoa

Vinylogue is a simple app I designed and developed for iOS that shows your last.fm weekly album charts from previous years. This post will detail the process of creating V1.0 of the app from start to almost finish (is an app ever really finished?).

The full source is available on GitHub.

Warning: this post is super long (10000+ words). I spend a lot of time discussing ReactiveCocoa techniques, a little bit of time on pre-production, and a little bit of time on design.

Contents

Idea

I recently came across an awesome app called TimeHop that compiles your past posts from Facebook, Twitter, etc. and shows you what you posted on that day one year ago. I love reminiscing through my old things, so this concept was right up my alley.

I also love music too though, and have been an avid Last.fm user since 2006 or 2007, scrobbling tracks religiously. I thus have quite a history of my past album listens in their database, and with those listens, a lot of memories connected to those albums. I can remember exactly what I was doing, where I was, and mental snapshots when I see an album or group of albums together.

And so the idea of combining Last.fm and TimeHop was born.

Planning

Getting a feel for the data

The first step was seeing what data was available from the Last.fm API. I could indeed get a weekly album chart (a list of albums, ranked by number of plays) for a specified user and week with user.getWeeklyAlbumChart. It’s also possible to get the same chart grouped by artist or track, but at the current time it seemed like albums would appeal to me most.

One of the great things about this particular API call is that you don’t need a password for the username you’re requesting. One of the bad things was that you can’t just send a particular timestamp and let Last.fm figure out what week that date falls into.

The latter problem is solved by making another API request to user.getWeeklyChartList. This call provides the specific weekly date ranges in Epoch timestamps you can then use as an input to the user.getWeeklyAlbumChart call. The data looks something like this:

user.getWeeklyChartList
1
2
3
4
5
6
7
8
9
10
11
   "chart": [
      {
          "from": "1108296002",
          "to": "1108900802"
      },
      {
          "from": "1108900801",
          "to": "1109505601"
      },
      
  ]

Two API calls so far to get most of our data. Not bad. So to document our information flow:

  • take the current date
  • subtract n years (1 year ago to start)
  • figure out where that date is within the bounds of the Last.fm weeks
  • request the charts for the username and date range
  • display the data

Pretty simple. Time to dig in a little more.

Features

It’s best to draw a line in the sand about what the constraints of the app will be, at least in the first version. Below are my first set of constraints. They (inevitably) changed later in the project, and we’ll discuss those changes as we make them.

  • We’ll support only one last.fm user.
  • The user can view charts from the week range exactly n years ago (with the lower bound provided by Last.fm).
  • The user cannot view any charts newer than one year ago.
  • The app has a chart view and a settings view (keeping it very simple).

Again, I’ll discuss changes as they happened in the process.

Schema

Next I had to decide how the data would be structured and stored. The initial options were:

Keep everything transient

We should request everything from the network each time. Don’t store anything in Core Data. We’re only looking at old data and we don’t have to worry about realtime feeds so we can use the regular old NSURLCache and set our cache times basically forever (in practice, it’s a little more complicated than this).

(As an aside, I ended up using SDURLCache as a replacement for NSURLCache. From pure observation, NSURLCache was faster from memory, but I could not get it pull from disk between app terminations.)

The weekly charts technically only need to be pulled from the server once a year (I’ll leave that as an exercise to the reader as to why). The chart data for a particular week is only relevant once per year the way the app is set up.

At the end of the day, the URL cache ends up being a dumb key-value store keyed on URLs and returning raw json data.

  • Pros
    • Less complexity keeping data in sync.
  • Cons
    • More data transferred.
    • Longer waiting for previously requested data.

Incrementally build our own database of the data we request

We should set up a full schema of related objects. Data should be requested locally, and if it doesn’t exist, it should be requested from the server and added to the local database.

  • Pros
    • Speed of subsequent requests.
    • Less API requests.
    • May enable future features.
  • Cons
    • Much greater complexity keeping data in sync and knowing when the cache is invalid.
    • More local storage required.

Decision

I was much more inclined to build a local database, but a big goal of this project was a quick ship. I decided to take a few hours building something out with AFIncrementalStore. It didn’t take long to realize doing things this way would slow down development substantially. I decided to keep the local database method as a goal, but leave it until the app idea itself was validated with users. At the current time, it felt like premature optimization.

I went ahead with the two table Core Data schema since I already had it set up and my classes generated with Mogenerator. I added fields for the data I wanted from the user.getWeeklyChartList API call.

Later, I would completely rid Core Data from the project and turn these model objects back into standard NSObject subclasses.

Schema V1
Schema V1

Wireframing

Since I now had an idea of what data I had to work with, it was time to put together wireframes of what the main views would look like.

The aim was simplicity. One main view that shows a single week’s charts. Another modal view for settings and about.

I wanted to go with a more classic look with a standard UINavigationBar. A UITableView as a given based on the nature of the data (unknown number of albums, 0 – ???).

Essential elements of the weekly chart view / Proposed inter-year navigation mechanism
Essential elements of the weekly chart view / Proposed inter-year navigation mechanism

I also needed to show additional metadata including what week and year were being shown, and also what user was being shown. Maybe some sort of table header view?

The next decision was how the user should navigate to past and future years. The first thought was a pull-up-to-refresh-type control to go to the future, and a pull-down to go back. iOS users are pretty used to that control type being for refreshing data, and it probably wouldn’t work with the metadata on a table header view already. Not to mention the amount of scrolling it might take to get to the bottom of the table view.

My solution was to combine the metadata header view and selection into one view/control. I like sliding things, so sliding left and right would request future and past years, respectively. While creating the control later, I also decided that classic arrow buttons would be a good idea so the functionality was immediately discoverable.

SlideSelectView
SlideSelectView

And finally, there was the table view cell itself. I wanted to keep this classic. Album image on the left, artist above album name, artist reemphasized and album name emphasized, and play count on the accessory side. I also left a placeholder for the numbered rank above the album image, but decided it was unnecessary later in the process.

Cell detail
Cell detail

I wanted to get a feel for the settings view too, so I sketched that out. When I was getting my first prototype results back, I realized that if you listen to a lot of mixes, you’ll have row after row of albums with 1 play. I personally wanted the option to only see “full” album listens, and decided to add an option to filter rows by play count.

Settings view wireframe
Settings view wireframe

Development

Finally time to dig into some coding (for real)! Well, almost time…

Setup

Up to this project, I still was git cloning all my external libraries into a lib folder and doing the target settings dance with static libraries. It seemed like CocoaPods was nearly complete on all the popular and semi-popular open source libraries out there. I decided to give it a shot, and it turned out to be mostly a boon to development. I ran into a few snags when I was doing my first archive builds, but I’m of the persuasion now that every iOS project should budget at least a full day to dealing with Xcode quirks.

ReactiveCocoa

I first learned about ReactiveCocoa (RAC) almost a year ago (Maybe March of 2012?) and was immediately interested and immediately confused. ReactiveCocoa is an Objective-C framework for Functional Reactive Programming. I spent a lot of time trying to understand the example projects and following development for the next few months. On a (yet to be released as of this writing) project before this one, I was comfortable enough with a few design patterns to use RAC in selected places throughout that app. Another one of my goals for this app was to try to use RAC wherever I could (and it made sense).

Luckily, RAC hit 1.0 right before I started the project and a substantial amount of documentation was added. For a good, brief overview of the library, I also recommend the NSHipster writeup.

I’ll be explaining my RAC code in some detail later in this post. I want to preface it by saying that I am still very much an RAC newbie and still getting a feel for its patterns, limitations, quirks, etc. Keep that in mind when reading through the code.

Other Libraries

I tried to keep the dependencies lower than normal on this project. I did, however, use a few staples.

  • AFNetworking – the defacto iOS/OS X networking library.
  • SDURLCache – didn’t add this until very close to shipping. I wrestled with NSURLCache during several non-adjacent coding sessions, and eventually decided to replace it with SDURLCache.
  • ViewUtils – there’s a bunch of UIView categories out there. I do a lot of manual view layout, so this library was invaluable.
  • DrawRectBlock – I don’t like cluttering my file list with single-use view classes, so having access to drawRect when creating a new UIView is often helpful. (Especially with different colored top & bottom borders).
  • TestFlightSDK – getting feedback from beta testers has improved my apps and workflow a lot. TestFlight is definitely doing some great work for the iOS community.
  • Crashlytics & Flurry – I spent an entire day before App Store submission trying to figure out the best way to do analytics and crash reports (and how all the libraries fit together). The jury is still out on this, but I have to note that the people at Crashlytics were super helpful and responsive in getting me set up.

TCSLastFMAPIClient

We’re going to start development from the back end (data) and move towards the front (views).

There are some older Last.fm Objective-C clients on GitHub, but it made sense to write my own since I was only using a few of the API endpoints and I wanted to use RAC as much as possible.

Interface

I followed the GitHub client RAC example as a template for my client. The initial interface looked like this:

(TCSLastFMAPIClient.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    #import "AFHTTPClient.h"
  
  @class RACSignal;
  @class WeeklyChart;
  @class WeeklyAlbumChart;
  
  @interface TCSLastFMAPIClient : AFHTTPClient
  
  @property (nonatomic, readonly, copy) NSString *userName;
  
  + (TCSLastFMAPIClient *)clientForUserName:(NSString *)userName;
  
  // returns a single NSArray of WeeklyChart objects
  - (RACSignal *)fetchWeeklyChartList;
  
  // returns a single NSArray of WeeklyAlbumChart objects
  - (RACSignal *)fetchWeeklyAlbumChartForChart:(WeeklyChart *)chart;
  
  @end

A few things to notice:

  • We’re subclassing AFHTTPClient.
  • A client instance is specific to a last.fm user.
  • We can request data from two API endpoints as discussed in the planning section.
  • The fetchWeeklyChartList method only requires a username.
  • the fetchWeeklyAlbumChartForChart: method requires a username and a WeeklyChart object returned by the former method. A WeeklyChart object simply holds an NSDate for from and to.

I want to focus on the RACSignal return types. I say in the comments that the methods “return an NSArray…” when what I mean is that they immediately return an RACSignal. Subscribing to that signal will (usually) result in an NSArray being sent to the subscriber in the sendNext method.

It’s probably not immediately clear why I wouldn’t just return the NSArray, but bear with me.

enqueueRequest

Feel free to read the full implementation, but I’m going to focus on a few methods in particular to explain the RAC parts. I’ll explain this one inline with comments.

(TCSLastFMAPIClient.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 // This is our request assembler/responder. 
  // It sits closest to the network stack in the code we'll write.
  - (RACSignal *)enqueueRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
  
      // An RACSubject is an RACSignal subclass that can 
      // be manually controlled. It's used to bridge the 
      // block callback structure of the AFHTTPRequestOperation to RAC.
      RACReplaySubject *subject = [RACReplaySubject subject];
      
      NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:parameters];
      // Network caching is such a fickle thing in iOS that I don't really rely on this to do anything
      request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
  
      // We assemble our operation callbacks here
      AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
      
        // Last.fm returns error codes in the JSON data,
        // so it makes sense to handle that on this level.
        // Our endpoint methods should only care about good responses.
        NSNumber *errorCode = [responseObject objectForKey:@"error"];
        if (errorCode){
          // Make a new error object with the message we get
          // from the JSON data.
          NSError *error = [NSError errorWithDomain:@"com.twocentstudios.vinylogue" code:[errorCode integerValue] userInfo:@{ NSLocalizedDescriptionKey: [responseObject objectForKey:@"message"] }];
      
          // Subscribers will "subscribeError:" to this signal
          // to receive and handle the error. It also completes the request
          // (subscribeComplete: won't be called).
          [subject sendError:error];
        }else{
          // If last.fm doesn't give us an error, go ahead and send
          // the response object along to the subscriber.
          [subject sendNext:responseObject];
      
          // There's not going to be any more data, so this
          // RACSignal is complete.
          [subject sendCompleted];
        }
      } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
          // A network error is handled similarly to a Last.fm error.
          [subject sendError:error];
      }];
  
      [self enqueueHTTPRequestOperation:operation];
      
      // RAC can be used for threading.
      // In this case, we want our "sendNext:" calls to be
      // processed by the API endpoint functions on a 
      // background thread. That way, the UI doesn't hang
      // while we're moving data from JSON -> new objects.
      return [subject deliverOn:[RACScheduler scheduler]];
  }

So that’s our first taste of RAC in action. RACSubjects are a little different than vanilla RACSignals, but again, they’re very useful in bridging standard Foundation to RAC.

fetchWeeklyChartList

We’re now at the point where an endpoint function can specify a URL and HTTP method and get a response object to process. I’m only going to explain one of the API endpoint functions (the simplest one), but the other ones should be very similar.

(TCSLastFMAPIClient.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 // A public API endpoint function exposed in our interface
  - (RACSignal *)fetchWeeklyChartList{
      
    // First, assemble a parameters dictionary to give to our enqueue method.
    NSDictionary *params = @{@"method": @"user.getweeklychartlist",
                             @"user": self.userName,
                             @"api_key": kTCSLastFMAPIKeyString,
                             @"format": @"json"};
  
    // This is one big method chain we're returning.
    // The first step is to get the proper request signal
    // from the enqueue method.
    return [[[self enqueueRequestWithMethod:@"GET" path:@"" parameters:params]
              
              // Then we hijack the signal's response and run it 
              // through some processing first.
              //
              // The "map:" blocks below are called with any
              // objects sent through the signal's "sendNext" call.
              // In this example, that object happens to be the
              // responseObject, which is an NSDictionary created
              // from a JSON file received from Last.fm.
              // 
              // The first processing we'll do is 
              // pull the object array from its shell.
              // I'm using an NSDictionary category to define a 
              // "arrayForKey" method that always returns an array,
              // even if there's only one object present in the
              // original dictionary.
             map:^id(NSDictionary *responseObject) {
               return [[responseObject objectForKey:@"weeklychartlist"] arrayForKey:@"chart"];
  
             // Next, we're going to iterate through the array
             // and replace dictionaries with WeeklyChart objects.
             // We use the "rac_sequence" method to turn an array
             // into an RACSequence, then use the map function to
             // do the replacement. Finally, we request a standard 
             // array object from the RACSequence object.
             }] map:^id(NSArray *chartList) {
               return [[chartList.rac_sequence map:^id(NSDictionary *chartDictionary) {
                 WeeklyChart *chart = [[WeeklyChart alloc] init];
                 chart.from = [NSDate dateWithTimeIntervalSince1970:[[chartDictionary objectForKey:@"from"] doubleValue]];
                 chart.to = [NSDate dateWithTimeIntervalSince1970:[[chartDictionary objectForKey:@"to"] doubleValue]];
                 return chart;
               }] array];
             }];
  
  }

So it might look a little hairy, but it’s essentially just a few processing steps chained together and all in once place.

We introduced the RACSequence at the end there to iterate through the array. There are more standard NSArray ways to do this, but RACSequences are a subclass of RACStreams, which have a bunch of cool operators like map, flatten, filter, etc.

The main point to get out of this is that our API endpoint method defines several processing steps for a stream, then hands the stream off to its assumed subscriber. At the point the API endpoint method is called, none of this work will actually be done. It’s not until the subscriber has called subscribeNext on the returned signal that the network request and subsequent processing be done. The subscriber doesn’t even have to know that the original signal’s next values are being modified.

That about does it for the API client. To summarize, the data flow is as follows:

  • The client object’s owner (we’ll assume it’s a controller) requests a signal from a public API endpoint method like fetchWeeklyChartList.
  • The API endpoint method asks the enqueue method for a new signal for a specific network request.
  • The enqueue method creates a new manually controlled signal, sets up the signal so that it will send responses when the network call completes, and then passes the signal to the API endpoint method.
  • The API endpoint method sets up a list of processing steps that must be done with responses that are known to be good.
  • The API endpoint passes the signal back to the controller.

At this point the signal is completely set up. It knows exactly where and how to get its data, and how to process the data once it has been received. But it is lazy and will only do so once the controller subscribes to it.

We haven’t shown the act of subscribing yet, and we’ll do that in the next section.

TCSWeeklyAlbumChartViewController

Our primary view controller is called TCSWeeklyAlbumChartViewController. Its function is to request the weekly album charts for a particular user and display them in a table view. It also facilitates moving forward and backward in time to view charts from other years.

It was originally imagined that this would be, for lack of a better term, the root controller of the app. In its original implementation, it had many more responsibilities and had to be more mutable. During the coding process, the app architecture changed, allowing me to assume that the controller would have an immutable user, and simplifying things a bit.

Public Interface

The interface for this controller is pretty simple. Just one designated initializer.

(TCSWeeklyAlbumChartViewController.h)
1
2
3
4
5
 @interface TCSWeeklyAlbumChartViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
  
  - (id)initWithUserName:(NSString *)userName playCountFilter:(NSUInteger)playCountFilter;
  
  @end

Our controller needs to know which user it should display data for. It also needs to know which albums will be filtered based on play count. This controller will also handle all delegate and datasource duties from within. As a side note, I sometimes separate table datasources and delegates out to be their own classes. For this particular controller, things haven’t gotten so complex that I’ve needed to refactor them out.

Private Interface

It’s good OO practice to keep your class variables private by default, and that’s what we’ll do by defining our private interface in the source file. I’ll go through all the class variables we’ve defined.

Views

Let’s start with the views.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
 @interface TCSWeeklyAlbumChartViewController ()
  
  // Views
  @property (nonatomic, strong) TCSSlideSelectView *slideSelectView;
  @property (nonatomic, strong) UITableView *tableView;
  @property (nonatomic, strong) UIView *emptyView;
  @property (nonatomic, strong) UIView *errorView;
  @property (nonatomic, strong) UIImageView *loadingImageView;
  
  ...
  
  @end

The slideSelectView is the special view we use to move to past and future years. It sits on above the tableView and does not scroll.

The tableView displays an album and play count in each row.

The emptyView, errorView, and loadingImageView are used to show state to the user. The empty and error views are added and removed as subviews of the main view when necessary. The loadingImageView is a animated spinning record that is added as a custom view of the right bar button item.

Data
(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 @interface TCSWeeklyAlbumChartViewController ()

  ...
  
  // Datasources
  @property (atomic, copy) NSString *userName;
  @property (nonatomic) NSUInteger playCountFilter;
  @property (atomic, strong) TCSLastFMAPIClient *lastFMClient;
  
  @property (atomic, strong) NSArray *weeklyCharts; // list of to:from: dates we can request charts for
  @property (atomic, strong) NSArray *rawAlbumChartsForWeek; // unfiltered charts
  @property (atomic, strong) NSArray *albumChartsForWeek; // filtered charts to display
  
  @property (atomic, strong) NSCalendar *calendar; // convenience reference
  @property (atomic, strong) NSDate *now;
  @property (atomic, strong) NSDate *displayingDate;
  @property (atomic) NSUInteger displayingYearsAgo;
  @property (atomic, strong) WeeklyChart *displayingWeeklyChart;
  @property (atomic, strong) NSDate *earliestScrobbleDate;
  @property (atomic, strong) NSDate *latestScrobbleDate;
  
  ...
  
  @end

From a general overview, we’re storing all the data we need to display, including some intermediate states. Why keep the intermediate state? We’ll respond to changes in those intermediates and make the chain of events more malleable. Some variables will be observed by views. Some variables will be observed by RAC processing code to produce new values for other variables. As you’ll see in a moment, we can completely separate our view and data code by using intermediate state variables instead of relying on linear processes.

I’ll reprint our data flow from the planning section above adding some detail and variable names. Variables related to the step are in [brackets].

  • take the current date [now]
  • subtract n years (1 year ago to start) [displayingDate & displayingYearsAgo along with a convenience reference to the Gregorian calendar]
  • figure out where that date is within the bounds of the Last.fm weeks [weeklyCharts & displayingWeeklyChart]
  • request the charts for the username and date range [rawAlbumChartsForWeek]
  • filter the charts based on play count and display the data [albumChartsForWeek]
  • We also need to calculate the date bounds we can show charts for [earliestScrobbleDate & latestScrobbleDate]

We store a lastFMClient instance to call on as our source of external data.

Controller state
(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
 @interface TCSWeeklyAlbumChartViewController ()

  ...
  
  // Controller state
  @property (atomic) BOOL canMoveForwardOneYear;
  @property (atomic) BOOL canMoveBackOneYear;
  @property (atomic) BOOL showingError;
  @property (atomic) NSString *showingErrorMessage;
  @property (atomic) BOOL showingEmpty;
  @property (atomic) BOOL showingLoading;
  
  @end

We have some additional controller state variables set up. I have to admit that I’m not sure my implementation of empty/error views is the best. There was plenty of experimentation, and I ran into some trouble with threading. It works, but will eventually require a refactor.

canMoveForward/BackOneYear depend on which year the user is currently viewing as well as the earliest/latestScrobbleDate. The slideSelectView knows what it should allow based on these bools. Any of our data processes can decide they want to show an error, empty, or loading state. Other RAC processes observe these variables and show the appropriate views. (Again, this took some tweaking and is a little fragile.)

loadView

I’ll annotate loadView inline:

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 - (void)loadView{
      // Create a blank root view
      self.view = [[UIView alloc] init];
      self.view.autoresizesSubviews = YES;
      
      // Begin creating the view hierarchy
      [self.view addSubview:self.slideSelectView];
      self.tableView.delegate = self;
      self.tableView.dataSource = self;
      [self.view addSubview:self.tableView];
      
      // loading view is shown as bar button item
      UIBarButtonItem *loadingItem = [[UIBarButtonItem alloc] initWithCustomView:self.loadingImageView];
      self.loadingImageView.hidden = YES;
      self.navigationItem.rightBarButtonItem = loadingItem;
      
      // double tap on the slide view to hide the nav bar and status bar
      UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doDoubleTap:)];
      doubleTap.numberOfTapsRequired = 2;
      [self.slideSelectView.frontView addGestureRecognizer:doubleTap];
  }

All view attributes are defined in the view getters section. I’ve taken up this habit to keep my controllers a bit more tidy. The views are created at the first self.[viewname] call in loadView.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 // This custom view does most of its own configuration
  - (TCSSlideSelectView *)slideSelectView{
    if (!_slideSelectView){
      _slideSelectView = [[TCSSlideSelectView alloc] init];
    }
    return _slideSelectView;
  }
  
  // The tableview is also pretty vanilla. I'm using a custom
  // inner shadow view (although it's still not quite perfect).
  - (UITableView *)tableView{
    if (!_tableView){
      _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
      _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
      _tableView.backgroundView = [[TCSInnerShadowView alloc] initWithColor:WHITE_SUBTLE shadowColor:GRAYCOLOR(210) shadowRadius:3.0f];
    }
    return _tableView;
  }
  
  // Spinning record animation. It uses 12 images in a standard UIImageView.
  - (UIImageView *)loadingImageView{
    if (!_loadingImageView){
      _loadingImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
      NSMutableArray *animationImages = [NSMutableArray arrayWithCapacity:12];
      for (int i = 1; i < 13; i++){
        [animationImages addObject:[UIImage imageNamed:[NSString stringWithFormat:@"loading%02i", i]]];
      }
      [_loadingImageView setAnimationImages:animationImages];
      _loadingImageView.animationDuration = 0.5f; // trial and error
      _loadingImageView.animationRepeatCount = 0; // repeat forever
    }
    return _loadingImageView;
  }

viewDidLoad & RAC setup

Now for the fun stuff. The majority of the controller’s functionality is set up in viewDidLoad within two helper methods, setUpViewSignals and setUpDataSignals.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
 - (void)viewDidLoad{
    [super viewDidLoad];
  
    [self setUpViewSignals];
    [self setUpDataSignals];
  
    // these assignments trigger the controller to begin its actions
    self.now = [NSDate date];
    self.displayingYearsAgo = 1;
  }

I’m going to start with the view signals to stress that as long as we know the exact meaning of our state variables, we can set up our views to react to them without knowing when or where they will be changed.

But before we can read through the code, we’ll need a quick primer on some more bread-and-butter RAC methods.

@weakify & @strongify

Because RAC is heavily block-based, we can use the EXTScope preprocessor definitions within the companion libextobjc library to save our sanity when passing variables into blocks. Simply throw a @weakify(my_variable) before your RAC blocks to avoid the retain cycles, and then @strongify(my_variable) within each block to ensure the variable is not released during the block’s execution. See the definitions here.

RACAble(…) & RACAbleWithStart(…)

RACAble is simply magic Key Value Observing. From the documentation:

[RACAble] returns a signal which sends a value every time the value at the given path changes, and sends completed if self is deallocated.

This will be the backbone of our RAC code and is probably the easiest place to get started with RAC. It’s easy to pepper these signals into existing code bases without having to rewrite from scratch. Or just use a few in a project to get started.

setUpViewSignals

Let’s dive right into our first view signal to get a feel for it. I’ve separated it out into several expressions in order to simplify the explanation. In the actual source, it’s a single expression.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 // Subscribing to all the signals that deal with views and UI
  - (void)setUpViewSignals{
    @weakify(self);
  
    // SlideSelectView: Top Label
    // Depends on: userName
    RACSignal *userNameSignal = RACAbleWithStart(self.userName);
  
    // View changes must happen on the main thread
    [userNameSignal deliverOn:[RACScheduler mainThreadScheduler]];
  
    // Change views based on the value of userName
    [userNameSignal subscribeNext:^(NSString *userName) {
      @strongify(self);
      if (userName){
        self.slideSelectView.topLabel.text = [NSString stringWithFormat:@"%@", userName];
        self.showingError = NO;
      }else{
        self.slideSelectView.topLabel.text = @"No last.fm user";
        self.showingErrorMessage = @"No last.fm user!";
        self.showingError = YES;
      }
      [self.slideSelectView setNeedsLayout];
    }];
  
    
  
  }

Let’s deconstruct this. RACAbleWithStart(self.userName) creates an RACSignal. Again, an RACSignal can send next (with a value) and error or complete messages to its subscribers.

The WithStart part sends the current value of self.userName when it subscribed to. Without this, the block in subscribeNext would not be executed until self.userName changes for the first time after this subscription is created. In our case, because self.userName is only set once in the controller’s init method (before the signal is created), it would never be called with a normal RACAble.

(At this point you may be wondering, “Why even observe the userName property if it’s guaranteed to never change within the controller?” That’s a good question. It’s partly vestigial from when the value could change. It would very much be possible to transplant the code from within the subscribeNext block to viewDidLoad, but as you’ll see it fits pretty well with the rest of the more dynamic view code.)

The next statement, deliverOn:, modifies transforms the signal into a new RACSignal on the specified thread to ensure next values are delivered on the main thread. This is a necessary because UIKit requires user interface changes to be done on the main thread. Like all other signal transformations, it only takes effect if the result is used.

subscribeNext is where our reaction code goes. Basically saying, “When the value of self.userName changes, execute the following block with the new value.” In this example, we’re going to change a label to show the userName. If it’s nil, we’ll throw up the error view.

I could have also used another variation of the subscribeNext method like subscribeNext:complete: to also add a block to execute when the signal completes. We don’t really need to do anything on completion, so we’ll just add a block for next:.

Instead of the if/else, we could have used two separate subscriptions that first filter for nil or empty userName. To keep it simple though, we’ll just mix in the iterative style for now.

Alright, so one signal down. Let’s look at another common pattern: combining multiple signals.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 - (void)setUpViewSignals{
      @weakify(self);
  
      
  
      // SlideSelectView: Bottom Label, Left Label, Right Label
      // Depend on: displayingDate, earliestScrobbleDate, latestScrobbleDate
      RACSignal *combinedSignal = [RACSignal combineLatest:@[ RACAbleWithStart(self.displayingDate), RACAbleWithStart(self.earliestScrobbleDate), RACAbleWithStart(self.latestScrobbleDate)] ];
      
      // Do it on the main thread
      [combinedSignal deliverOn:[RACScheduler mainThreadScheduler]];
      
      // Actions to perform when changing any of the three signals
      [combinedSignal subscribeNext:^(RACTuple *dates) {
           NSDate *displayingDate = dates.first;
           NSDate *earliestScrobbleDate = dates.second;
           NSDate *latestScrobbleDate = dates.third;
          @strongify(self);
          if (displayingDate){
            // Set the displaying date label
      
            // Calculate and set canMoveBackOneYear and canMoveForwardOneYear
      
            // Only show the left and right labels/arrows 
            // if there's data there to jump to.
      
          }else{
            // There's no displaying date, so set all labels to nil
          }
      
       }];
      
      
      
  }

I’ve separated out the expressions again, and I’ve replaced a bunch of tedious date calculation code and view updating code with comments in order to focus on what’s happening with RAC. You can see everything in the source.

The slideSelectView has a couple components that depend on displayingDate, earliestScrobbleDate, and latestScrobbleDate. I want to update this view when any of these values change. Luckily, there’s an RACSignal constructor for this.

[RACSignal combineLatest:] allows you to combine several signals into one signal. The new signal sends a next: message any time one of its component signals sends next:. There are a few variations of combineLatest: but the one we’ll use in this example will combine all the latest next: values of our three component signals into a single RACTuple object. If you haven’t heard of a tuple before, you can think of it like an array for now.

combineLatest takes an array of signals, which we’ll generate on-the-fly with RACAbleWithStart.

When we subscribe, we expect a single RACTuple object to be delivered with the latest values of our component signals in the order we placed them in the original array. We can use the RACTuple helper methods .first, .second, etc. to break out the values we need.

Within the block, we calculate some booleans and set some labels. This could be broken up into multiple methods, but in this case, it made the most sense to do these calculations and assignments in the same place because they depend on the same set of signals.

Let’s do one more view-related signal to show how primitive values are handled.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 - (void)setUpViewSignals{
      @weakify(self);
  
      
  
      // Show or hide the loading view
      [[[RACAble(self.showingLoading) distinctUntilChanged]
       deliverOn:[RACScheduler mainThreadScheduler]]
       subscribeNext:^(NSNumber *showingLoading) {
        @strongify(self);
        BOOL isShowingLoading = [showingLoading boolValue];
        if (isShowingLoading){
          [self.loadingImageView startAnimating];
          self.loadingImageView.hidden = NO;
        }else{
          [self.loadingImageView stopAnimating];
          self.loadingImageView.hidden = YES;
        }
      }];
      
      
      
  }

Here we’re observing the showingLoading state variable. This variable will presumably be set by the data subscribers when they’re about to do something with the network or process data.

This time I left the signal creation, modification, and subscription all in one call.

RACAble(self.showingLoading) creates the signal. distinctUntilChanged modifies the signal to only send a next: value to subscribers when that value is different from the last one. For example, let’s assume showingLoading is YES. If somewhere in our controller, a method sets showingLoading to NO, then it is also set to NO later, the subscribeNext: block will only be executed on the first change from YES to NO.

We’ve seen deliverOn: a few times now. No surprises there.

Now for the subscription. You can see that the next: value is delivered as an NSNumber object even though self.showingLoading is a primitive BOOL. RAC will wrap primitives and structs in objects for us. So before we compare against the value, I’ll use [showingLoading boolValue] to get the primitive back.

You can check out the rest of the view signals and subscriptions in the source.

setUpDataSignals

We’ll introduce a couple new RAC concepts in this method. But first, here’s an ugly ascii variable dependency graph. We’ll use this to set up our signals.

    userName            now         displayingYearsAgo
        |                \              /
    lastFMClient          displayingDate
        |                       /
    weeklyCharts              /
        \                   /
        displayingWeeklyChart
                |
        rawAlbumChartsForWeek
                |
        albumChartsForWeek
                |
        [tableView reloadData]

When any of these variables change, they trigger a change upstream (downstream?) to the variables that depend on them.

For example, if the user changes displayingDate (moving to a past year), a change will be triggered in displayingWeeklyChart, which will trigger a change in rawAlbumChartsForWeek and so on. If our data was more time sensitive (by-the-minute instead of by-the-week), we could set up a signal for now that would trigger a change down the line every minute. Or if we decided to reuse our controller with different users, a userName change would trigger a change down the line.

We’ll start with the userName observer.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // All the signals that deal with acquiring and reacting to data changes
  - (void)setUpDataSignals{
  
    @weakify(self);
  
    // Setting the username triggers loading of the lastFMClient
    [[RACAbleWithStart(self.userName) filter:^BOOL(id x) {
      return (x != nil);
    }] subscribeNext:^(NSString *userName) {
      NSLog(@"Loading client for %@...", userName);
      @strongify(self);
      self.lastFMClient = [TCSLastFMAPIClient clientForUserName:userName];
    }];
  
    
  
  }

We’ve seen the RACAbleWithStart pattern. We’re going to use filter: to act only on non-nil values of userName. Filter takes a block with an argument of the same type as is sent in next:. In this case, we don’t need to cast it directly, we just know it shouldn’t be nil. filter’s block returns a BOOL that indicates whether it should pass the next value to subscribers. Returning YES passes the value. Returning NO blocks it and the subscribeNext block will never see it. filter is a operation defined by RACStream like map which we saw earlier.

Next is another RAC pattern. You can automatically assign a property to the latest next value sent by a signal by using RAC(my_property) = my_signal. There are actually a couple other ways to accomplish this too. Here’s an example from Vinylogue.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 - (void)setUpDataSignals{
  
    @weakify(self);
  
    
  
    // Update the date being displayed based on the current date/time and how many years ago we want to go back
    RAC(self.displayingDate) = [[[[RACSignal combineLatest:@[ RACAble(self.now), RACAble(self.displayingYearsAgo) ]]
      deliverOn:[RACScheduler scheduler]]
      map:^(RACTuple *t){
        NSDate *now = t.first;
        NSNumber *displayingYearsAgo = t.second;
        NSLog(@"Calculating time range for %@ year(s) ago...", displayingYearsAgo);
        NSDateComponents *components = [[NSDateComponents alloc] init];
        components.year = -1*[displayingYearsAgo integerValue];
        return [self.calendar dateByAddingComponents:components toDate:now options:0];
      }] filter:^BOOL(id x) {
        NSLog(@"Time range calculated");
        return (x != nil);
      }];
  
    
  
  }

This one is a little tricky so let’s step through it. First thing we’re doing is setting up the RAC(property) assignment. self.displayingDate will be assigned to whatever next value comes out of our complicated signal on the other side of the equals sign.

We’re creating a signal that combines our self.now and self.displayingYearsAgo signalified properties. Remember, they’re not just regular properties. We’ve made them into signals by wrapping them with RACAble, and they send their values each time they’re changed.

We’re injecting a deliverOn with the default background scheduler [RACScheduler scheduler] before doing any work on the next values to make sure the work will be done off the main thread (ensuring a snappy UI).

Edit: Doing this on a background thread is probably overkill and most likely not the best idea since we’re now changing controller properties off the main thread.

Remember that combineLatest creates an RACTuple with the next values of each signal it wraps. We break that tuple out into an NSDate and an NSNumber and use those to calculate a date in the past. Remember that map just modifies next values. It has to return a value to pass to subscribers. It doesn’t have to be the same type; our input is an RACTuple and our output is an NSDate in this case.

Our last operation is a filter for nil. It’s useless to assign nil to our displayingDate. If this returns YES, then our next date returned from map will automatically be assigned to self.displayingDate.

We’ll do one expression in order to show how we use the Last.fm client functions we wrote earlier.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 - (void)setUpDataSignals{
  
    @weakify(self);
  
    
  
   // When the weeklychart changes (being loaded the first time, 
   // or the display date changed), fetch the list of albums for that time period.
   [[[RACAble(self.displayingWeeklyChart) filter:^BOOL(id x) {
     return (x != nil);
   }] deliverOn:[RACScheduler scheduler]]
    subscribeNext:^(WeeklyChart *displayingWeeklyChart) {
      NSLog(@"Loading album charts for the selected week...");
      @strongify(self);
      [[[self.lastFMClient fetchWeeklyAlbumChartForChart:displayingWeeklyChart]
        deliverOn:[RACScheduler scheduler]]
       subscribeNext:^(NSArray *albumChartsForWeek) {
         NSLog(@"Copying raw weekly charts...");
         @strongify(self);
         self.rawAlbumChartsForWeek = albumChartsForWeek;
       } error:^(NSError *error) {
         @strongify(self);
         self.albumChartsForWeek = nil;
         NSLog(@"There was an error fetching the weekly album charts!");
         self.showingErrorMessage = error.localizedDescription;
         self.showingError = YES;
       }];
    }];
  
    

  }

Once we have a specific time period to request charts for, we’ll get a signal from the client by supplying the displayingWeeklyChart. We have a signal within a signal in this block. As soon as we subscribeNext to the signal that was returned from the client, it will request data from the network and do the processing.

We also subscribed with an error block this time so we can pass the error along to the user. By setting showingError and showingErrorMessage, the view signal subscriptions we created earlier are triggered. Remember that in this subscription, we’re still on a background thread. Changing these properties on a background thread will still trigger the view updates on the main thread. Pretty cool.

The rest of our setUpDataSignals method uses similar tricks with RAC, observing properties as outlined in our simple ascii chart. Check out the source to decipher the rest.

You should be at the point now where you can start picking through the signal operations in RACSignal+Operations.h. RACStream.h also has some operations you should be aware of.

In the next section we’ll also briefly cover RACCommands.

TCSSlideSelectView

The slideSelectView is the extra special control we dreamed up earlier in the wireframing section.

Reminder of slideSelectView
Reminder of slideSelectView

We should break it out so we know how we should code the view hierarchy.

slideSelectView broken out
slideSelectView broken out

Let’s implement it!

Interface

From our sketch, we can deconstruct the views we need.

(TCSSlideSelectView.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 @interface TCSSlideSelectView : UIView <UIScrollViewDelegate>
  
  @property (nonatomic, readonly) UIView *backView;
  @property (nonatomic, readonly) UIButton *backLeftButton;
  @property (nonatomic, readonly) UIButton *backRightButton;
  @property (nonatomic, readonly) UILabel *backLeftLabel;
  @property (nonatomic, readonly) UILabel *backRightLabel;
  
  @property (nonatomic, readonly) UIScrollView *scrollView;
  
  @property (nonatomic, readonly) UIView *frontView;
  @property (nonatomic, readonly) UILabel *topLabel;
  @property (nonatomic, readonly) UILabel *bottomLabel;
  
  // Signals will be fired when the scroll view is dragged past the offset
  @property (nonatomic) CGFloat pullLeftOffset;
  @property (nonatomic) CGFloat pullRightOffset;
  
  @property (nonatomic, strong) RACCommand *pullLeftCommand;
  @property (nonatomic, strong) RACCommand *pullRightCommand;
  
  @end

We’ll decide up front that this view will be somewhere between a generic and concrete view. A good future exercise would be figuring out how to make this view generic enough to be used by other controllers and apps. We’ve made it sort of generic by exposing all the subviews as readonly, therefore allowing other objects to change view colors and other properties, but not allowing them to replace views with their own.

We’ll actually redefine these view properties in the private interface in the implementation file.

Although our view will have defaults for the pull offsets and commands, we’ll allow outside classes to change or replace them at will.

The pull offsets are values that answer the question, “How far do I have to pull the scroll view to the right or left before an action is triggered?” The commands are RACSignal subclasses that are designed to send next values triggered off of an action, usually from the UI. We’ll use these constructs instead of the delegate pattern to pass messages from our custom view to the controller that owns it.

Implementation

Here’s the private interface:

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
 @interface TCSSlideSelectView ()
  
  @property (nonatomic, strong) UIView *backView;
  // redefine the rest of the views as strong instead of readonly
  // ...
  
  @end

We’ll define the view hierarchy and create defaults in init.

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 - (id)init{
    self = [super initWithFrame:CGRectZero];
    if (self) {
      [self addSubview:self.backView];
  
      [self.backView addSubview:self.backLeftLabel];
      [self.backView addSubview:self.backRightLabel];
  
      [self.backView addSubview:self.scrollView];
  
      // Buttons technically sit above (but not on) 
      // the scrollview in order to intercept touches
      [self.backView addSubview:self.backLeftButton];
      [self.backView addSubview:self.backRightButton];
  
      [self.scrollView addSubview:self.frontView];
      [self.frontView addSubview:self.topLabel];
      [self.frontView addSubview:self.bottomLabel];
  
      // Set up commands
      self.pullLeftOffset = 40;
      self.pullRightOffset = 40;
      self.pullLeftCommand = [RACCommand command];
      self.pullRightCommand = [RACCommand command];
    }
    return self;
  }

Logically, the buttons would be behind the scrollView, but I was having trouble getting taps to get forwarded from the invisible scrollview to the button below it (maybe I should have just made the scrollView narrower?). Instead, the buttons sit above the scrollview and disappear when scrolling begins.

We also define defaults for the pull offsets and set up generic RACCommands.

I use layoutSubviews to resize the views and lay them out. This is mostly self explanatory and tedious to explain. Feel free to read the source to see how I do that.

We’ll move on to the more interesting part: using RACCommands to pass messages.

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    # pragma mark - UIScrollViewDelegate
  
  - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    [self showBackButtons:NO];
  }
  
  - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    CGFloat offset = scrollView.contentOffset.x;
    if (offset <= -self.pullLeftOffset){
      [self.pullLeftCommand execute:nil];
    }else if(offset >= self.pullRightOffset){
      [self.pullRightCommand execute:nil];
    }
    [self showBackButtons:YES];
  }

Like I just mentioned before, we’ll hide the buttons when scrolling starts and show them again when scrolling ends.

When scrolling ends, we check the x offset of the scrollview. If it’s past the offsets that were set earlier, we use the execute method of the RACCommand. An RACCommand is just a subclass of an RACSignal with a few behavior modifications. execute: sends its argument in a next message to subscribers. In our example, we don’t need to send any particular object, just the message is enough. We could have alternatively only had one command object and sent the direction as the message object. That’s a little confusing though.

This design pattern works for a few reasons. If the command has no subscribers, the message will be ignored, no harm done. So far though, it isn’t really that much different than creating a protocol and holding a reference to a delegate. I’ll show you the interesting part in a second.

Before we move back to the controller to show how we handle these messages, here’s how we handle the buttons:

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
    # pragma mark - private
  
  - (void)doLeftButton:(UIButton *)button{
    [self.pullLeftCommand execute:nil];
  }
  
  - (void)doRightButton:(UIButton *)button{
    [self.pullRightCommand execute:nil];
  }

The buttons trigger the same action as the scrollView.

Another way to interface UIControls with RAC is to use the RACSignalSupport category:

(UIControl+RACSignalSupport.h)
1
2
3
4
5
6
7
 @interface UIControl (RACSignalSupport)
  
  // Creates and returns a signal that sends the sender of the control event
  // whenever one of the control events is triggered.
  - (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents;
  
  @end

Let’s quickly jump back to the TCSWeeklyAlbumChartViewController to show how we interface with this custom control.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 - (void)setUpDataSignals{

    
  
    // Change displayed year by sliding the slideSelectView left or right
    self.slideSelectView.pullLeftCommand = [RACCommand commandWithCanExecuteSignal:RACAble(self.canMoveBackOneYear)];
    [self.slideSelectView.pullLeftCommand subscribeNext:^(id x) {
      @strongify(self);
      self.displayingYearsAgo += 1;
    }];
    self.slideSelectView.pullRightCommand = [RACCommand commandWithCanExecuteSignal:RACAble(self.canMoveForwardOneYear)];
    [self.slideSelectView.pullRightCommand subscribeNext:^(id x) {
      @strongify(self);
      self.displayingYearsAgo -= 1;
    }];
  
    
  
  }

We’re creating signals (commands) and assigning them to the slideSelectView. The slideSelectView will own these signals, but before the controller hands them over, it will add a special “canExecuteSignal” and then subscription instructions for each.

The command will check the latest value of its canExecute signal (which should be a BOOL) to decide whether it should fire before executing its next block. In the example, we don’t want to let the user move to the past unless there are weeks to show there. We create a signal from our BOOL property canMoveBackOneYear and assign it to the command. Our canMove properties will now govern the actions of the slideSelectView for us.

When these commands are allowed to fire, they’ll update our displayingYearsAgo property, and the changes will propagate through our ascii dependency chart.

That’s about it for the slideSelectView. Now that we have the skeleton of our app created, time to start thinking about the design.

Design

Alright, so we’ve now done a fair amount of development. There’s at least enough for a prototype using built in controls. Now it’s time to get a feel for how everything is going to look.

This is approximately what our app looks like at this point:

Ugly working prototype
Ugly working prototype

I am not a designer. I usually work with my friend CJ from Oltman Design, but since this was a low-stakes, unpaid project with a tight-timeline, I decided to give it a shot myself.

Many shops will complete the wireframes, UX, and then do the Photoshop mockups before even involving the developers. Since I’m doing everything, I sometimes pivot back and forth between the two, using new ideas while doing each to iterate in both directions.

You can also see that from my ugly prototype screen shot that I like to multicolor my views initially to make sure everything is being laid out as expected.

Photoshop

I took my ugly mockup and threw it into photoshop, then styled on top of it. I wanted to start with a more colorful mockup and used this color picker to pick out a bunch of colors I liked (again, not a designer!).

Here is my first mock up:

First mock up
First mock up

I chose iOS6’s new Avenir Next font in a few weights because it’s pretty, a little more unique, but still very readable and not too opinionated.

As a non-designer, I simply asked myself what the relative order of importance for each element was, and adjusted its relative color and size accordingly. The artist name is not as important as the album name, therefore the artist name is smaller and has less contrast. The album cover is important, so it is nice and big and the first thing you see on the left side (also a standard iOS design pattern). I added padding to the album images so they wouldn’t bump up against each other on the top and bottom, which sometimes looks weird. The number of plays is big and stands out, while the word “plays” can be inferred after seeing it the first time, and can therefore be downplayed heavily. I gave the cells a classic one-line top highlight and bottom shadow.

For my slide control, I wanted to give it depth to imply it being stacked. A darker color and inner shadow accomplished this (although I’ve found Photoshop-style inner shadows much harder to implement with Core Animation and Core Graphics). The user name is mostly understood and is only there as a reminder, so it can be downplayed. The week is important so it should stand out.

I was originally planning on this being the only controller, so I put the logo top and center. The logo is just our normal Avenir Next font with a little extra tracking (not a designer). It looks just hipstery enough I think.

Code the cell design

Now that there’s a map for our cell layout, let’s implement it.

Interface

(TCSAlbumArtistPlayCountCell.h)
1
2
3
4
5
6
7
8
9
 @interface TCSAlbumArtistPlayCountCell : UITableViewCell
  
  @property (nonatomic, strong) WeeklyAlbumChart *object;
  
  - (void)refreshImage;
  
  + (CGFloat)heightForObject:(id)object atIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView;
  
  @end

We’ll keep a reference to the model object we’re displaying. There’s some debate about the best way to implement the model/view relationship. Sometimes I borrow the concept of a “decorator” object from Rails that owns the model object and transforms its values into something displayable. I decided to keep this app simple this time and have the cell assign its own view attributes directly from the model object. If we didn’t have a one-to-one relationship between views and model objects, I would definitely reconsider this.

Ignore refreshImage for now. That tackles a problem we’ll run into later.

heightForObject is our class object that does a height calculation for an object before an instance of the cell is actually allocated. Part of the UITableView protocols is knowing the height of a cell before it is actually laid out. I have yet to figure out a good way to do this without doubling up on some layout calculation code, but alas I’ve gotten used to writing it.

Implementation

Our private instance variables:

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
 @interface TCSAlbumArtistPlayCountCell ()
  
  @property (nonatomic, strong) UILabel *playCountLabel;
  @property (nonatomic, strong) UILabel *playCountTitleLabel;
  @property (nonatomic, strong) UILabel *rankLabel;
  @property (nonatomic, strong) UIView *backView;
  
  @property (nonatomic, strong) NSString *imageURLCache;
  
  @end

I’m reusing UITableViewCell’s textLabel and detailTextLabel for my artist and album labels, and reusing the imageView for the album image. Our cells aren’t currently selectable, so the cell only has a normal background view and not a selected one.

I’ll come back to that imageURLCache string in a moment.

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 @implementation TCSAlbumArtistPlayCountCell
  
  - (id)init{
    self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:NSStringFromClass([self class])];
    if (self) {
      self.selectionStyle = UITableViewCellSelectionStyleNone;
  
      self.backgroundView = self.backView;
  
      [self configureTextLabel];
      [self configureDetailTextLabel];
      [self configureImageView];
      [self.contentView addSubview:self.playCountLabel];
      [self.contentView addSubview:self.playCountTitleLabel];
      [self.contentView addSubview:self.rankLabel];
  
    }
    return self;
  }
  
  
  
  @end

I create and/or configure my views in custom getters at the bottom of my implementation file. Nothing too interesting here. Moving on…

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 - (void)setObject:(WeeklyAlbumChart *)object {
    if (_object == object)
      return;
  
    _object = object;
    self.textLabel.text = [object.artistName uppercaseString];
    self.detailTextLabel.text = object.albumName;
    self.playCountLabel.text = [object.playcount stringValue];
    self.rankLabel.text = [object.rank stringValue];
  
    [self refreshImage];
  
    if (object.playcountValue == 1){
      self.playCountTitleLabel.text = NSLocalizedString(@"play", nil);
    }else{
      self.playCountTitleLabel.text = NSLocalizedString(@"plays", nil);
    }
  }

Our custom object setter is in charge of properly assigning model object data to our views. The interesting problem we come across is the albumImage. Let’s look at the refreshImage instance method:

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
 - (void)refreshImage{
    UIImage *placeHolderImage = [UIImage imageNamed:placeholderImage];
    if (self.imageView.image == nil){
      self.imageView.image = placeHolderImage;
    }
  
    if(self.object.albumImageURL && ![self.object.albumImageURL isEqualToString:self.imageURLCache]){
      // prevent setting imageView unnecessarily
      [self.imageView setImageWithURL:[NSURL URLWithString:self.object.albumImageURL] placeholderImage:placeHolderImage];
      self.imageURLCache = self.object.albumImageURL;
    }
  }

What do we know about the image at this point? When our WeeklyAlbumChart object is originally created, it does not have an album image URLl The Last.fm API does not return that data with the call we’re using. If we want that image URL, we have to request it using a separate album.getInfo API call. And it may not even exist for a particular album.

But getting that URL isn’t the cell’s responsibility. We don’t want to create or pass in a copy of the lastFMAPIClient to each cell. That seems like the controller/datasource’s responsibility.

Why don’t we just request all these image URLs when we originally receive the album chart list from the API client? We could, but if that list has 50+ albums in it, that’s 51+ API calls we just made. And if the user only scrolls through a couple, it’s a lot of wasted data. We should strive to do it more lazily. Only request the album image URL if the cell is actually being displayed. Luckily, we have a nice table view delegate method for that.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
 - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    // If the object doesn't have an album URL yet, request it from the server then refresh the cell
    TCSAlbumArtistPlayCountCell *albumCell = (TCSAlbumArtistPlayCountCell *)cell;
    WeeklyAlbumChart *albumChart = [self.albumChartsForWeek objectAtIndex:indexPath.row];
    if (albumChart.albumImageURL == nil) {
      [[[self.lastFMClient fetchImageURLForWeeklyAlbumChart:albumChart] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSString *albumImageURL) {
        [albumCell refreshImage];
      }];
    }
  }

In the controller, if the model object does not have an albumImageURL we request it using a new API client method (that returns an RACSignal of course). We subscribe, so when it’s done we can refresh the cell and load the new image using AFNetworking’s UIImageView category that loads images asynchronously from a URL.

While we’re waiting for our response, the user could possibly have scrolled past the cell, and the cell could be reassigned a new object. No problems though, because refreshImage will just fall through without doing anything and the URL will be saved and loaded the next time the object is assigned to the cell.

The last thing we should do is clear out our imageURLCache in prepareForReuse, although technically everything will still work without doing so.

All of this work seems like exactly the thing that RAC would excel at. Unfortunately, I wrestled with several such implementations but could not get things to work out as I wanted. On my to-do list is to try again and see if something unrelated was the problem.

I was considering digging into the view layout calculations, but I think as was with the slideSelectView, explaining the layout code would be tedious and superfluous. Check it out in the source if you’re interested.

Photoshop part 2

We coded up our cell layout and did the same for the slideSelectView and took a step back to see how it looks on the iPhone.

Coded layouts
Coded layouts

Everything seems to be in the right place. Let’s go ahead and adjust fonts and colors, then tweak the layout to get things as close to our mock up as possible.

Set fonts and colors
Set fonts and colors

Cool. Looking pretty close to what we had in photoshop. We haven’t gotten the inner shadows of the slideSelectView yet though. And the sharp rectangle of the slideSelectView’s top view doesn’t look particularly draggable. Let’s round off the corners. We’ll also add a button for opening up the settings (user name and play count, along with about info).

Some tweaks
Some tweaks

Settings

Next we need to create a settings controller and a controller for setting the user name.

I’m not going to go in depth for this one. I used a generic static table view datasource class I threw together for another project. You basically give it a specially coded dictionary with titles and optional selectors, then tell it what cell classes to use for headers and cells.

I didn’t do this one in photoshop first. Since it’s only one line of text per line, I simply coded it up and experimented a little with the fonts and colors I already had chosen.

First settings controller
First settings controller

I kept this theme going with the username input controller. I heavily borrowed the styling of the awesome and beautiful app EyeDrop.me.

User name edit controller
User name edit controller

Iteration

We now have a functional app! But now we can take a step back and really look at what’s going on.

  1. The color scheme of the root controller is too much. The colors of the album art should draw the most focus not the background of the table cells nor the play count.
  2. The flow of the settings page kind of works, but it feels a little odd.
  3. My original thought was that this would be a one-user app, but during testing, I realized that:
    • We don’t need to input a user’s password to view their charts.
    • During testing I was viewing my friends’ charts and enjoying perusing them.
    • Why not just having a landing page controller where we can easily select charts for different users!?

It seems obvious in hindsight, but at the time it was anything but.

So let’s address each of these points.

  1. I’m kind of liking the settings page. It was sort of an accident, but it’s minimalist, which is easier to pull off for someone with no sense of design. It’s also the way design trends have been going lately. Let’s aim for that on the root controller.
  2. We can probably address this along with #3.
  3. The root controller should be a list of users.
    • Selecting a user pushes their chart on the nav controller.
    • Settings can be viewed and changed from the root controller.
Re-imagined wireframed controller flow
Re-imagined wireframed controller flow

Awesome! A little more complicated, but overall a much more functional app.

Photoshop part 3

Luckily all the layout is still winning our favor. All we have to do is tweak fonts and colors.

Photoshop comp 2
Photoshop comp 2

That looks a lot cleaner. The slideSelectView looks a little weird, but we’ll play around with those colors on the device/simulator.

Design implementation part 2

Redesign on the simulator
Redesign on the simulator

Yeah, much better.

Let’s check out the new users controller.

Users controller
Users controller

Looks good. UIKit automatically adds those table header view bottom borders and I can’t reliably get rid of them. Weird.

Because we’ve moved the primary user name out of settings, here’s the settings controller one more time.

Settings controller with no user name
Settings controller with no user name

Back to development

Alright, we totally skipped the implementation of the users controller. Let’s touch on that and see if RAC has anything to do with it.

Data store

We have a new decision to make: how will we set/store users? We have a few options:

  1. The user adds their friends’ user names manually.
    • Pros: simplest, may be what most users want.
    • Cons: may be difficult for users to find/enter their friends’ usernames.
  2. Automatically sync friends with last.fm.
    • Pros: users also might find this easiest.
    • Cons: may be difficult to keep the user’s personal ordering.
  3. Manually sync friends with last.fm on user request.
    • Pros: probably a good compromise as a “first run” option or default.
    • Cons: users may get upset if they sync again and everything is overwritten.

We should probably start with option 1. Later we can add option 3 if the interest is there. Keep an ear open for option 2.

(Update: V1.1 has friend list importing, which solves these problems by only importing friends not already in the list).

The easiest way to implement option 1 is NSUserDefaults. We should probably wrap it in its own datastore class in order to abstract out the implementation details and make option 3 easier to add in the future.

Our new app start up sequence is the following pseudo code in our AppDelegate:

  1. Pull the play count filter setting from NSUserDefaults.
  2. Create a user data store class instance that will pull user data from NSUserDefaults.
  3. Create a UsersController and pass in our userStore and playCountFilter.

Why not have each controller interact with NSUserDefaults as needed? I consider NSUserDefaults a store for global variables. Doing all global IO in one place (the AppDelegate) is better separation of concerns.

At the end of the day it’s a judgement call, and with an app this size there’s only so much anti-pattern spagettification you can even create. With larger apps, it’s a better idea to keep your controllers as functional (in the functional programming sense) as possible.

Moving data around

TCSFavoriteUsersController has a by-the-book table view delegate and datasource implementation. It includes adding, removing, and rearranging elements in one section only.

An instance of TCSFavoriteUsersController is a spring board for 3 other controllers.

  • TCSSettingsViewController – users can change the value of playCountFilter.
  • TCSUserNameViewController – used to add a new friend or edit an existing friend.
  • TCSWeeklyAlbumChartViewController – used to display charts.

We need to keep a connection to the Settings & UserName controllers we create and display because we need to know when they change values. Normally this is accomplished through the delegate pattern. Our parent controller will get a message with the new data and dismiss its child. We’re going to try a different pattern using RAC.

This example is for editing the userName of a friend by tapping the cell during edit mode (this used to be done by tapping the accessory button, but changed before release).

(TCSFavoriteUsersViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
  
     // Get the user name for the row using a helper function.
     // We could get the whole User object, but in this case 
     // we only need the name.
    NSString *userName = [self userNameForIndexPath:indexPath];
  
    // Selecting the cell has different behavior depending on 
    // whether or not the controller is in edit mode.
    if (!self.editing){
  
      // In non-editing mode, just present a new chart controller.
      TCSWeeklyAlbumChartViewController *albumChartController = [[TCSWeeklyAlbumChartViewController alloc] initWithUserName:userName playCountFilter:self.playCountFilter];
      [self.navigationController pushViewController:albumChartController animated:YES];
    }else{
  
      TCSUserNameViewController *userNameController = [[TCSUserNameViewController alloc] initWithUserName:userName headerShowing:NO];
      @weakify(self);
  
      // The userNameController has a signal (analogous to a protocol) that
      // sends the User object if it has changed.
      // We subscribe to it in advance, and write our instructions for
      // processing changes.
      [[userNameController userSignal] subscribeNext:^(User *user){
        @strongify(self);
  
        // Section 0 is for the primary user.
        // Section 1 is for friends.
        // Our userStore class takes care of the actual replacement 
        // and persistence.
        if (indexPath.section == 0){
          [self.userStore setUser:user];
        }else{
          [self.userStore replaceFriendAtIndex:indexPath.row withFriend:user];
        }
      }completed:^{
        // The signal completing tells us that we can remove the user name controller.
        [self.navigationController popViewControllerAnimated:YES];
      }];
  
      [self.navigationController pushViewController:userNameController animated:YES];
    }
  }

The child controller has a signal instead of a protocol method. We place what would be the protocol method’s contents in the signal subscription block.

What do I gain by doing it with RAC? Not much in this particular implementation. I can keep the response behavior localized in the controller definition, and expand it to its own method if it happens in multiple places. I also could have done some filtering on the sending or receiving side or sent an error signal.

To make this work on the TCSUserNameController.m side, I first created a public property for the userSignal. Then, I created a new RACSubject in the designated init method of the controller. Remember, an RACSubject is capable of sending next, complete, or error values whenever we want.

We’ll use a done button or textField’s return button as our confirm button.

(TCSUserNameViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    #pragma mark - private
  
  - (void)doDone:(id)sender{
    self.loading = YES;
    [[[self.lastFMClient fetchUserForUserName:self.userNameField.text]
       deliverOn:[RACScheduler mainThreadScheduler]]
     subscribeNext:^(User *user) {
      [self.userSignal sendNext:user];
      [self.userSignal sendCompleted];
    } error:^(NSError *error) {
      [[[UIAlertView alloc] initWithTitle:@"Vinylogue" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
      self.loading = NO;
    } completed:^{
      self.loading = NO;
    }];
  }

I initially implemented this by simply returning whatever userName the user entered in the box without any checking. As an additional feature, I implemented a network call into this step that checks the userName with Last.fm before returning it. The side effect is that the name gets formatted as it was intended.

Release

We’re skipping a few steps at the end here (testing, analyzing, prepping for the App Store), but this post is long enough as it is.

Improvements

There are a few things I’d like to take a look at in future versions.

More stable threading

I’m doing some strange things with threading, including setting controller properties on background threads. Most of the time it seems to work out, but I need to take a step back and map out the flow of property setting and events.

Better cell image handling

I covered how I handle cell images above, but my method has some code smell and could probably use another look.

Testing

At this point in time, I haven’t explored unit testing or UI automation testing at all, and would like to give those a shot with this project.

V1.1

While the app was processing in the App Store, I worked on several improvement to V1.1. I added friend importing to the Scrobblers controller. I also created an album detail controller which changes background color based on the album image.

I’ll be waiting to hear back from users before implementing any other new features.

Final thoughts

Congrats if you made it this far. Although I didn’t exactly hit all my goals for this post, I didn’t think it’d be nearly this long either. I hope this post will add to the discussion about ReactiveCocoa. Ping me on twitter @twocentstudios if you have comments or questions. Or check out the Hacker News thread.

Simple AJAX Comments With Rails

Update 2014-06-08: This post is over two years old now. Although I’ve heard the below walkthrough works mostly as expected, I’ve been away from Rails too long to know the ins and outs of the current version of Rails and all the gems used. So a word of warning: I can’t guarantee all of the below will work line-for-line anymore. Feel free to ping me on Twitter if you find any changes.

I’ve been working on and off with Rails for a few years now, but when I started I had little HTML/CSS/JS knowledge from which to build. Most of my web experience I learned along the way in the context of Rails.

HTML and CSS were much easier to build familiarity with than JavaScript. I always found more time-tested best practices concerning HTML and CSS than I did with JS/AJAX. AJAX with Rails techniques seemed to have changed significantly between releases of Rails major (and even minor) versions.

I am by no means an expert, but my goal with this post is to walk beginners through a working technique for vanilla AJAX comments on resources based on Rails 3.2.x.

What we’re making

Our goal is to make a comment form that we can attach to any resource in our Rails app. It will look something like this:

Our goal
Our goal

The layout is pretty standard. A create form sits on top of a list of comments (newest first).

Our example resource throughout this post is an “Event”. We’ll only discuss it in terms of being an example generic resource with comments that belong to it.

Create

When a logged in user enters a comment and clicks “Create Comment”, the browser sends a message back to the server with the comment body, the resource name, and resource id. Once the server processes the message, it will send the comment body rendered in HTML in the same partial as the other comments were rendered with.

In the meantime, on the client side, we’ll be doing some basic jQuery effects to let the user know their comment is being processed. We’ll disable the textarea and submit button so they don’t accidentally submit the same comment twice.

Once the server returns our new HTML, we’ll reenable the form controls, clear the text from the textarea, and then add the new HTML to the top of the comment list.

To keep it simple for now, we won’t be handling the error cases in significant detail.

Processing order

  • route: GET /event/1
  • controller: events#show
  • view: events/show.html.haml
  • partial: comments/_form.html.haml
  • partial: comments/_comment.html.haml
  • user: add comment body and click create
  • js: comments.js.coffee –> ajax:beforeSend
  • route: POST /comments
  • controller: comments#create
  • partial: comments/_comment.html.haml
  • js: comments.js.coffee –> ajax:success

We’ll touch on each of these steps, but not necessarily in that order.

Delete

We’ll also allow users to delete comments (eventually only comments they’ve created!). When they click the ‘x’ next to the comment, we’ll prompt them with a standard confirmation. If they answer yes, we’ll then send the comment id to the server.

On the browser side, we’ll immediately dim the comment to half opacity to let the user know we’re trying to delete the comment. Once we receive a response indicating the comment has been removed from the database, we’ll then hide the comment in their browser the rest of the way.

There are a few error conditions we should handle here as well, but we won’t look at those in this post.

Processing order

  • route: GET /event/1
  • controller: events#show
  • view: events/show.html.haml
  • partial: comments/_form.html.haml
  • partial: comments/_comment.html.haml
  • user: click “x” next to comment
  • user: click “yes” to confirm
  • js: comments.js.coffee –> ajax:beforeSend
  • route: DELETE /comments/1
  • controller: comments#destroy
  • partial: comment.json
  • js: comments.js.coffee –> ajax:success

The first half is the same as we’ll see for comment creation, so we’ll focus on the last half mostly in that order.

Where to start?

First place to start is getting our backend comment system in place. We’ll be using the acts_as_commentable_with_threading gem (although we won’t be using the threading right away).

The instructions for setting this up are pretty simple. I’m just using ActiveRecord and SQLite right now.

  • Put the gem in your bundle gem acts_as_commentable_with_threading.
  • Run bundle install.
  • Run the migrations rails g acts_as_commentable_with_threading_migration.
  • Run rake db:migrate.
  • Add acts_as_commentable to the Event model class (and any other model you want to have comments).

      # event.rb
      class Event < ActiveRecord::Base
          acts_as_commentable
      end     
    

This post is supposed to be more about AJAX than Rails associations, but it’s worth mentioning that acts_as_commentable uses a polymorphic association. This means that any of your models can reference the same kind of comment model object, and we don’t have to have a separate table in our database for an EventComment or a VideoComment for example. Each comment record keeps track of what type of object its parent is, which will be important later since we need to know information about the parent in order to create a comment.

Routes

Next we’ll set up our routes just to get that out of the way. We’re going to let the CommentsController handle creation and deletion of comments, so the routes should point there.

    # routes.rb
    resources :comments, :only => [:create, :destroy]

This will give us two methods from the following urls (from rake routes).

    $ rake routes
    comments    POST        /comments(.:format)     comments#create
    comment     DELETE  /comments/:id(.:format)     comments#destroy

This is going to give us a commments_path helper and comment_path(:id) helper to complete our POST and DELETE requests, respectively. It will forward requests to those URLs to the CommentsController’s create and destroy methods. The create method has no parameters in the URL string. The destroy method takes the comment’s id as the single parameter of the URL string. Like we mentioned earlier, in order to create the comment, we’ll need a few more parameters. We’ll talk more about that when we get to the form.

Alternate implementation

Aside: An alternate implementation worth mentioning is to include comments as a nested resource beneath each resource that has them. It would look something like this:

    # routes.rb - alternate
    resources :events
        resources :comments, :only => [:create, :destroy]
    end

    resources :videos
        resources :comments, :only => [:create, :destroy]
    end

This works fine if your resources are all siblings. In my case, I have Video nested within Event already. It gets pretty hairy pretty quickly and gives you (unnecessarily) complicated routes and URLs. In this case, we’ll go with the other implementation that includes the necessary data about the comment’s parent in the HTTP POST data rather than the URL string.

Again, it works either way so always tailor your implementation based on your particular situation.

show.html.haml

Now that we’ve got the bridge between the view and the controller built (the route), we’ll tackle the show view template.

Our goal is to be able put the comment “block” (the add new form and the list of previously created comments) anywhere. In this example, we’ll stick it in the show view of the EventsController.

(Sidebar: I use Haml and simple form, sorry in advance to users of other templates. Hopefully you can still follow along.)

    / show.html.haml
    .event
        %h1= @event.name
    .comments
        %h2 Comments
        = render :partial => 'comments/form', :locals => { :comment => @new_comment }
        = render :partial => 'comments/comment', :collection => @comments, :as => :comment

As you can see, our show template expects 3 different instance variables from the EventsController.

  • @event: the unique Event object we’re showing.
  • @new_comment: a new Comment object that acts as our framework for building out the comment form. It exists only in Rails for now and has not been created in the database yet.
  • @comments: an array of all or just a subset of the Comment objects that exist as children of the @event object (in reverse chronological order of course).

In our views/comments folder, we have the two partials _form.html.haml and _comment.html.haml. _form expects a local variable named comment as an input to help build the new comment form. comment.html.haml is our partial for displaying a single comment. It takes a collection of comments and tells the renderer to treat each object in the collection as a comment.

events#show

Before we dig into writing each partial, let’s step backwards in the chain of events and go back to our EventsController to set up those instances variables that the show template will be looking for.

    # events_controller.rb
    class EventsController < ApplicationController
      def show
        @event = Event.find(params[:id])
        @comments = @event.comment_threads.order('created_at desc')
        @new_comment = Comment.build_from(@event, current_user.id, "")
      end
    end

The first line of the show method should be par for the course. We’re pulling the event in question from the database based on the id provided in the URL. Rails automatically inserts a render 'show' for us at the end of the method.

The second line looks a little fishy. We’re using a helper method included in acts_as_commentable_with_threading to get the comments associated with the @event and order them by date. You might also want to do pagination at this step too, but with our nested event->comment architecture, it might also warrant an AJAX solution to load more (that’s a topic for another post).

The third line creates a placeholder comment object that acts as sort of a carrier for our parent object info. This new blank comment object will carry with it a reference to the parent @event and therefore its object type and id, and the current user. The build_from method is another helper created by acts_as_commentable_with_threading.

comments/_form.html.haml

Now we can continue on to our new comment form partial.

    # _form.html.haml
    .comment-form
      = simple_form_for comment, :remote => true do |f|
        = f.input :body, :input_html => { :rows => "2" }, :label => false
        = f.input :commentable_id, :as => :hidden, :value => comment.commentable_id
        = f.input :commentable_type, :as => :hidden, :value => comment.commentable_type
        = f.button :submit, :class => "btn btn-primary", :disable_with => "Submitting…"

Let’s step through line by line.

First, we’ll wrap the form with the comment-form class.

Next, we’re going to use simple form to create a form block for our comment. Adding :remote => true will provide the Rails magic to turn our standard form into an AJAX one. The form_for helper is smart enough in this case to pick the correct URL and HTTP method. We could specify it directly as:

    = simple_form_for comment, :url => comment_path, :method => 'post', :remote => true do |f|

The first input is the textarea for our comment body. Nothing special here, just limiting the rows to 2 and turning the label off.

The next two inputs are hidden from the user and will be included with the form submission to the server. We’re including the commentable_type or class name of the parent object and its id so that our CommentsController will know what object to link the new comment to.

Aside: I want to mention that since these hidden inputs are technically open to alteration, they must be properly sanitized by the server before being acted upon. By altering these values, the user could potentially create a new comment on a different object type and/or an object they aren’t allowed to see.

Our last form element is a submit button with Twitter Bootstrap classes for styling. Clicking this will trigger the AJAX action and submit our form data to the CommentsController for handling. The disable_with takes care of some of the JS we’d have to write by disabling the submit button.

I’m going to skip the JS for now and move onto the CommentsController implementation. We’ll get back to the JS in a moment.

CommentsController

If you recall earlier, we set up routes to our CommentsController for two methods: create and destroy. Let’s take a look at create.

    # comments_controller.rb
    class CommentsController < ApplicationController
      def create
        @comment_hash = params[:comment]
        @obj = @comment_hash[:commentable_type].constantize.find(@comment_hash[:commentable_id])
        # Not implemented: check to see whether the user has permission to create a comment on this object
        @comment = Comment.build_from(@obj, current_user.id, @comment_hash[:body])
        if @comment.save
          render :partial => "comments/comment", :locals => { :comment => @comment }, :layout => false, :status => :created
        else
          render :js => "alert('error saving comment');"
        end
      end
    end

The first thing we do is grab a reference to the form data. Our form data is in the params hash under the :comment symbol. We’ll store it as @comment_hash for use below.

Next we need to derive the parent object where the comment was created. Luckily, we included the commentable_type and commentable_id in our form data. @comment_hash[:commentable_type] will return the string "Event". We can’t call find on a string, so we have to turn it into a symbol that Ruby recognizes. We can use constantize to do this conversion (it would be a good idea at this point to check to make sure the commentable_type is legitimate). With a fully qualified Event class we can call the class method find and pass it the :commentable_id. Out pops our event object.

The next step is to determine whether the current_user has permission to create the comment on the object. This depends on your authentication system, but should definitely be included.

We now have references to all the objects we need in order to create the comment. We’ll use the build_from helper method again and give it the object, current_user, and the body of the comment.

We need to save the comment back to the database. If the save is successful, we’re going to do a few things.

  • Render the single comment partial with our new comment as the local variable. This will give the comment all the markup it needs to be inserted directly into the existing page.
  • :layout => false will tell the renderer not to include all the extra header and footer markup.
  • :status => :created returns the HTTP status code 201 as is proper.

If the save is not successful, we need to tell the user that there was a problem. I’m leaving this outside the scope of the post simply because there are several different ways of doing this depending on how you set up your layout. Above, all we’re doing is popping up an alert box to the user. You should consider this an incomplete implementation.

Aside: using Rails to render HTML is a technique opposite that of returning raw JSON and using client-side JS libraries to handle all things view related. You may want to look into something like Ember.js.

JavaScript for create

We’re finally back to the JavaScript, or more specifically, CoffeeScript. I’m not an expert in either, but for this stuff you don’t need to be one. I’m using CoffeeScript because it makes the code slightly cleaner.

The only CoffeeScript we’re going to write can sit comfortably in the asset pipeline in the comments.js.coffee file (more specifically, app/assets/javascripts).

    # comments.js.coffee
    jQuery ->
      # Create a comment
      $(".comment-form")
        .on "ajax:beforeSend", (evt, xhr, settings) ->
          $(this).find('textarea')
            .addClass('uneditable-input')
            .attr('disabled', 'disabled');
        .on "ajax:success", (evt, data, status, xhr) ->
          $(this).find('textarea')
            .removeClass('uneditable-input')
            .removeAttr('disabled', 'disabled')
            .val('');
          $(xhr.responseText).hide().insertAfter($(this)).show('slow')

What is code actually doing? We’re simply registering for callbacks on the AJAX requests that will originate from our comment form. When those events occur, we’re going to run functions.

$(.comment-form) targets the comment-form class we applied to the div that wraps our comment form partial. This allows us to actually use multiple comment forms on a single page if we want to.

.on is the jQuery function that binds an event to a function. It replaces the older jQuery functions .bind, .delegate, and .live. You can read about it here.

The first event we’re binding to is "ajax:beforeSend". When the user clicks the submit button, Rails will trigger this event, and our function will be called. The arguments passed to the function (and all the available callbacks) can be found on the jquery-ujs wiki.

The function that runs on this event is embedded as anonymous. We could call a function that exists elsewhere just as easily.

$(this) is the jQuery object version of the .comment-form div that was involved in the click. Alternatively, we could grab a reference to the form from $(evt.currentTarget). We’ll use $(this) to extract the textarea element in the next line. .find('textarea') will select textarea elements within the form. In our case, we only have one. We then chain two functions together to perform two operations on the textareas.

    $(this).find('textarea')
      .addClass('uneditable-input')
      .attr('disabled', 'disabled');

is equivalent to:

    $(this).find('textarea').addClass('uneditable-input');
    $(this).find('textarea').attr('disabled', 'disabled');

addClass adds the uneditable-input class to our textarea, which will perform some Bootstrap styling to our textarea, but not actually make it uneditable.

attr adds the disabled='disabled' element to our textarea actually disabling the user input.

We’re then chaining another .on for the ajax:success event that gets called if the AJAX call returns successfully. Our first move is to find the textarea and undo the temporary disabling (you may want to consider doing this at the ajax:complete event, because it should be done regardless of whether the AJAX was successful). You’ll notice we chained one additional function .val('') at the end. This will clear the textarea in anticipation of the user adding another comment. You wouldn’t want to do that in the error case, because the user should have an opportunity to resubmit the comment without having to retype it.

We’re finally ready to add the nicely formatted comment to the top of our comment feed.

  • $(xhr.responseText) gets a jQuery object version of the response HTML returned by the server.
  • .hide() disappears our new div so it can be animated in.
  • .insertAfter($(this)) places our new div after the comment form. If you want to put it somewhere more specific, you can replace the $(this) selector with a more specific selector.
  • .show('slow') animates our new div sliding down from the form.

_comment.html.haml / deletion

I skipped our single comment template, so I’ll add it here for completeness. This will lead us into the comment deletion section.

    # _comment.html.haml
    %div.comment{ :id => "comment-#{comment.id}" }
      %hr
      = link_to "×", comment_path(comment), :method => :delete, :remote => true, :confirm => "Are you sure you want to remove this comment?", :disable_with => "×", :class => 'close'
      %h4
        = comment.user.username
        %small= comment.updated_at
      %p= comment.body

Our wrapper div has a comment class, and a CSS id unique to each comment. We’re not actually going to use that id, but it could be useful in the future.

link_to should look familiar. Our display text is an x. The link will go to the delete path we created earlier in the Routes section. To refresh your memory, it will go to /comments/:id. :method => :delete tells Rails to use the DELETE HTML method.

:remote => true performs the Rails AJAX magic like we saw earlier with the creation form. :confirm pops up a JS alert to confirm the user wants to do remove the comment. :disable_with makes sure the user can’t try to delete the comment while the server is processing the first request. And the close class is Bootstrap styling.

Another reminder: you’ll probably want to conditionally display the delete link to the comment creator and admins. Draper is a good option for doing this cleanly.

The rest of the markup should be pretty straightforward.

Back to CommentsController

Time to add the destroy method to your CommentsController.

    # comments_controller.rb
    def destroy
      @comment = Comment.find(params[:id])
      if @comment.destroy
        render :json => @comment, :status => :ok
      else
        render :js => "alert('error deleting comment');"
      end
    end

@comment will track down the comment-to-be deleted from the database (check that user is allowed to delete it!).

Then try to destroy the comment. This time, when the call completes successfully, I’m sending raw json back to the client with an ok status. There are a myriad of options here. Use what’s best for your app.

And on error I’m copping out again and sending back JS.

Aside: if you want to do some informal testing, I recommend throwing a sleep 5 call before the if statement so you have more time to observe your AJAX.

JavaScript for destroy

Back to our comments.js.coffee file.

    jQuery ->
      # Create a comment
      # ...

      # Delete a comment
      $(document)
        .on "ajax:beforeSend", ".comment", ->
          $(this).fadeTo('fast', 0.5)
        .on "ajax:success", ".comment", ->
          $(this).hide('fast')
        .on "ajax:error", ".comment", ->
          $(this).fadeTo('fast', 1)

We’re going to use the other incarnation of .on for the reason I’ll explain in a moment. This time we’re calling .on on the whole DOM. We specify our event first as we did before, but now we’ll add the ".comment" selector as the second argument. Again, this applies to all of our comment divs with the comment class.

We’re not going to bother including the arguments to the ajax event callbacks (for example (evt, xhr, settings)); we don’t need them.

$(this) refers to the comment div that generated the event. We’re going fade the entire comment to half opacity before sending the request to the server by calling .fadeTo('fast', 0.5). On success, we’ll animate the comment fading the rest of the way out and disappearing to show the user the request was completed succesfully. On error, we’ll fade the comment back to full opacity to show that the comment still exists.

The reason we used $(document) this time instead of calling .on on the selector directly is because it will apply the callback to newly created DOM elements as well. For example, I can add a comment and then immediately delete it without refreshing the page.

Wrap up

This turned out to be quite the mega-post. I may have gone into too much detail, but I’m hoping this has enlightened any new Rails devs out there.

We didn’t actually write that much JavaScript, and most of it was simply for decoration. But this should give you the building blocks you need to add more interesting functionality on AJAX triggers. I highly recommend this jQuery reference/overview.

Discuss this on Hacker News.

Core Data + iCloud Is Kind of Ridiculous

I’ve spent most of the evening getting reacquainted with Core Data. My goal is to get up to speed enough to use it and iCloud for a simple app idea I want to prototype out. It’s turning out to be a little more daunting than I had hoped.

Past Core Data experience

On a previous app, I used Core Data to store values pulled from a server in a single table on the user’s device as a makeshift cache so that it could be used as a fallback if the network was not available. This was almost two years ago. I could cobble together enough code from intros and blog posts to set up my one-table schema, set up my persistent store, pull a managed object context, do some simple storing, and some very simple fetching based on a unique object field.

I was using it as more of a nice-to-have than a core feature of my app, so I never had to dig into it that much.

In my new app, I’m also planning on storing a single table of data. But this time, that data is the heart of my app. It’s also going to need to be sliced and diced in a number of different search contexts. And the biggest X-factor is that I want to be able to access and modify that data from different iOS devices. This is where iCloud comes in.

I almost used iCloud on my BrightBus application, but pulled out of that decision last minute because I needed to support older devices and didn’t want to have two different document models. In the case of BrightBus, I wanted to store a collection of favorite stops. This is a simple plist, so it would be using the simpler UIDocument APIs.

Commence learning

So now that I’ve decided that it makes sense to use Core Data and iCloud for this app, it’s time to get started.

First thing, I need to set up a new app ID specific to this app that has iCloud enabled. Okay great, but I have no idea what to name my app yet. Well I can just create a throwaway one and change it after I’ve got most of the app ready to go. Okay, but the ID will hang around forever in my account because you can’t delete or rename them. Okay whatever.

Now a new provisioning profile, okay that’s not that bad.

Let’s make a new project. Again, not sure what the name is, but whatever, no one will see it anyway.

Now I’ve got to set the entitlements. Alright I’ll use my app ID and reverse URL string. But oh wait, I used a dash in the app ID, but a space when I created the Xcode project. Ugh, let me go back and try again. Delete folder, create new project.

I have to decide whether I want people with iCloud disabled to be able to use my app. Uh, yeah probably, but oh wait that makes things much more complicated on my side? Uh… I’ll punt on that decision while I do some more research.

I have to decide what data isn’t important and can be regenerated and make a separate persistent store for that that won’t go to iCloud. Or I can use CoreData configurations to handle that in a single store. I’m thinking that in my case it’s all important so I don’t have to worry about that.

I don’t have to worry about seeding either, so that’s good.

But if I start digging into the actual details of setting this stuff up, I get constants like NSPersistantStoreUbiquitousContentNameKey and NSPersistentStoreDidImportUbiquitousContentChangesNotification that look scary, but maybe if I watch this WWDC video enough, the framework designers’ explanations will give me insight into what those words represent.

And then I stop for a second.

At this point I have to tally my pros and cons for using iCloud versus something like Parse, which I’ve done a little research into in the past but haven’t actually used it on a production app.

iCloud

  • pros
    • I don’t have to worry about managing users accounts
    • I don’t have to worry about storing user data on my servers
    • My app really is only storing individual data, but I can still post to Twitter and Facebook if need be
    • I can sync between iOS devices and Macs
  • cons
    • If people don’t have iCloud accounts, switch accounts, or delete my app’s data from their accounts it gets hairy
    • I have to set up all the weird provisioning and entitlements and app keys right
    • I have to deal with conflicts
    • I can’t ever run a normal web service or let users use this data from anything other than an iOS or OS X device
    • Have to worry about schema migrations if I decide to change stuff in the future = more/more difficult maintenance
    • Wow is this complicated (CoreDataController from WWDC sample is ~750 LOC and takes lots of shortcuts)

Parse

  • pros
    • I (kind of) don’t have to worry about managing user accounts
    • I don’t have to worry about storing user data on my servers
    • I have access to all data in case there’s a need
    • Users can get the data from a normal web service or any device I want
    • I can sync data
    • Schemaless = more flexible
    • Users can still use the service anonymously
  • cons
    • I still have to deal with merge conflicts
    • Fetch performance is probably much worse because I have to hit the network (although there’s a lot of caching so that could be ambiguous)

Neither (punt)

  • pros
    • I can focus more on user experience within the app
    • No worrying about syncing edge cases
    • Less boilerplate
    • I can validate the main app idea faster
    • I don’t have to deal with conflicts
  • cons
    • Users may immediately request app access from multiple devices
    • I may want to add out-of-app features that need the infrastructure
    • It will be more difficult to migrate over to a network model later (the foundation of my app might need to be rearchitected)
    • I’m missing out on an opportunity to become skilled in one of the above technologies

This is a tough call. I’m going to finish out these CoreData + iCloud videos and sleep on it.

All I know is that with Rails and ActiveRecord this is a completely different ballgame. Yes, at first it was just as opaque, but it just seems so much more flexible up against the Core Data APIs. Apple exposes so much complexity in their APIs sometimes, and you think that in the vast majority of cases it just doesn’t need to be that way.

Now I’m just ranting, so I’ll have to sleep on it like I said and circle back in the morning.

Deconstructing a chirp.io

UPDATE (10/13/12): The kind folks at chirp.io pointed me to their tech page. Read more at the end of the post.

TL;DR: I tried to figure out the chirp.io sound->URL protocol but failed.

I came across an interesting app today called chirp.io. From the chirp.io website:

Chirp is an incredible new way to share your stuff – using sound. Chirp sings information from one iPhone to another.

Just reading about it, I was very impressed. It’s not easy to encode a few hundred kilobytes of data (small jpeg) into a sound. But in the App Store blurb, it says:

Sharing requires a network connection.

Oh, so it’s actually just transmitting a link. Still pretty cool.

I downloaded the app and played a few of the example chirps. I noticed that they were relatively high pitched and seemed to be the same length. I also noticed they were monophonic – only one frequency was played at a time.

By tapping on a chirp, it shows what is basically a short URL for that resource. An example is chirp.io/gsm2h88c7u which links back to chirp.io/blog. You can also share images and text.

I did some similar DSP and frequency detection projects in college, so I decided to see if I could reverse engineer the protocol that chirp.io uses. I’m definitely no codebreaker or cryptographer, but we’ll see how far we can get.

Busting out the DAW

I usually use Magix Samplitude as my Digital Audio Workstation of choice, but since I was booted up on my OS X side, I decided to use cross-platform Reaper instead.

The first thing I needed to do was record the waveform. I could have direct connected into my sound card using a 3.5mm to 3.5mm jack, but I didn’t have one of those handy. I did have my Shure KSM27 set up, so I decided to record it through the air instead.

The first chirp I analyzed was chirp.io/gsm2h88c7u. If you notice, the short URL is only 10 characters long. We may be able assume that it uses lowercase characters a-z and 0-9.

The full waveform of a single chirp
The full waveform of a single chirp
One monophonic segment of the chirp
One monophonic segment of the chirp

If you count, there are 20 monophonic segments in the chirp. Each segment is around 88ms long.

Reaper has a pitch detector plug-in, so I looped each segment and estimated the frequencies. The pitch detector plug-in sometimes got confused though, so I had to double check with a normal FFT.

The fourth segment of the chirp was about 8981Hz
The fourth segment of the chirp was about 8981Hz

Looking at the data

I recorded the data for this first chirp:

chirp1 = [4717, 5300, 4453, 8981, 6324, 
            1976, 4717, 2797, 2797, 3522, 
            2640, 10000, 3737, 9400, 6660, 
            3965, 4189, 2220, 7131, 5613]

Those are the 20 frequencies in Hz in the order they’re played.

With only one data point so far, I decided to make an initial hypothesis:

  • Points 8 and 9 are the same (2979Hz) so maybe that divides the chirp into a metadata section and a URL section.
  • The unique URL part is 10 characters so maybe that’s sections 10-19 and 20 is the stop bit.

I can’t do much with only one data point, so I analyzed a second chirp. This one is a short text block with the URL chirp.io/mnac2dvevb.

chirp2 = [4717, 5300, 6324, 6660, 3143, 
            3522, 1976, 3737, 10844, 3965, 
            10844, 3329, 5000, 4717, 2797, 
            6660, 4189, 2098, 3965, 2220]

Hmm… not as much correlation as I expected. Let’s look at them side by side.

1   4717    4717
2   5300    5300
3   4453    6324
4   8981    6660
5   6324    3143
6   1976    3522
7   4717    1976
8   2797    3737
9   2797    10844
10  3522    3965
11  2640    10844
12  10000   3329
13  3737    5000
14  9400    4717
15  6660    2797
16  3965    6660
17  4189    4189
18  2220    2098
19  7131    3965
20  5613    2220

The only thing that stands out at first glance is segments 1 and 2 are the same. That would make sense as our start code.

Let’s combine these two sets, sort them, then discard the duplicates.

uniq_freqs = [1976, 2098, 2220, 2640, 2797, 
                3143, 3329, 3522, 3737, 3965, 
                4189, 4453, 4717, 5000, 5300, 
                5613, 6324, 6660, 7131, 8981, 
                9400, 10000, 10844]

Between the two chirps, there are 23 unique frequencies. So frequencies are shared quite a bit.

Now let’s subtract the neighbors to try to figure out how many we’re missing.

diff_freqs = [122, 122, 420, 157, 346, 
                186, 193, 215, 228, 224, 
                264, 264, 283, 300, 313, 
                711, 336, 471, 1850, 419, 
                600, 844]

I’d guess that we’re missing a couple from the low range, but a few more in the higher range. I expect the differences to increase as the we get higher up the scale, but that really depends on the frequency detection algorithm being used by the app.

It seems like we’re kind of stuck. My initial hypothesis is mostly wrong. It doesn’t look like frequencies map directly to letters. Let’s do one more chirp before we give up.

Flower Picture: chirp.io/9gf6q9ltu3

chirp3 = [4717, 5300, 2963, 4453, 4189, 
            2490, 7922, 2963, 5945, 9400, 
            10000, 2098, 7922, 5945, 7521, 
            3965, 8981, 5000, 4717, 2098] 

1   4717    4717    4717
2   5300    5300    5300
3   4453    6324    2963
4   8981    6660    4453
5   6324    3143    4189
6   1976    3522    2490
7   4717    1976    7922
8   2797    3737    2963
9   2797    10844   5945
10  3522    3965    9400
11  2640    10844   10000
12  10000   3329    2098 
13  3737    5000    7922
14  9400    4717    5945
15  6660    2797    7521
16  3965    6660    3965
17  4189    4189    8981
18  2220    2098    5000
19  7131    3965    4717
20  5613    2220    2098

uniq_freqs = [1976, 2098, 2220, 2490, 2640, 
                2797, 2963, 3143, 3329, 3522, 
                3737, 3965, 4189, 4453, 4717, 
                5000, 5300, 5613, 5945, 6324, 
                6660, 7131, 7521, 7922, 8981, 
                9400, 10000, 10844]

diff_freqs = [122, 122, 270, 150, 157, 
                166, 180, 186, 193, 215, 
                228, 224, 264, 264, 283, 
                300, 313, 332, 379, 336, 
                471, 390, 401, 1059, 419, 
                600, 844]

We’re now up to 28 unique frequencies. I’m not sure if there’s enough frequency space left to suggest they’re using a 36 character alphabet.

Unfortunately, it doesn’t look like we can deduce anything new from our third set of data. The two segment start code is the same. But other than that, there doesn’t seem to be any correlations I can tease out.

Analysis

My assumption that the unique URL component was related one to one with the frequencies was wrong. It’s looking more and more like there’s some sort of hashing combined with error detection/correction.

It looks like I’ve failed to deduce the protocol, but it’s interesting to see how chirp.io uses the frequency space.

I can’t find the specs of the iPhone internal speaker and mic, so I don’t know what the hard limits are for frequency response. But small speakers are bad at reproducing low frequencies so it makes sense that they’re not going lower than 1000Hz.

The upper limit is a little more difficult to determine. It still has to do with the limit of the speaker and mic, but at a certain point, those higher frequencies may start to get a little annoying, even if the duration is short. At a certain point, due to the limitations of human hearing, the higher tones wouldn’t be audible enough even if they were annoying. Chirps are supposed to sound like a continuous stream of notes, and therefore even if the mic could deduce the correct frequency, it would lose some of the value.

Another one of my initial assumptions was that the amplitude of each frequency segment was not relevant. From the waveform, it looked like all segments were not of equal amplitude, but that may have been due to micing the iPhone speaker, which basically puts another 3 filters on the signal (speaker response, air, and microphone response).

The App Store description also mentions that chirping works in noisy environments, so I’m going to stick with my assumption that even relative amplitudes aren’t used.

Looking at pitch detection algorithms, there are three choices: Time domain, frequency domain, or both.

A simple time domain algorithm like period detection through zero-crossing would not work in a noisy environment, especially for higher frequencies. Autocorrelation is possible especially since we are only looking for a single frequency. Frequency domain methods are also likely because the spacing between frequencies can be chosen and there are no harmonics to worry about.

The iPhone CPUs are powerful enough now to use almost any of the popular pitch tracking algorithms and libraries, so performance shouldn’t be a limiting factor.

Conclusion

I’m looking forward to trying out the chirp.io app with some friends to see how well it performs. It’s definitely a cool idea, and I’m interested to see if it picks up steam.

If you happened to have some insights about my data than I didn’t, it’d be great to hear about it: @twocentstudios.

Update

If I would have read the FAQ on chirp.io more carefully, I would have seen their post about the technology behind chirp.io.

I was almost there…

Let’s see where we went wrong.

  • 20 pure tones – got that one.
  • 87.2ms each – I estimated 88ms.
  • 2 tone start code – got that.
  • 32 character alphabet – I first guessed 36, but then revised to saying it probably wasn’t more than 30.
  • [0-9, a-v] characters – I assumed they’d use up through ‘z’, and that it would start with letters and end with numbers.
  • [startcode][shortcode][error-correction] – I’m not sure why I didn’t think the shortcode would be at the front.
  • Pitch detection algorithm – nothing specific is mentioned yet, although the site says they’ll be publishing more on the topic soon.
  • Error-correction with Reed-Solomon – I don’t have enough experience with error correction algorithms that I could have made a prediction on this one. But my lack of understanding did cause me to overestimate how good the pitch detection algorithm needs to be to recover the signal.

    Error correction means that Chirp transmissions are resilient to noise. A code can be reconstituted when over 25% of it is missing or misheard.

Overall, it was a fun exercise and taught (or re-taught) me a little bit about DSP, coding & protocols, and I even got to play around with some Ruby.

I highly recommend downloading chirp.io if you’ve got an iOS device.

TDD, RSpec & Conway’s Game of Life

On my continuing quest to do more micro-projects, I decided to begin focusing on TDD today. After doing a little RSpec Googling, I came across this live coding video for doing TDD with RSpec on Conway’s Game of Life, something I hadn’t heard of before.

After watching about half the video, I realized I was far enough along that I could try it myself. So I took a stab at it doing TDD the whole way. It was actually pretty fun doing a simple OO Ruby project and getting my feet wet with RSpec. I’ve always had this ugly feeling of repeating myself with tests, which is one reason I never could bring myself to write tests upfront for my little Rails projects.

It turns out (as everyone knows) that testing actually saves the repetition that comes with hand-testing everything in irb. If I had a nickel for every time I sat there banging away at the keyboard constructing objects and relationships in the Rails console just to do a reload and wipe everything out, I’d be rich (and a cliche).

In any case, it was apparent very quickly that I was really saving myself a lot of headaches by writing tests. The feedback loop was much quicker writing tests first. Fixing bugs and not having to re-establish all of your test setup by hand each time makes it much easier to focus on the bug at hand. And this was just for a three-class micro-project. I can only imagine how much headaches testing saves in large scale Rails projects.

RSpec was actually a lot more intuitive than I expected. It helped that I found an old cheat sheet to kind of guide my thinking of how my should statements should be structured. I tried to keep all the best practices in mind while writing my tests, but I’m sure I missed quite a bit on my first run. Looking back, I probably should have had a few more edge case tests especially on the 4 big rules. I’m still getting used to describe, context, let, before, and all the other structural stuff. It doesn’t help that, from what I’ve seen, there seems to be a personal style to writing tests that varies. It varies in both test structure and amount of testing.

I also forced myself to get a primer on Ruby Debug (using the debugger gem). It’s a little different than I’m used to with the full IDE and the goodies that Xcode offers, but I’m sure it’ll be another useful tool in the toolbox.

I wrote a Cell class and a World class in what I think was good OO. Then I wrote a Display class that handles printing the world to the terminal (and even a little animation). I didn’t write tests for that though, it was kind of a just-for-kicks deal.

Sidebar: this actually reminds me a lot of the big project we did in my C++ class Sophomore year of high school. We spent what seemed like several months writing all these classes to simulate fish. I can’t remember exactly what the rules were, but I remember it being a bit tough to wrap my mind around. I don’t think I really understood the difference between UI code and game logic back then. I didn’t get that STDOUT was all we really needed for the concepts we were learning. I kept wanting to understand the GUI classes when all that was in the curriculum were the comp sci basics.

I’m not sure if my code is worth posting to github. I’ll keep it off for now and try to leave room for the more important/interesting projects I’ve got planned.

Summer 2012 Project Wrap Up

A lot has happened since my last quarterly project wrap-up. I’ve done a lot of little projects here and there, abandoned a few, and dreamed up a bunch more that I can’t wait to start on. Here’s a summary of the smaller things that might not have gotten their own posts over the last couple months.

To-be-titled Music App

Early in the summer, I decided to start seriously working on the music app I’ve been talking about since last Winter. It went through several iterations of what format it should be and what the scope of it should be. I coded up a quick Rails app to see the basic concept in a couple days, but left it untouched for a while because at the time it didn’t seem like the browser was the right primary format for it.

After spending a few more weeks rethinking the purpose of the app, it seemed possible to strip the app down to its core and do an iPad/iPhone only release. Initially everything was going to be local to the user’s device. Then, of course, the scope started to expand, and eventually I found myself and what I considered to be a happy medium of still having a core iOS UI, hosting backend data with Parse, and leveraging existing social media sharing.

In prep for the iOS app, I circled back to a new iOS library from Github called Reactive Cocoa. I spent a number of days just wrapping my head around the concept and example code, and finally felt confident enough to start diving in myself.

I spent a few consecutive weekends with my Graphic Designer friend CJ doing wire framing, prioritizing features, learning some more about the Parse framework, and starting on some prototypes of the backend. We even spent a full day going through vocabulary related to our concept to come up with a good name and good terminology within the app to best describe what the user would be doing.

At a certain point though, a few things converged and the project sort of sputtered out. CJ and I seemed to have more divergent ideas about what the core purpose of the app was, which was sort of a wakeup call that maybe it would be best to do more of that up-front work of surveying users and seeing if we actually have any interest in what the concept is.

And so the project is currently on hold. I don’t plan on giving up on it. The plan now is to build a few smaller projects before starting up again in order to be able to leverage that experience. Rails has proven to be a much quicker platform to prototype on, and thus my first steps will be to build out a shell that I can start soliciting user feedback from.

TestPlanIt

One of the first big Rails undertakings I started almost a year ago was an app targeted at a specific data management task I wanted to solve for a friend of mine at my day job. He is a Test Engineer, which basically means receiving physical samples and a test plan, performing the test, acquiring data, writing a test report, and distributing it.

My initial cobbled together solution to this problem involved a SharePoint list with a few basic bells and whistles. I always imagined a much better system with full database representation of all the elements he works with, and thus started wire framing a Rails app that could accomplish this on my nights and weekends.

It turned out that my vision was a little bit too complex to accomplish with my early Rails knowledge. A lot of it had to do with my Javascript deficiencies. And after a ton of work, eventually the project sort of died under the weight of its own complexity.

Fast-forward now to early summer. My Test Engineer friend brought up his data organization and workflow problems, and I started to think about the problem again. I revised my initial data model to reduce the complexity in a way that didn’t decrease functionality much, and basically started over again. I had dabbled in a few other projects since then and my familiarity with Rails-related technologies had given me enough skill to be able to power through the roadblocks that had hindered me the last time around. I reused some of my haml and some model code which saved time with the tedious aspects.

In what I think was less than a few weeks, I had already gotten the core of the app working well beyond what I used to have. I was already adding embellishments (PDF export support, amongst others), but realized that there was still a great deal of work left to do making the app enterprise-ready (roles & permissions, deployment). It was at this point that we started working on implementing our new Product Lifecycle/Data Management application (see this post). I realized that my app would probably never be used in production, and therefore stopped actively working on it. I took it as a learning experience and used that knowledge to build more apps like Flexible Parts.

myGengo API Wrapper

I wrote about this in detail, but the overall summary is that I missed Objective-C and wanted to do a little weekend project.

Looking back, I don’t think I did myself as much of a service as I should have with this project. I took shortcuts that I shouldn’t have because deep down I knew that I probably wouldn’t be using what I had written at all. Kind of another learning experience in choosing personal/open-source projects.

EngSurvey

My Test Engineer friend from work came to me with another request. He also needed an automatic survey system for his test reports. For ISO certification, you need to have some sort of feedback system from your clients (the Engineers that need their samples tested) about your reports.

I found an almost framework-scale gem called Surveyor which got me almost 80% of the way there. The last 20% is always the hardest. My friend wasn’t great at giving me requirements, I saw a lot of headaches ahead in deploying it internally, and the project wasn’t high on either of our priority lists, so this project died out as well.

Codecademy

I had read a lot about Codecademy since their New Years push. A non-programming friend of mine had been doing the full course for a few months, so I decided to check out the jQuery course. I finished about half the lessons which helped ready me for the AJAX part of my next few projects.

HaveRead

I got a request from another co-worker to find a solution to a problem she had. Her one requirement was that she needed a way to send off a document to a dozen or so people and simply collect responses of when they had read the document. I explained the voting feature of Outlook mail, but she didn’t seem to think that would be simple enough.

She needed it urgently of course. I threw together a Rails project to do this in about two half-days of work (naming them is always the hardest part). In the process I learned a little more simple AJAX and using the CarrierWave gem, which would help me in an adjacent project I was working on.

I showed her what I had, and although she was impressed, she said the limitation I have of running it off my laptop wouldn’t work out in the short term. But that she would consider using it in the future.

I wasn’t too bothered because it was a quick project and I learned a lot doing it. It was getting a little tiresome though that deployment was becoming a problem. Heroku or other cloud services weren’t an option because they were outside the company firewall. And getting my own VM in the company was blocked by too much red tape since I wasn’t part of the IT department. For the time being, the only way to deploy at work was to run the dev server on my machine and send links to my (non-static) IP, which is the technique I used in my next project.

SolidWorks Model Challenge Friday

Part of my (new) day job was administrating SolidWorks for our couple dozen Mechanical Engineers. We have a bunch of younger Engineers that still love a good modeling challenge, and from a seed of an idea sprang “Model Challenge Friday”. We’d spend half an hour on Friday morning designing/modeling various objects. The goal was speed and creativity, and to give the Engineers a sandbox to experiment with new techniques that they might not be comfortable with doing on real mission-critical projects.

In a lot of ways, it was inspired by the kind of projects I mentioned above. Stuff I never intended to launch and support, but instead used to experiment with new gems or techniques without having to get bogged down in details irrelevant to that goal.

I tried to participate in the first two weeks, but realized quickly that I could barely model a cylinder in an hour. I spent the next two weeks giving myself a crash course in 3D modeling with some help from my fellow Engineers. Before long, I was modeling up my own chair designs and actually participating in the contests. After a few more Fridays, I was to the point where other judges couldn’t tell which design was mine (because they used to be so simplistic and bad).

The judging was initially done by passing some unmarked printouts around the office area for an informal poll of the fan favorite design. This process was just begging to be web-appified though.

The next few weeks I spent several nights and weekend days working on my next Rails app to facilitate this process.

My design goals were the following. * Engineers would have user accounts. * Registered users could submit screenshots of their designs each week to the contest. * Voters would be able to vote without logging in, but they should only be able to vote once per contest. * Entries would not show the creator’s name until the contest was over. * When the contest was declared over, the votes would be automatically tallied and a winner declared.

This required a good combination of normal CRUD layouts, AJAX for voting, CarrierWave for image uploads, Bootstrap for a basic design, cookies to store voting status, and of course running my ad-hoc “production server” on my laptop.

After informally launching one Friday as sort of test, things went over really well. The system worked almost flawlessly (except for a few unscrupulous Engineers voting for themselves by not logging in). I realized quickly that I had compromised my dev server by using it to store real production data and thus had a bit of a problem on my hands working on improvements without breaking stuff.

We used my new creation for a few weeks. It worked well. I made some improvements to the entry browsing using the Bootstrap js components and digging around for other good js plugins. Eventually though, the contest itself died out when suddenly we all seemed to get too busy for that half hour.

AppleCart

I’ve talked about AppleCart in detail here, but the long and short of it is that the app has been a big success so far in raising money for the American Cancer Society and keeping things organized in the drive.

What’s Next?

I’ve started transitioning out of my day job role and into a more freelance/consulting lifestyle. I plan on doing this for a little while to increase my skill level and be able to tackle more theoretical and practical programming problems faster than I would by doing so only on nights and weekends. Time and energy have always been an issue, and especially with a lengthy commute, it just makes more sense to go all-in and laser focus on what I really want to do for the rest of my life.

Some of the programming related things I’m planning on doing with my time this Fall: * Tackle a bunch of small projects I’ve wanted to explore on iOS and Rails. * Really dig into RSpec and TDD. * Finish some programming books I’ve started, and few more I haven’t opened yet. * Start contributing to an open source project. * Immerse myself in the code bases of a few well-known projects to learn more about architecture and best-practices. * Begin learning a functional programming language. * Keep exploring the elements of traditional graphic design. * Do a project with a NoSQL database. * Go to a start-up event in Chicago. * Watch at least one conference talk a week on YouTube. * Organize my browser bookmarks.

I don’t know where the priorities will end up being for each of those, but here’s to a healthy and productive Fall.