This article is all about how to use RxSwift with MVVM. RxSwift has been a hot topic in the swift community for a few years now, but somehow I’ve managed to avoid it. It took me a while to switch my brain to the state that everything is an Observable. I also had some trouble at first to figure out when to use
PublishSubject and how I should bind values to UI components. I'll cover all these topics in this blog. I'll show how to use RxSwift with MVVM,
UITableView with RxSwift, how to write a network layer and how to test a RxSwift app. I won't go through the MVVM pattern from the ground up but after you've read the series, you'll be able to use RxSwift with MVVM. In case you want to learn the basics of the MVVM pattern, I suggest that you check out my earlier post MVVM with Swift application.
I’ll cover these topics by showing how to write an application called Friends. You can get the complete source code for the app from GitHub, just check out the RxSwift branch. I wrote the app using MVVM without RxSwift 18 months ago. Now, I thought it would be nice to refactor it and see how it looks like when using RxSwift with MVVM. Friends is an iPhone app that downloads a list of friends and displays them in the app. You can also add, remove and update friends. So it is a simple app with just enough complexity to cover many of the basic needs of an iOS app. It is also a great place to start learning how to use RxSwift with MVVM! Btw, the backend is written in swift using Vapor!
In this first part of the app, I’ll show the basics of using RxSwift with MVVM. Setting up correct CocoaPods. Binding data between the ViewModel and the view. Using
UITableView, showing a loading indicator and how to displaying an error to the user. We'll first go through the ViewModel side of the implementation and then the view.
At first, we need to add RxSwift to the project. In this example, we’ll use CocoaPods but you can also use Carthage and Swift Package Manager. Check the GitHub repo for more info.
In the pod file, you’ll need to add:
RxSwift adds the basic library including
PublishSubject etc. RxDataSources includes
UITableView & UICollectionView related reactive libraries. RxSwiftExt helps with binding the observables straight to the UI-Components.
We’ll also add all the libs for the testing targets that we have. And after we are done editing the Podfile, we’ll need to run
pod install in the terminal.
In the first part of how to use RxSwift with MVVM, we’ll be concentrating on the first view of the app:
The first view has a table view that shows all items loaded from the backend. To present all this, we’ll dive in to
FriendTableViewController. Let's start with the ViewModel.
ViewModel is the module that makes the data ready for the view (which in this case is the FriendTableViewController). ViewModel is also the place where we put most of the business logic. I say ‘most’ since we should try to avoid a situation where the ViewModel turns into just another place where we dump all our code. You might have heard about the MassiveViewController problem, and we don’t want to end up with a MassiveViewModel either. So if something can be refactored to its own module, we should always try to do that. But I am sure that you are eager to take a look at the code, so let’s check it out!
At first, we’ll import RxSwift so that we have the tools provided by the library available for use. Right under the import statement, there is an enum
FriendTableViewCellType. This enum contains all the cell types our table view can show. The types are normal cell, error and empty cell. Normal cell presents the data for a friend. Error cell shows error information to the user and empty cell is shown when there is no data on the server. We'll check how to use them more specifically in the view controller codes, but for now, this is all we need to know.
Then we can start with the RxSwift stuff! At the bottom of the code block, you can see two variables that are defined as
Variable is a type provided by RxSwift. It is the simplest type to use, so it's a good place to start observing the RxSwift observables.
Observables in RxSwift change their state by emitting
onCompletedevents. However, with
Variable, you use the
value property to set up a new value. When we want to subscribe to a
Variable, we need to use the
asObservable() function. After all that is set up and whenever the value is changed, the observer is notified. Furthermore,
Variable is guaranteed not to emit error so it makes things a bit simpler to handle on the view controller side.
Here we have defined
Cells contains the cellViewModels, which are used when constructing the cells. The value for the array is created every time a valid response containing friend data is received from the server, and the value only changes when a new request to the server is initiated. The
cells is a private member so that the cell value can only be changed by the ViewModel. This way there is no chance that the value is accidentally changed on the view controller side. As a pair for the private
cells variable, we have a
Observable. It is a computed property and it returns an observable for the
cells variable. This is the variable that we'll use later on the view controller side to bind the cell value to the tableView.
loadInProgress variable is used whenever this class is performing a network request. It is also defined as private, just as the
cells variable we discussed above.
loadInProgress also has a public computed property
onShowLoadingHud. It is defined as an
Observable and it returns the
loadInProgress as observable. This is the variable that we'll bind to on the view controller side to present the loading hud. Notice the
distinctUntilChanged which means that the value is only emitted if it is changed.
Now let’s check the
onShowError which is defined as a
PublishSubjectreceives information and then publishes it to the subscriber. Here, the subject that is received is defined as
SingleButtonAlert and that is also what it will publish to the receiver. The value is emitted the same way as with all observables, using the
onNext() function. So using
PublishSubjectis very similar to using variables, but instead of setting the
value, we’ll call
onNext() instead. I think we could have also used simple
onShowError, but I wanted to use
PublishSubject to cover a bit more types from RxSwift.
SingleButtonAlert is a type that defines a title, a message and a button title with an action to present an alert type to the user. The code is pretty self-explanatory and you can check the class here.
The last two members here are
AppServerClient is a component which does all the requests to the server. All the codes are available, but I'll dive in to the network layer in another post.
The last but one of the most important variables is the
DisposeBag. To destroy an
Observable, we should always call
dispose() to it. It would be very hard work to handle the disposing manually, so RxSwift equips us with the
DisposeBag. When creating an
Observable you should always add it to disposeBag by calling
.disposed(by:) to it. This way when the
disposeBag is deallocated, it calls
dispose() to all the observables, which takes care of the memory they've used. So inside the view model, we define our own disposeBag. When the view model gets deallocated, all the observables are deallocated as well.
With these simple variables, we can already see that the data binding between the ViewModel and View is very simple! On the view controller side, we’ll only need to subscribe to these variables and data binding is completed. There is no need to use any other data binding techniques (such as
Bindable we were using in the 'How to use MVVM' tutorial) or delegation since RxSwift does it all for us! How cool is that! 🙂
As mentioned, we’ll be using
AppServerClient for the server requests. Every time a request is sent to
AppServerClient, it returns an
Observable. Let's see how this looks when we are getting a list of friends from the
So we have defined a function
getFriends(). The first thing to do is to present the loading indicator to the user whenever we are calling this function. This is done by setting the value for
loadInProgress variable to true. After that, we'll call
getFriends() from the
appServerClient and subscribe to the observable it returns. Now, we'll start to listen for the different values it can emit.
Observable receives a new value, it sends an event containing the value. We could subscribe to the event, then go through all the states that the event can have and unwrap the value inside the event. But there is an easier way. RxSwift also provides subscribe functions we can use for the different states. So instead of always checking which event was emitted, we can directly define the blocks for different states, as we've done above. The events can be
Here we don’t need to free any memory when the
onDisposedis called, so we only handle the
onError states. Inside the
onNext, we'll first set the
loadInProgress to false. Then, we'll check that the friends array we received contains items. In case it is empty, we'll set
[.empty] cell as the value for the friendCells. If we have a value, we'll use compactMap to convert the friend items to cell view models and set the value for the
onError, we again hide the loadingHud. Then we'll set the
[.error] and for the message we'll use an extension to convert the provided error value to the correct error message:
The last thing we need to do is to add this observable to the disposeBag so that it gets disposed when the view model is deallocated.
Now we have covered the view model. Let’s move on to the view controller side.
In the view controller, we’ll use the
RxDataSources for the tableView handling and
RxSwiftExt for binding the observables directly to the UI-Components. In this part, we'll also concentrate on presenting the loadingHud and errors to the user. We'll also bind the friendCells values to tableView and see how we can delete a friend.
At the beginning of the class, we’ll notice the view model definition. This is where we’ll also create the view model since this is the first view of the application.
viewDidLoad, we'll call the preparing functions:
First, we’ll prepare the view model by binding all the values in the
bindViewModel(). Then we'll set up cell deleting and tapping. After those function calls, the view is completely setup and we can use the
getFriends()function to start downloading the data.
Next, let’s check the
At first, we’ll bind the
friendCells to tableView. As you might remember,
friendCells is a computed property of
cells and it returns the
cells variable. After that, we'll call
bind(to:) and give the
tableView.rx.items as parameter.
tableView.rx.items is a binder function working on observable sequence of elements, such as
Observable. Binding creates an ObserverType which subscribes it self to the observable friend array. It also sets it self as the
delegate for the tableView. Whenever a new value is received from the
friendCells, tableView reloads its content.
RxSwift calls the closure that we have defined for each item. Here is where we can configure the cells. Element contains the enum value defined on the view model side and index is the index of the element. Since our view only has a single section, we’ll convert the index as indexPath, using section value zero. Then, we’ll use switch to check if the element contains
In the normal case, we’ll deque the cell from the tableView and set the
viewModel received as the cells
In the error case, we’ll create a default
UITableViewCell and set the provided error message as the
textLabel?.text. In the empty cell's case, we'll do the same as with the error case, with the exception that we'll use hard coded "No data available" as the
Now that we have handled the data source and delegation of the tableView, all that is left is to make sure that this observable is disposed of using the disposeBag, when View is deallocated.
So what do you think? When you compare this piece of code to the normal way of setting up a data source and implementing all the tableView delegate functions, which one do you feel is easier?
Now, let’s see how to handle the selection of a cell by checking the cell deleting!
Cell deleting is also handled by a function provided by the rx extension:\
Again, we can access the helper functions for tableView using the .rx. Whenever the delete event gets called for the tableView, also the
modelDeleted gets called. So inside the function, we'll just check that the cell type is what we expect, and call the viewModel.delete function with the correct ViewModel as a parameter. Since the friend application updates the cells by reloading the content from the server, we'll also deselect the row here to make the UI work smoothly.
Selecting a cell is done with
modelSelected and the handling is very close to cell deleting. I hope you can figure it out by yourself just by looking at the code.
Now the only thing left for us in this part is to present an error and a loading hud! Isn’t that exciting or what? 🙂
bindViewModel(), we also start observing when to present a loading hud and, if needed, an error note. We could do it in the same way as when we were listening to the observable states when receiving friends from the network client. But since the error handling isn't that complex here, we can do this in a simpler way like this:
First, we’ll get the
onShowError and map the received event. Whenever we receive the onNext event, we'll access the emitted
SingleButtonAlert value with the
$0 and present the error dialog. Next we'll call the
subscribe to start listening to the events, and finally, we'll set the disposeBag to dispose the observable. Next, we'll do the same thing for the
And with that, the first part of this RxSwift with MVVM series is completed. In the next part, we’ll see how to validate input data from multiple
UITextViews and how we can provide data back to the presenting viewController. We'll also check how to bind data to back and forth UIComponents between the viewModel and the View.
If you have any questions, comments or feedback, you can comment below or contact me on Twitter! Also if you liked the post, I hope you’ll share it with some of your friends, I’d really appreciate it! Thanks for reading and see you next time, my friend!