Dependency Injection in iOS and Android Apps

As apps become large, it often makes sense to build parts of the application as reusable components or modules. For large companies, either with several apps or several mobile teams, reusing the code owned by another team becomes a no-brainer.

For example, a mobile platform team might own networking and shared architecture components. A Money team would build and own payments components — this was my team at Uber! — and a Maps team would own all things mapping-related. Components and modules in the app would often map to the structure of the company's teams, mirroring the observation known as Conway's law.

With multiple modules, modules need to have a way to define their dependencies, both at a module and a class level. This concept is dependency injection, a form of inversion of control. It is a simple, yet often underrated concept in mobile development and one that is far more commonplace with backend and web projects. Here is a Swift code sample and explanation of the concept.

A major challenge with dependency injection is the amount of work it takes to modify or update dependencies if you do not use a framework to solve this. Even without using a framework, the time-consuming nature of this is a trade-off for clearer abstractions and good testability.

Dependency injection is a powerful tool for maintaining testable code consistently across the codebase. With dependency injection, classes that have multiple dependencies can be unit-tested by passing mock dependency classes when instantiating them. Larger, modular apps tend to introduce this concept one way or the other.

Without dependency injection: a Trip class would create their own Payments and Map dependencies. However, this makes unit testing the Trip class impossible without instantiating those two classes (and all their own dependencies). This makes tests slow, and it might make it very difficult to test scenarios that depend on Payments being in a specific state.
With dependency injection: the Trip class gets passed an IPayments and IMap class/interface. This approach makes it easy to unit test the Trip class, with mocked Payments and Map classes. Those mocks can be set up to behave in ways the tests need, with little effort.

Manual dependency injection — creating all interfaces, then hardcoding all dependencies — works fine when there are few such dependencies. However, as the number of components and the number of dependencies grow, maintaining and updating dependencies becomes more difficult. Spotting things such as circular dependencies also becomes tricky and using dependency injection frameworks begins to make more sense.

Android has a mature dependency injection framework that analyzes dependencies’ compile-time, called Dagger2. Google recently introduced Hilt on Dagger; a dependency framework built on top of Dagger, and Koin is becoming more popular with Kotlin.

On iOS, there have historically not been similar frameworks. At Uber, we built, used, and open-sourced Needle, based on similar concepts. We would have had trouble scaling the code with over a hundred engineers working on the same codebase without dependency injection. We used this tool to be explicit about all class dependencies, make unit testing easy — and non-negotiable for most of the code — and reuse components across teams.

This post is an  excerpt from my book Building Mobile Apps at Scale: 39 Engineering Challenges. Get the book here as a free PDF, until 31 May.

Featured Pragmatic Engineer Jobs

  1. Senior backend engineer & Senior frontend engineer at Rise Calendar (EU, €80-120K + equity) I'm an investor.
  2. Software engineer at Interact (Remote, $120-200K + equity)
  3. Full stack software engineer at Pallet (New York, Remote, US)
  4. Engineering manager & Software engineer at Orbit (Remote, global)
  5. Senior platfrom engineer at Qualified.com (Remote, US)
  6. Product engineer at incident.io (London, £70-130K + equity) I'm an investor.
  7. Tech lead/Senior fellowship at Commit (Canada): test startups for 3 months at a time!
  8. Founding full-stack engineer & Founding web3 engineer at Launch House (Remote, US)
Browse more senior engineer and engineering leadership roles with great engineering cultures, or add your own on The Pragmatic Engineer Job board.

Gergely Orosz

Writing The Pragmatic Engineer Newsletter and advisor at mobile.dev. Previously at Uber, Microsoft, Skype, Skyscanner.

Amsterdam, Netherlands