Today I am going to show you to build a simple SpaceX Launches iOS app with SpaceX open-source APIs using MVVM and RxSwift.
If you have used MVC structure (Model, View, Controller) in iOS development before, you will know as the project grows bigger, the ViewController is likely to have a “Massive View Controller” problem. It means that majority of the code is written in ViewController, causing it to have hundreds if not thousands of lines of code for business logic and presentation logic. If any bugs occur, it becomes very hard for you to debug or pinpoint the issue with this massive amount of code all in 1 file.
Here is where MVVM can come to your rescue. The MVVM pattern consists of three layers:
- Model: The data that the app uses.
- View: The user interface’s visual elements. In iOS, the view controller is inseparable from the concept of the view.
- ViewModel: Updates the model from view inputs and updates views from model outputs.
MVVM offers some advantages over the conventional Model-View-Controller (MVC) approach:
- Reduced complexity: MVVM makes the view controller simpler by decoupling and moving a lot of business logic out of it.
- Expressive: The view model better expresses the business logic for the view.
- Testability: A view model is much easier to test than a view controller. You don’t have to worry about the presentation view while testing the business logic.
We are going to build a simple SpaceX Launches iOS app, we need to show a list of SpaceX launches in the past 3 years on the first screen with details like launch number, details, date and indicator for upcoming launches.
First of all, let’s create the project by choosing App.
After that, you can name the project as you like, I will just name mine as SpaceXLaunch. Then, remove ViewController.swift file and its Scene in Main storyboard, add the Navigation Controller into the storyboard and check “is Initial View Controller” under Attributes Inspector.
Then, create the LaunchListViewController.swift file with basic ViewController set up and type it as the class name and storyboard ID in the RootViewController Scene’s identity Inspector to bind them together.
Then, add tableView and table view cell into LaunchListViewController in the storyboard. After that, create LaunchListTableViewCell.swift and connect to the table view cell by typing LaunchListTableViewCell in the identifier.
Then, we need to create LaunchListTableViewCellViewModel for LaunchListTableViewCell.
There is some code above, let me explain one by one:
- LaunchListTableViewCellViewModelType is a protocol I created to declare the variables and functions we are going to use in LaunchListTableViewCellViewModel class. Declaring the variables and functions this way allowed us to be able to use dependency injection later to run unit tests, I will explain more in the next article when we implement unit test.
- LaunchListTableViewCellViewModel is the class that inherit and implement the definition of the variables and functions created in LaunchListTableViewCellViewModelType protocol.
- This is an init function of LaunchListTableViewCellViewModel class. When using this function, we are required to pass in Launch model, then we will convert it to LaunchListTableViewCellViewModel inside this function.
- Later in LaunchListViewController.swift, configure function will be called and used to assign value to viewModel, finally populate the information in LaunchListTableViewCell.swift after calling the SpaceX API.
You might be wondering what BehaviorRelay is. It is actually a Relay variable in RxSwift that only accept and relay the next event. In the summary, when you use accept keyword, you are passing a value to it, it will then broadcast to whichever places that are actively subscribing to this variable, then those subscriber functions will get triggered and run. We will add the subscriber in LaunchListTableViewCell next.
In MVVM, you are required to use data binding to allow ViewModel to communicate with ViewController. In this project, we are going to use RxSwift to achieve that. BehaviorRelay is one of the commonly used components in RxCocoa. You can refer to the table or RxMarbles to understand different components in RxCocoa.
Let’s explain the code one by one.
- DisposeBag in RxSwift helps to dispose of the active subscription, else it will cause the problem of memory leak and potentially crash the app if the subscription is not disposed of correctly. So, we create disposeBag variable to help us to monitor and control the lifecycle of the subscription in the table view cell.
- Then, we add the viewModel variable with type, LaunchListTableViewCellViewModelType and set LaunchListTableViewCellViewModel() as default variable.
- We do any further setup of the view in SetupViews() function.
- We set up the listeners or the subscriptions in this function when the cell is initialised.
- To prevent displaying the wrong data while reusing the cells, we disposed of the subscription by setting disposeBag to nil in prepareForReuse function.
- Then, we initialise DisposeBag again in setupListeners() to resume a new subscription.
What are asDriver(), drive() and dispose(by: disposeBag)? You might ask.
- asDriver() – we convert the variable from BehaviorRelay to Driver. Driver is the observable sequence that runs on the main thread and mainly used for updating the UI components in the view.
- drive() will update the UI component whenever the value changes.
- dispose(by: disposeBag) bind the lifecycle of this observable or subscription to the disposeBag variable. Its subscription will get terminated whenever disposeBag is set to nil.
After that, lets create APIService class to get the information from SpaceX Open-Source API.
- APIServiceProtocol declares the functions that APIService is going to implement.
- We utilise the power of mixins in Swift by giving the functions in APIServiceProtocol with default parameters.
- Finally, we create APIService class and implement the functions shown in APIServiceProtocol.
- In the init function, we pass the baseUrlString and sessionManager when initialising APIService class, this implementation will help us a lot in adding the unit test later.
- fetchLaunchesWithQuery will get the launches data from SpaceXAPI. As the API is using GraphQL, I added the parameters to query the data based on the startDate and endDate and in ascending order.
- fetchRocket fetches the rocket detail information from the API.
- Other helper functions to facilitate Dependency Injection and support unit testing later.
For more information on the SpaceX public API, you can check their Github repository.
Then, create LaunchListViewModel.swift class to store the business logic of the screen.
There is some code in the gist above, don’t worry, I will explain one by one what each component does and what is its purpose.
- launchViewModels is a list of LaunchListTableViewCellViewModel classes that populate the information in the TableViewCell on LaunchListViewController.
- notifyError is a variable for us to inform the view controller to present an alert with the error message to notify the user when something is wrong.
- fetchLaunchesWithQuery as the name tells is a function we called to get the SpaceXLaunches from the SpaceX Open-Source API.
- apiService is a variable of type APIServiceProtocol that we created to store the apiService we use when calling LaunchListViewModel. APIServiceProtocol serves the same purpose as LaunchListViewModelType, I am going to show its usage in the next article when we are implementing the Unit Test for this project.
- This is an init function of LaunchListViewModel class. By default, apiService will be APIService class but we can inject a MockAPIService when we implement the unit test in the next article. It also calls fetchLaunchesWithQuery to get the launches detail.
- processFetchedLaunches is a helper function to help convert launchResponse into launchViewModels.
- Like 6, convertLaunchesToLaunchListTableViewCellViewModels is a helper function to convert Launches into LaunchListTableViewCellViewModels.
Finally, lets connect tableView in storyboard to LaunchListViewController.swift and register LaunchListTableViewCell in LaunchListViewController.
- Show alert with an error message when notifyError has accepted an Error object.
- Return number of rows based on the total number of launchViewModels.
- Assign and return LaunchListTableViewCell when refreshing table view cells.
- Navigate to RocketDetailViewController when the user taps on any of the cells. The steps to create Rocket Detail screen is similar to LaunchListViewController, so I won’t go into details to explain that. But, it is included in the project and feels free to comment below to ask me any questions that you have.
That’s it. Here should be your result:
You can view the full project on Github.
I also a wrote an article to explain how we add unit-testing into this project, feel free to view it here, Implement Unit Testing in a simple iOS app with MVVM and RxSwift
Feel free to comment to give your suggestion or ask me any questions. Thank you.