Every developer has that moment of realization when you look at your code and barely understand what it’s suppose to do or how it got to be so many lines long. This doesn’t mean you have to completely start from scratch, but it’s important to be open to refactoring parts of your code. The point of refactoring is to make your code more understandable, testable and/or modern.
This is the first post of many where I’m going to share the refactoring process for my app, We Read Too, along with the patterns, libraries & tools I learn along the way.
When I first started learning iOS development four years ago, the design pattern I learned was MVC which stands for Model View Controller. The following diagram shows the user interaction an MVC pattern:
If you’re learning iOS and rely on Apple’s documentation you’ll notice that they follow the MVC pattern. Over time as your app grows it’s easy to write all the logic for your app into the view controller which leads to extremely large classes that are hard to test and hard to understand (commonly known as the Massive View Controller).
A different design pattern that has grown in popularity recently is MVVM which stands for Model-View-ViewModel. The view model allows the business logic to be decoupled from the view controller. That way our view controllers stay small and focused on the UI. The view model usually doesn’t import UIKit and has no knowledge of the view or view controller. This allows the view model to be unit testable. The model is usually a struct or simple class that represents the data.
Following the MVVM pattern doesn’t mean your code will be perfect. Just as you can have massive view controllers, you can also have massive view models. You don’t have to use one design pattern throughout your whole app, it depends on what’s works best for the functionality you’re trying to implement.
In the MVVM diagram above you there are terms like “data and user action binding”, “updates”, and “notifies”. How do you actually implement those relationships in MVVM?
Many developers model those relationships through reactive programming. Reactive programming is a paradigm that has existed in software development for decades that allows developers to write declarative code focused on asynchronous data streams. Check out this great documentation written by Andre Staltz that goes into more explanation. Reactive extensions (Rx) are how reactive programming became popularized especially in mobile development.
Why am I learning Rx? Users expect modern apps to be responsive. Thinking of everything as streams with inputs and outputs makes sure there is always a reaction to any change that comes in whether it’s a user action or network request. Reactive programming uses an Observer design pattern where a subscriber “listens” to a stream (the observable). For more on reactive programming and RxSwift specifically, watch this great talk by Shai Mishali or read the documentation.
Red, green, refactor method for testing
I wrote earlier about how testable view models are, but how will we test them?
I’ve decided to use the Red green refactor method created by John Shore. This ensures I’m following a TDD mindset when writing my code. The tests are written before the logic is completed and you write the logic to conform to the tests. Once the tests are passing you find ways to improve your code without breaking the tests. Kickstarter follows this method in their codebase and you can watch this video to see how it works. They use ReactiveSwift and have written their own test helpers. I’m using RxSwift and found a Cocoapods library that has been very useful called RxExpect.
Implementing the refactor
Now that we’ve laid the ground work for understanding the basics of what MVVM and reactive programming are, let’s get into beginning implementation of refactoring my app We Read Too which is written all in Swift.
We Read Too is a book directory app with the following features:
- a collection view that displays the books filtered by age category
- search by author, title or description
- a detail view that shows the specifics of a book with data pulled from Goodreads and allows the user to share the book or view it on Safari/Goodreads
- a suggestion view that allows the user to send a suggestion of a title that should be added to the directory.
The database (recently moved to Firebase) contains all the books and can be updated at anytime with new data or changes on existing data.
For the refactor I started with the suggestion view because it is the least complex and will help me build my comfort level with writing code in MVVM reactively. In later posts I’m going to cover how I refactor the other parts of the app and how I improve the code over time.
I decided to adopt the functional input-output approach to view models inspired by Kickstarter. The inputs and outputs are protocols which make easier to test and very readable.
The SuggestionViewModel adopts all three of these protocols and is a final class so that it cannot be inherited. For each of the inputs there is an associated BehaviorRelay subject (formerly known as Variable) which is used to transform the observed inputs into outputs.
Before we implement the logic for the outputs, we write our tests.
The first three tests above are all testing the functionality of the submit button which is only enabled when there is text present in the title and author text fields in our view. The last test is for the alert message after the submit button is pressed.
Logic for the view model
Now that we’ve got our tests, we have to write the logic to get them passing. We do this in the init method of the view model.
First, we combine the streams from the title text field and the author text field using the combineLatest operator which makes a tuple of the emitted values. We use the sample operator which will emit the most recent events from a given Observable. In this case we need the alert message when the submit button is pressed. Using a helper method isPresent which checks the length of the given text, we filter so that we give the alert message only when the text is present. For more on Rx operators check out this post.
Lastly we need the observable for knowing when to enable the submit button. When the view loads the submit button should not be enabled and then we want to monitor the form data and if isPresent returns true then the button will be enabled. With these two values merged we’re listening for both values and you can see that in how our tests are implemented above. Now when we run our tests, they pass!
View controller binding
Now that we’ve got our logic passing our tests, it’s time to actually connect it to a view. We create our view controller with two UITextFields, a UIButton, and a reference to a view model. Then in viewDidLoad we add targets that are initialized with the following selectors:
This makes sure all the logic is abstracted from our view controller. We pass any user interaction in as inputs to our view model. Next we need to use the observables from our view model’s outputs.
For the alert message observable we create an alert controller for every value that’s emitted and display it. For the submit button we bind our view model output to Rx control property for UIButton, isEnabled.
Lastly, we trigger our view model input for viewDidLoad.
This is only the start! We’re not finished with refactoring the suggestion view. The only functionality we have set up is ensuring that the user has inputted text into the the title and author fields so we can enable the button that would trigger sending their suggestion.
We need to add another field to the form. The user has to pick a category their book suggestion matches to; picture book, chapter book, middle grade or young adult. We’ll do this with an Swift enum and UIPickerView that’s connected using RxSwift.
You also may notice that the send button logic is not actually finished. We’re not actually sending anything! And where’s our model? Next we need to create the model object from the form data and send it off to Firebase.