Here’s a verdict that shouldn’t be surprising: Apple Watch users are most enthusiastic about actions that either come to you, or require as few taps (and as such, as little interface) as possible. Anything requiring more time and attention can be fine, but certainly doesn’t feel like the ideal way to use the device. Put another way, notifications and complications are what make the Watch a joy to use. Glances and apps are occasionally useful, but for the most part are not where you want to be spending too much time.
Being notified about something is nice, but being able to act on it right from the notification UI itself is extremely powerful, and a key part of a great Apple Watch experience. Interactive notifications seem underutilized on iOS – likely since they aren’t particularly discoverable – but they’re front-and-center on the Watch. Someone requests a Venmo payment from you, and you can pay them right from the notification1. It can’t be overstated how important it is that interactive notifications work right out of the box on Apple Watch, without requiring the app’s developer to build anything specifically for the Watch. If you already had them on iOS, they simply became that much more useful overnight when early adopters started receiving the first Watch shipments.
WatchKit does allow developers to build custom, enhanced “long-look” notifications, as well. Here’s what Uber’s looks like – it simply shows you a preview of which car you should be looking out for.
Unlike interactive notifications, receiving these long-looks requires having an app specifically installed on your Watch, highlighting a discoverability concern: there’s no way for you to know whether a Watch app comes with enhanced notifications or not. Many of the apps I have on my phone include Watch components, but I haven’t installed most because I don’t want to have too many apps on there. Less is often more, especially when considering how rapidly the Watch’s paged glances and honeycomb app grid decrease in usability as the number of apps in them increase.
That said, if I knew that a Watch app came with notifications that would allow me to actually take more direct actions or view additional information, I’d be much more likely to give it a shot. It seems like the best way to find this out is to simply install and wait until notification are received, to see if any custom long-look capabilities are being taking advantage of. Including a long-look notification as part of your app’s App Store screenshots could go a long way, and I wonder if there’s an opportunity for Apple to adjust the store pages to be more informative about which Watch capabilities a given app is taking advantage of.
That said, another way to attack the same problem would simply be to make having a lot of apps installed on your Watch be less unbearable. I’d love to see glances be able to hide themselves when they don’t have anything pertinent to show, much like how Today widgets behave on iOS2. This would allow users to have more glances enabled, knowing that they’ll only have to scroll through those with pertinent information at any given moment3.
Unfortunately, I don’t think this change could be made without first modifying the app honeycomb. In it’s existing state, the honeycomb becomes so unavigable with a large number of apps that glances end up becoming repurposed as app launchers4. As inelegant as it sounds, folders or some other way to bury apps that will rarely, if ever, be launched from the honeycomb (e.g. those installed purely for notification purposes, or built-in Apple apps that you simply may find uncompelling) would go a long way.
It’s far from an original thought, but notifications are arguably the “killer” Apple Watch feature that many have been looking for. If changes to the (far less important) glance system and the app launcher improve the experience of having a larger number Watch apps installed, and as such, provides users with richer, more actionable noticications, I think the Apple Watch becomes a far more compelling product.
My personal favorite: when I connect to my work VPN, I can easily approve the two-factor authentication request on my wrist. ↩
FlightTrack is a perfect example. It’s incredibly useful in both iOS Today widget and watchOS glance form, but couldn’t be less interesting when there isn’t a plane that you care about in the air at this exact moment. ↩
I’d love to see third-party complications work this way as well, though it’d definitely have the potential to get confusing quickly. But maybe I want a sports score complication while a game is going on, but my calendar otherwise? This kind of flexibility could decrease reliance on glances as well. ↩
Swarm is the app that I primarily use this way. Checking in from your wrist is quick and easy, but I only ever get to the app by tapping the (otherwise uncompelling) glance. ↩
It didn’t make the press release and wasn’t picked up on by most of the tech press obituaries, but when Path sold it’s apps to KakaoTalk, the best product that the company had released to date was silently brought out behind the barn and shot. That product was the Path Talk app’s “Places” feature, or as it had been known before being acquired by Dave Morin’s oft maligned app factory, TalkTo.
TalkTo, and subsequently, Path Talk Places (I’m going to refer to both products as TalkTo for the remainder of this post), are easy to describe but almost impossible to convey how well they actually worked in practice. Simply message any place – a bar, Home Depot, your doctor’s office – the same way that you’d message a friend or family member, and receive accurate, timely, natural human responses. All of the actions that you’d previously need to suffer through speaking to another person on the phone in order to perform – checking store hours, seeing if something is in stock, making a reservation at a restaurant that doesn’t support OpenTable – were now as painless as texting a friend to ask what time they’d be getting off work that evening. It felt too good to be true. Those of us who used it loved it, and tried to balance the desire to show our enthusiasm by shouting from the rooftops with our worry that this clearly wasn’t a sustainable business, and that if we got more people using it, we might actually cause it to disappear faster than we already feared it was going to.
The acquisition seemed like the nail in TalkTo’s coffin, and while it still lasted for quite a while, I feel like my initial reaction ended up being pretty justified.
Somebody please buy Path, save @TalkTo, then shut down the rest.
So now what? Using TalkTo felt like using the future, as the potential for chat as an alternative to conventional app interfaces remains huge. (My understanding of) the original TalkTo thesis still makes a lot of sense; while TalkTo had employees (or contractors) receiving your messages, calling a venue on your behalf, then texting you back, the end goal was to provide business with some combination of hardware and software that would allow them to remove the middlemen. Clearly a system like this, in addition to being preferred by what I assume would be a majority of customers, would allow businesses to provide more efficient and higher quality customer service. This is something that someone should still be moving towards, but who?
On the surface, this sounds like exactly the type of task that Google is best suited for. Historically, they’ve been the ones willing to throw lots of resources at large, manual tasks – such as sending cars to photograph as many storefronts as possible, globally – in the interest of making their mapping product more useful. I haven’t used the Google Maps app in quite some time1, but something like this would almost certainly be enough to bring me back.
The overwhelming majority of my transit and POI needs take place in New York City, where I find Citymapper and Foursquare to be the best in their respective classes. I rarely drive but Apple Maps has been perfectly fine for turn-by-turn navigation in my experience. ↩
“Dependency injection” is a phrase that has been tossed around a lot over the past few years, as the culture of unit testing (and as such, writing testable code) has grown within the iOS developer community. On more than one occasion, I’ve seen the concept of dependency injection reduced to something along the lines of “just passing instances into an initializer,” and confusion as to why such a practice would even be given a special name.
I’d like to discuss exactly what dependency injection is, and specifically what it means in the context of iOS development.
Passing an object’s dependencies into its initializer1, and as such remaining ignorant of where they came from, has obvious benefits. Those of us who try to write tests for our classes – or reuse classes across targets or applications, for that matter – know the perils of referencing global accessors in order to get access to a shared instance. There’s no innate problem with “singletons”; there are valid reasons for only a single instance of some class to ever be allocated within an application, but the classes that rely on this instance needn’t actually concern themselves with this fact. To do so would be to tightly couple, a shortcut that future developers will almost certainly pay a price for. Along the same lines, when an object creates an instance of something that it needs, it’s often preferable have that instance instead be passed into the object’s constructor. This allows for a mock or stub to be passed in in its place, during testing. By architecting your code like this, objects start to represent pure functions in the sense that they only operate on their inputs.
Let’s assume that we all agree that our classes should ideally take their dependencies as constructor arguments rather than reference singletons via global accessors, or create new instances themselves2. By putting ourselves in this mindset, we could very reasonably end up with a situation like the following:
The iOS application that we’re working on has a tab bar controller as it’s window’s root view controller. The tab bar controller contains an account view controller as one of its children (among others, which we’ll gloss over for now). The account controller displays a button that allows the user to navigate to a settings view controller. The settings controller displays a bunch of data from a property list located in our application bundle. In order to practice proper dependency injection, our application does the following in order:
Loads data out of the property list
Creates the settings controller by passing the .plist data into its initializer
Creates the account controller by passing the settings controller into its initializer
Creates the tab bar controller by passing the account controller into its initializer
You get the picture. If this sounds backwards to you – creating all of your object graph’s leaves first in order to facilitate creating the root – you’re not alone. In fact, this practice is often even referred to as “inversion of control.”
Clearly, this could get unwieldy. The client-side apps that we work on are long-running and stateful; extrapolating the above example to it’s logical conclusion, you’d basically create everything that your application might ever need upfront, regardless of whether the user actually navigates to those screens during any given session.
For a moment, let’s step away from iOS and consider how this might differ if we were working on a server-side application. Rather than our entry point being an application delegate that knows very little about what the user may do during the duration of the application’s run, HTTP server processes are generally spun up to handle only individual, stateless requests. If a user makes a POST request to delete a row from the database, the entry point knows exactly which objects will be needed in order to do so, and can easily create those and only those in order to facilitate the operation. Which order they’re created in and how they’re pieced together doesn’t seem like a big deal when there are way fewer pieces.
Back on iOS, this sounds like a nightmare in terms of the tedium involved in manually instantiating and wiring this massive object graph together. What we really want is some other object that knows how to create whatever instances the application needs, as well as all of their dependencies (and their dependencies, recursively). This object is called a dependency injection container.
(Perhaps now you understand why we give all of this a name.)
A container is configured with our object graph and instructed as to how it should go about creating each node. It knows which instances are singletons so that the rest of your classes don’t have to. Containers can even take shortcuts by looking at types. If you have a number of classes that all get instantiated with an instance of class X and the container is only configured with a single X instance, it can infer that this is the instance to be passed in. Less manual dependency specifying for you!
Once your container is configured, the root object of your graph is simply pulled out when your application launches, and voila. You no longer manually instantiate objects and pass them to other objects. The container takes care of all of this for you. It’ll feel really weird at first but you’ll get the hang of it.
Examples of popular dependency injection libraries on iOS include Objection and Typhoon. Configuring Typhoon looks something like the following:
If you think the process of configuring these containers looks procedural, you’re not wrong; there’s no reason that this needs to be done in “code” at all. In languages that support metaprogramming, dependencies can be specified by adding metadata to constructors or setter methods. Java’s annotations are pretty perfect for this, as shown by this example from Google Guice:
Many dependency injection frameworks (notably, Spring) even support wiring up dependencies using a markup language. This can end up looking something like the following:
You may have a knee-jerk aversion to the above, having dealt with some crummy XML API in the past, but try considering this approach without preconceived notions. Markup languages are actually really good for defining relationships between nodes in a tree structure. Separating the object graph definition from your actual class implementations ends up providing for a really nice separation of concerns. It’s a joy to implement a class’s logic without worrying about where it’s dependencies are actually coming from.
Yes, it’s a bit of a pipe dream, but imagine initializing your application with little more than:
That’s it. You pull a window out of your container, which has already configured it with a root view controller. The root view controller is already configured with its child view controllers, each of which is already configured with the various objects that they each need, each of which is configured with the objects that they need, and so on and so forth. You don’t initialize any of these manually.
In closing, I’m not suggesting that you rewrite your application to use Typhoon or Objection, or start rolling a dependency injection container yourself. Rather, I’d encourage you to take inspiration from these concepts when trying to strike a balance between the reusability of your classes and the overhead involved in implementing them to be that way. Selectively applying dependency injection to your application, use something like a factory, is a totally reasonable alternative to going “all in” and flipping your whole application inside out. With this approach, each factory serves as a mini DI-container: glue code that knows how to create instances of your classes using whatever they each need.
Constructors aren’t the only way to inject dependencies. Setters work just fine, but when possible, let’s pass dependencies during construction in the interest of keeping our classes immutable. ↩
There are exceptions to this, e.g. for trivial objects that you’d never reasonably want to pass in a replacement for. ↩