Update, as of February 2017: I wrote this article three years ago and probably get an email once a month asking if that's still how we're building our mobile app. The tl;dr is that it isn't, but our mobile Tech Lead David Langley wrote an update with why we've moved away from it and what we're doing today.
Selecting the right architecture for your mobile app is a pretty big deal. It will shape your daily workflow, frame the problems you face, and can be a huge asset or huge liability.
HubSpot's app is fully-featured. It's an analytics app, a social media app, an email app and a contacts management app (with more to come) all coexisting under one roof. As we set out to build this fairly complex app last summer, we knew we had to have an architecture that'd scale with it.
We actually build each sub-app as a fully complete standalone app, then use CocoaPods to integrate them into the main app.
In the screenshots below, you can see how each sub-app - Sources, Dashboard, Social Media, is actually both a standalone iPhone app as well as an app that can be brought into the menu of the main app.
This gives us a few huge advantages:
- Most critically, we're very easily able to ensure that the master branch of each sub-app is ready to ship, and can pull in specific versions of sub-apps in a snap.
- We spend a lot more time building and a lot less time merging. Each individual app's sandbox makes it very easy to iterate within the sub-apps and spend minimal time integrating with other apps. If you've worked on an iOS team of more than one, you've undoubtedly had gross .xcodeproj merges. While they are resolvable, they're a pain -- this lets us sidestep them almost completely.
- We are able to individually deploy each app if necessary -- this is amazing for doing usability testing on an individual app level. We could ship the app to our testers earlier before “glue” features like navigation were complete so we could get high quality, targeted feedback.
- Because user flows between sub-apps are only done with URL-based routes (more about this later), it means that routes are built-in and documented -- instead of searching through a pile of UIViewControllers for the right way to instantiate a particular view, there’s a well-defined route. This is useful when building meta-functionality like walkthrough tutorials or new push notifications.
This architecture has been a huge timesaver for us in building multifaceted iOS apps with a team of more than two people. Sound like your jam? Read on.
Learning from the Web
The inspiration for splitting up our mobile app into sub-apps came from the successes we've seen with HubSpot's web architecture.
HubSpot's web app architecture is built for development-speed and scalability. As my colleagues have written, we use a variety of tools and techniques to let us collectively deploy about 300 times a day. This is critical, as HubSpot's product suite consists of several loosely coupled but very different applications -- analytics, social media, email, blogging, and reporting tools.
On the web, we can build, test, and deploy small sections of the HubSpot app independently -- including backend APIs and jobs written in Java, front end CoffeeScript projects, and Python projects. Why not do the same for mobile?
CocoaPods: Use It.
CocoaPods, the excellent dependency management solution for iOS, is key in bringing everything together.
A multi-app architecture may be overkill for your use case, but CocoaPods certainly is not -- even if you're just bringing in a handful of 3rd party libraries for usage tracking, view components, or networking -- investing the few minutes to set it up is fully worth your time. The ruby gem-like syntax makes integrating open source components into an app nearly seamless.
Core libraries and shared resources like login, styling classes, and API/credential persistence and access are built as independent projects with Kiwi tests and a podspec file. We publish them to our private CocoaPods repository and include them in our actual fully-built applications. However, we take it a step further by building each sub-app -- all of Social Media, Email, or Sources, for example -- as a separate project with a podspec, then build them all into a single app using CocoaPods.
This means we can ship test versions of a single feature internally, and can move quickly with breaking changes in a single app without worrying about breaking the big build for other developers working in other unrelated sub-apps.
The Podfile for our aggregate app, therefore, looks like:
Gluing it all Together
To make it so that we don't have to provide information in the base app about the different menu items and routes each sub-app can handle, each sub-app provides a single class that implements an HSBaseApp protocol with a few methods:
An example implementation is:
We use routes to handle incoming push notifications, and we use the same scheme to link across sub-apps within the main app -- as is the case when we link to Contacts from Sources or Social Media, for example.
HSRoutingDelegate has a little bit of magic in it for passing around the currently active UINavigationController so we can push on top or create a modal in a route based on context, but otherwise it's a simple wrapper for JLRoutes' excellent block-based syntax.
What else can we do?
In the long run, we'd like to grow past our simple Kiwi test for some shared libraries and build in KIF tests so each version of a subapp passing Kiwi and KIF tests is built in a continuous integration setup and we can pick known good versions of each to ship for each release of the main application.
How do you organize large iOS apps with multiple developers? Is there a better way? We'd love to hear from you!