In Mastering MVVM With Swift, we refactor a weather application, Cloudy, built with MVC to use MVVM instead. One of the most common questions I receive is how to build Cloudy from scratch. This series is an answer to that question. We build a weather application that is inspired by Cloudy. The application is aptly named Rainstorm.
Few things are more enjoyable than setting up a brand new project. Fire up Xcode and choose New > Project from Xcode's File menu. Select the Single View App template from the iOS section and click Next.
View controller containment is an indispensable pattern in iOS projects. Several key components of the UIKit framework take advantage of view controller containment, including the UINavigationController class, the UITabBarController class, and the UISplitViewController class. As I mentioned earlier in this series, view controller containment is a pattern I adopt in every iOS project and Rainstorm is no exception.
In the previous episode, we laid the foundation of Rainstorm's user interface. Before we continue building the user interface, I'd like to know what the weather data the application will use to populate its user interface looks like. In this episode, we fetch weather data from Dark Sky. The Dark Sky API is very easy to use. Create a Dark Sky developer account if you'd like to follow along. It's free and it only takes a minute.
Project hygiene is very important in my opinion and it immediately shows you what type of developer you're working with. The Xcode theme I usually use for development highlights string literals in bright red, showing me when a string literal has made its way into the codebase.
The root view controller is currently in charge of fetching weather data from the Dark Sky API. Having a view controller that performs network requests isn't uncommon if the project adopts the Model-View-Controller pattern. The current implementation of the RootViewController class isn't complicated, but that can change as the project grows and evolves. It can eventually lead to a fat and overweight view controller. That's something we absolutely want to avoid.
In the early days of Swift, working with JSON was clunky and inelegant. Most developers relied on third party libraries that simplified this tedious task. The Swift team was aware of this gap in the Swift standard library and, after focusing on the foundation of the language first, they introduced the Codable protocol in Swift 4. The Codable protocol is a powerful solution that makes working with JSON quick, easy, and intuitive.
In the previous episode, we parsed the JSON response of the Dark Sky API. It's time to integrate the DarkSkyResponse struct into the Rainstorm project. Remember that we didn't handle any errors in the playground. That's also something we tackle in this episode.
At the end of the previous episode, I mentioned that I'm not quite happy yet with the implementation of the RootViewModel class. It passes an instance of the DarkSkyResponse struct to the RootViewController class via a completion handler. The RootViewController class still knows too much about the weather data and its origin.
Before we can populate the day and week view controllers, we need to create a view model for each view controller. The view models transform the weather data into values the view controllers can present to the user. This is straightforward if you've watched the previous episodes. There are a few details that are worth pointing out, though.
It's time to create the user interface of the day view controller, the topmost child view controller of the root view controller. In this series, I show you several techniques for building user interfaces. Each of these techniques has its pros and cons. To create the user interface of the DayViewController class, we take advantage of Auto Layout and storyboards. This approach is ideal for building static user interfaces.
The user interface of the day view controller is ready to be populated with weather data. Before we populate the user interface, I'd like to clean up the implementation of the DayViewController class. As I mentioned earlier in this series, object literals are very often an opportunity for improvement. In this episode, I show you how we can improve the implementation of the DayViewController class.
Everything's in place to populate the day view controller. Open DayViewModel.swift. Press the Option key and select DayViewController.swift to open it in the Assistant Editor on the right. Implementing the DayViewModel struct is surprisingly straightforward. We first need to inspect the DayViewController class and figure out what type of data the view model needs to provide to populate its user interface.
Because a view model isn't or shouldn't be coupled to the user interface, it's easy to write unit tests for a view model. I'd like to illustrate that in this episode by writing a handful of unit tests for the DayViewModel struct. If you're new to unit testing, then this episode is an excellent start.
In this episode, we populate the week view. We build the user interface and implement the WeekViewModel struct and the WeekViewController class. Let's start with the user interface.
This episode should look and feel familiar if you've watched the previous episodes because we are about to write unit tests for the WeekViewModel and WeekDayViewModel structs. Let's start with the WeekViewModel struct.
The application we're building is shaping up nicely. It's time to make it a little bit more functional. The application is currently only capable of fetching weather data for a predefined set of coordinates. This severely limits the appeal of the application.
In this episode, we use the Core Location framework to fetch the location of the device. The application will use that information to show the user weather data for their current location. That will make the application much more useful.
In the previous episode, we asked the Core Location framework for the location of the device and used that location to fetch weather data. That change has made the application much more useful.
At the end of the episode, I mentioned that the current implementation introduces a subtle problem. The RootViewModel class is responsible for fetching the location of the device, using the Core Location framework. The implementation of the RootViewModel class depends on the Core Location framework. Because Core Location is a framework we don't control, it impacts the testability of the RootViewModel class. Remember what I said earlier in this series. To write a fast and reliable test suite, you need to be in control of the environment the test suite runs in.
In this episode, we use protocol-oriented programming to decouple the RootViewModel class from the the Core Location framework. We used protocol-oriented programming earlier in this series to add a layer of abstraction. It's a powerful pattern that is easy to adopt.
It takes time to become familiar with a new programming language and some features don't always immediately make sense. Enums are a joy to work with in Swift, but it took some time before I found a use for associated values. In this episode, we refactor Rainstorm by taking advantage of enums with associated values. We remove unnecessary optionals and make the code we write more readable.
The application fetches the location of the device on launch and it subsequently asks the Dark Sky API for weather data for that location. That's fine, but it isn't sufficient. What happens if the user backgrounds the application and opens it several hours later. The weather data may be out of date and, if they're traveling, the location of the device may have changed.
I'd like to give the user the ability to manually refresh the weather data. While this isn't strictly necessary since the application refreshes the weather data every time the application is opened, it gives me the opportunity to show you how to implement pull to refresh, a common design pattern in mobile applications.
Earlier in this series, we wrote unit tests for the DayViewModel, the WeekViewModel, and the WeekDayViewModel structs. Writing those unit tests was fairly straightforward. Unit testing the RootViewModel class is a bit more challenging for a number of reasons.
The RootViewModel class asynchronously fetches the location of the device and weather data for a location. To unit test asynchronous operations, we need to take a different approach.
Developers that are new to unit testing often wonder what they should unit test. Unit testing is a type of black-box testing, which means that you don't care how the entity under test does what it does. From the moment you start writing unit tests, you need to stop being a developer and take on the mindset of a tester. What do I mean by that?
We wrote the first unit test for the RootViewModel class in the previous episode. It's true that unit testing asynchronous code is more complex than unit testing synchronous code, but I hope that the previous episode has shown that it isn't that hard.
The interface of the RootViewModel class shows that we only need to unit test the initializer and the refresh() method. I explained earlier why I don't unit test the initializer and, in the previous episodes, we unit tested the refresh() method. Does that mean that the test suite now completely covers the RootViewModel class? The answer is no. This is a common mistake developers make. In this episode, we collect code coverage data to expose the gaps in the test suite.
We're currently only unit testing the happy path. In this episode, we expand the test suite and I show you how to simulate failures. It isn't easy to accurately test failures in the real world and that emphasizes the importance of a robust test suite. Let me show you what I have in mind.
In the previous episodes, we used dependency injection to easily replace objects with mock objects when the unit tests are executed. It's a solution that works fine and it can be applied in a wide range of scenarios.