Redesigning Food at Work
Turning Challenges into Opportunities
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.
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.
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, 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:
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.
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.
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.
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.