Back in 2014, we wrote a post about how we were using CocoaPods to Modularize our iOS app. With our latest iOS app we released a few months ago, we went in a different direction - here's why.
The modular app approach proved very useful early on when we had multiple independent tools that we wanted to find product-market fit for. Each engineer was given a clear mission to focus on. They had to think less about the pieces that glued the overall application together and just provide a clear interface via routes where there were integration points between sub-apps.
For a team like ours that was quickly iterating on a product and building mobile versions of a variety of independent tools, this approach proved very useful. There have however been a few caveats and learnings we have had along the way with this approach:
Firstly, splitting the app into sub-apps didn’t prevent us from having to synchronize dependencies when bringing the app together to ship. In the case there were breaking changes across dependencies, we’d still need to resolve those manually.
Secondly, there is an overhead associated with maintaining multiple Xcode workspaces for each sub-app (multiple project level settings, multiple podfiles, multiple Xcodes open on your desktop). When code changes happen to span more than one of your sub-apps, to test the changes locally, you have to set your pod to resolve locally. To avoid this overhead we moved to using a single XCode workspace and a single podfile, both of which had multiple app targets (one for each sub-app).
This meant that developers only ever had one workspace they needed to work from and no more setting up of local pods. It also meant you were managing your pods from a single file, also mitigating somewhat the pain of synchronizing dependencies across sub-apps.
As you can see in the above screenshot, we still have the separate apps, that we can build, run and deploy independently.
Finally, and most importantly, we underestimated the number of cross-app user flows there would be. We ended up having a lot of cases where users wanted to navigate across sub-apps and each instance there would require a lot of communication and collaboration across developers. Independent sub-apps tacitly encourage slightly different approaches that created a bunch of friction for us as we integrated the apps.
Three years has passed since we adopted that approach and HubSpot has evolved. There are now three products rather than one, each with its own set of tools. Those tools are also a lot more integrated and the integrations are much richer. They are very valuable individually but are even more valuable when all used together.
Given that tight integration, it didn’t really make sense to keep that approach. Our customers think about the HubSpot Growth Stack as a single entity and only a limited set of the tools in the platform really make sense on mobile. We now have a single app that provides just the tools that the users need and none of the ones they don’t.
As our app came together in a much more integrated way, we needed to re-evaluate and adopt an approach to match how the business was changing. A lot of apps with more integration means more state and a lot more complexity. We now use a Redux-like architecture which provides a single source of truth (a “store”) for the large quantity of data we now have in the system. The advent of reactive technologies like ReactiveX and Anvil really complement this kind of architecture.
Redux means our application scales in a very sane and predictable way and makes it much easier to think about the state of the system. While the reactive UI tools mean we can still move really quickly building new features and UI, we can avoid odd data issues by keeping everything in the single Redux store.
So what approach is right for you? Maybe it’s a modular approach with clearly defined interfaces? Maybe it’s Redux style architecture with a single state tree? One also doesn’t preclude the other – you could imagine a Redux style architecture where the modules of your application each contribute to part of the state tree. The important point is the approach you take should match the mission of your team and your business.