Improving Dependency Injection with View Controllers – Fooda
Fooda Hub

Improving Dependency Injection with View Controllers

Improving Dependency Injection with View Controllers

We recently rewrote Fooda’s iOS consumer application from the ground up. Our goals with the rewrite were to increase app stability, testability and team velocity. These goals, along with our long-term product vision for the app, drove our architectural decisions. One of the key architectural decisions we made was to not use storyboards in our app. There were many factors in our decision to not use storyboards like code reviewability and merge conflicts, but one that I want to describe today was the improvements to dependency injection.

What is Dependency Injection?

Dependency injection is a pattern whereby an object is provided their dependencies instead of creating dependencies or asking a factory for dependencies. One of the main benefits of dependency injection is independent classes that are easily testable.

Example

Let’s consider a simple view in the app that displays various menu items in the app. The MenuViewController is a detailed view of a Menu’s items. This view controller can be navigated to from a handful of different locations.

Before the Rewrite Using Storyboards

Before the rewrite, there was a storyboard segue between the view controllers that navigated to the MenuViewController and the MenuViewController itself. The MenuViewController looked like this:


The MenuViewController depends on the Menu being loaded from the local database, fetched with a network request or injected by some other class. There are a few issues with this approach:

  • A MenuViewController can be initialized and presented without the Menu ever being set. If the MenuViewController does not load or fetch the menu or if no other class sets the MenuViewController’s Menu, the view controller’s Menu will be nil and will fail to display the correct menu information. There are no compile time or run time checks forcing this. You could add an assert at run time to make sure the menu != nil, but it wouldn’t do us much good to crash the app in those cases.
  • The Menu is mutable. If we wanted to make the Menu immutable we would have no good way of doing so. If the Menu were to use “let” instead of “var”, we would need it to be initialized during the MenuViewController’s initialization.
  • The Menu is optional. In our case a MenuViewController must have a Menu, so it’s optionality doesn’t make a lot of sense. We could implicitly unwrap our Menu, but in general we try to avoid doing so.

After the Rewrite Without Storyboards

Removing storyboards allowed us to create custom inits for our view controllers.


This new pattern helped us improve our dependency injection. Now it is clear that to present a MenuViewController you need a Menu. Even better, if you try to initialize a MenuViewController without a Menu, the build will fail at compile time and Xcode will display an error telling you exactly what is wrong.

This new pattern solves our problems.

  • A MenuViewController cannot be initialized without a Menu.
  • We have the freedom to make the Menu mutable or immutable.
  • We have the freedom to make the Menu optional or non-optional.

Let’s say we now want to inject a MenuViewController with a “Menu Title” to display. We can update the MenuViewController:


When we try to compile after these changes we notice errors in every place the MenuViewController is initialized.

This is very convenient. Now we can confidently go through our code base and make sure we update all of the MenuViewController initializers. Before the rewrite, if we added a menu title to the MenuViewController we would not have had the same luxury.

Testing

Dependency injection improves testability because we can now easily configure classes with mock objects during unit testing. We can create mock Menu objects and inject them into the MenuViewController. If the MenuViewController were reading the Menu from disc or getting the Menu from a network call, testing would not be so simple. Decoupling the Menu from the MenuViewController allows for independent testable components.


Wrapping Up

Storyboards are a great tool, but come with tradeoffs. For us, removing storyboards helped improve dependency injection with view controllers. We can now write view controllers that are more easily testable and have less responsibility. There is also a greater control over the mutability and optionality of the view controller’s dependencies. An added benefit is this pattern leverages the Swift compiler. Debugging cycles are decreased because builds quickly fail to compile if a view controller is created improperly. We now ship with a bit more confidence.

If you love eating good food and solving challenging technical problems check out our careers page! https://www.fooda.com/careers

Jake Hergott is a Senior Software Engineer at Fooda. He fell in love with iOS development while studying CS at the University of Chicago. In his spare time, you can find him hacking around a golf course in the Chicagoland area.