All posts
iOS

RxSwift by Examples
#3 – Networking.

Łukasz Mróz

Łukasz Mróz

Edit 18.01.2017: This post was updated to Swift 3.0, RxSwift 3.1 and Moya 8.0

As we dive more and more into the wild world of functional reactive programming, today we will talk about networking and connecting our data with UI. We will also make sure everything is (as always!) simple, smooth and nice (guaranteed)! To feel comfortable with pace of the tutorial, make sure you’ve checked out part #1 and #2 from our series!

For Rx there are many networking extensions, including RxAlamofire and Moya. In this tutorial we will focus on Moya, which I really like.

Moya

Moya
Moya is an abstract layer above all the networking stuff you would normally need to take care of by yourself. Basically, by using this library we will make our connection with API in no-time, and with extensions to it that consists of RxSwift and ModelMapper, we will have full package for our journey.

Setup

To setup Moya, we need a Provider, which consists of setup for stubbing, endpoint closure etc. (more of it when we will do testing). For our simple case we actually don’t need anything at all, so this point is just initializing the Provider with RxSwift. Then there is the second thing we need to do, which is the Endpoint configuration – an enum with our possible endpoint targets. It is really simple! We just need to create enum that conforms to TargetType and we are done. But what is this TargetType? It is a protocol that has url, method, task(is a request/upload/download), parameters and parameterEncoding (so really basic stuff for URL requests), but there is also one more thing! The last parameter we need to specify is something called sampleData. Moya is really heavy relying on tests. It treats test stubs as first-class citizens. But more about testing in Moya and RxSwift in the next chapters. The only thing you need to know for now is that for every request we should specify sample response from the server.

Example

That’s right, straight to the example app! We skipped definitions because there isn’t much theory to tell before the example, but we will learn much step by step during the coding part. In the example we will try to get issues for specific repository using GitHub API. But to complicate things a little bit, first we will get the repository object, check if it exists, and then by chaining the requests we will get issues for that repository. And we will map everything from JSON to objects. And we will have to take care of errors, duplicating requests, spamming API and so on.

Too complex

Don’t you worry, most of it we already covered in first part of the series! Here we will need to understand the chaining and error handling, plus how to connect the chained operation to table view. Not that hard, right? Right?

Never mind, let’s dive in. Our Issue Tracker at the end should looks like this one:

We type full repository name (with repository owner and slash), like in example: apple/swift, apple/cups, moya/moya and so on. When the repository is found (which is one URL request), we then search for issues of this repository (second URL request). That’s our main target, so let’s start coding!

First, we create a project and we install CocoaPods in it. We would need a few more pods this time. We will use RxSwift, Moya, RxCocoa, RxOptional and Moya’s extension for RxSwift and ModelMapper, to map objects, Moya-ModelMapper. Quite a lot! We can reduce the Podfile to 3 pods, which are:

These are really helpful and you’ll see that our task is really simple with the help we’ve got from them.

Step 1 – Controller and Moya setup.

We will start with UI, which is just a UITableView  and UISearchBar. Really simple one. You can follow up the layout in gif above, or create your own design – what you like the most!

Then we would need a controller to manage everything. We can try to describe role of our controller before we create the architecture.

So what should our controller really do? It should get the data from search bar, pass it to model, get issues from model and pass it to table view. Quite simple! Let’s start with our IssueListViewController. Create file IssueListViewController.swift, and prepare our controller with importing modules and basic configuration:

As you can see I have prepared the setupRx() method already, because of course we will have to setup our bindings! But before that, let’s setup Moya’s Endpoint. Remember that I’ve told you that we need two steps: step one, which is Provider and step two, which is the Endpoint. Let’s start with the Endpoint.

We will create new file, let’s call it GithubEndpoint.swift and we will create an enum with few possible targets in it:

Alright, alright, but you’ve told us that it needs to conform to the TargetType and this one is just an enum. And you are right! We will make an extension to the GitHub enum, which will have all the needed properties. As I told you, we need 7 (7, not 6, because URL is a baseURL + path). Besides baseURL and path and task, we also have method, which is just a request method like .get, .post, etc. There is also parameters and parametersEncoding, which should be self-explanatory, and sampleData, which we covered at the start of the tutorial.

So let’s implement it! In the same file, we will create an extension for GitHub to conform to TargetType:

Whole GithubEndpoint.swift should be fine now! It looks scary, but if you read it, it really isn’t! We won’t really need any parameters sent in here, so we return nil, method is always .get in our case, baseURL is also the same, just sampleData and path need to be put in a switch.

If you would want to add another target, you would need to check if this request needs .get or maybe .post method, maybe it needs parameters, then you would need to add there switches as well. What we’ve also added is the function URLEscapedString, which is really helpful with encoding characters in URL. Other than that, everything else should be clear. Back to the controller!

We now have to implement Moya’s Provider. We will also implement hiding keyboard on cell click, which will be done of course with RxSwift, and for that we will also need DisposeBag. Additionally we will create new Observable, that will be our text from search bar, but filtered (remove duplicates, wait for changes, everything from #1 of our tutorials).

In summary we will have 3 properties to add and setupRx() method to implement. Let’s do this!

And wow, first magic happened. I hope that latestRepositoryName variable code looks familiar to you, because it was in first part of the series and was deeply discussed there. Let’s move onto the more interesting things. First we have that mystery Provider setup we were talking about. As you can see – nothing special, just initializer. And because we are using Moya with RxSwift, we have to use RxMoyaProvider. If you would ever want to write API using Moya and ReactiveCocoa or using just Moya, there are different providers for each of them (MoyaProvider for pure Moya and ReactiveCocoaMoyaProvider for ReactiveCocoa + Moya).

Then we have the keyboard hiding setup. Thanks to RxCocoa, we have access to tableView.rx.itemSelected, which emits signal every time someone taps on table view cell. We can of course subscribe to it, and do our thing (hiding keyboard). We are checking if search bar is our first responder (if the keyboard is shown), and we are hiding it.

That would be it for basic View Controller and Moya setup. Step 2, we are coming!

Step 2 – Network model and mapping objects

Now we need our model that will give us the data based on the text. But first, we will also need to parse the objects before we send any info at all. That will be done thanks to our friends at ModelMapper. We will need 2 entities, one for Repository, and one for Issue. These are really easy to create, we need to conform to Mappable protocol and just try to parse objects. Let’s create them!

We won’t need many properties, you can add more based on this GitHub API docs.

Okay, that was quick, now we move to the most interesting thing in this tutorial, IssueTrackerModel – the core of our Networking. First, our model should have Provider property that we will pass in init. Then we should have a property for our observable text, which is Observable type, that will be our source of repositoryNames, that our view controller will pass. And from methods, we for sure will need one method to return our observable sequence of issues array, Observable<[Issue]>, that view controller will use to bind the table view. And we don’t need to implement init, because swift backs us up with memberwise initializer.

 Let’s create the IssueTrackerModel.swift:

As you can see I’ve added two more functions. One, findRepository(_:) will return optional repository (nil if it can’t map the object from response, Repository object if it can), and findIssues(_:) (the same logic with optional), that will of course search for repositories based on a given repository object. First let’s implement those two methods. What you think is really tricky, in fact is so amazingly simple to do with our setup:

Step by step:
1. We have provider, on which we can perform request with a given enum case.
2. We then pass GitHub.repo or GitHub.issues and voila, request done!
3. We use the debug() operator, which prints for us some valuable info from the request – it’s really useful in development/testing stage.
4. We could then try to parse and map the response manually, but thanks to our extension we have access to methods like mapObject(), mapArray(), mapObjectOptional() or mapArrayOptional(). What is the difference? With optional methods, when the object can’t be parsed, function returns nil. In normal methods it throws errors and we have to catch them using catch() functions or retry(). But in our case optionals are perfectly fine. We can clear our table view if the request was a failure.

Okay, okay. We have two methods that give us something based on something. How do I connect them though? For this task we will need to learn new operator, flatMap() and especially flatMapLatest(). What these operators do is, from one sequence they create another one. Why would you need that? Let’s say we have a sequence of strings, that you want to convert into sequence of repositories. Or sequence of repositories into sequence of issues. So… exactly like in our case! We will transform it in a chaining operation. And when we got nil (while getting Repository or Issues for the Repository object), we will return empty array to clear our table view. But what’s the difference between flatMap() and flatMapLatest()? Well, flatMap() gets one value, then performs long task, and when it gets the next value, previous task will still finish even when the new value arrives in the middle of the current task. It isn’t really what we need because when we get a new text in the search bar, we want to cancel the previous request and start another. That’s what flatMapLatest() does.

Our trackIssues method should look like the one below:

Step by step:
1. We wanna make sure it is observed on MainScheduler, because the purpose of this model is to bind it to UI, in our case table view.
2. We transform our text (repository name) into observable repository sequence, that can be nil in case it doesn’t map our object correctly.
3. We check if the repository we’ve mapped is nil or not.
If it is nil, just return observable nil sequence (which in case the repository is nil, next flatMapLatest() guarantees the empty array as a response). Observable.just(nil) means that we will send one item as an observable (in our case that item will be nil).

If it isn’t nil, we want to transform it to an array of issues (if the repository has any issues). It can also return nil or array with issues, so we still have observable with optional array.

4. .replaceNilWith([]) is RxOptional extension that helps us with nil, in our case we transform nil to empty array to clear table view.

And that’s it for our model! Really easy if you think about it. Just read the code few times, try to move operators, change them, replace them. Try it yourself.

Step 3 – Bind issues to table view

The last step would be to just connect data from the model we created to the table view. Which means we have to bind the observable to our table view.

Normally, you would need to conform our view controller to UITableViewDataSource, implement a few methods like number of rows, cell for row, and so on, then assign dataSource to the view controller.

With RxSwift, we can setup our UITableViewDataSource with only one closure! That’s right, RxCocoa provides us with another great utility, called rx.itemsWithCellFactory, which in closure takes the cell that we want to show. It simultaneously does everything for us, just based on our observable and the closure we provide. Magic! And the code looks really good too!

Now we go back to our IssueListViewController, and we implement the full setupRx() method:

What is new here, is our new property for our IssueTrackerModel(which we also initialize in setupRx()), and also new binding: from the model’s trackIssues()  method, to rx.itemsWithCellFactory property. Also don’t forget to change the cellIndentifier in dequeueReusableCell() method.

And that’s it! Everything we wanted to implement is implemented! Run the project and be happy with the results!

It was really long run and I’m really proud that we’ve come so far. I hope that everything was clear, but if you have any questions or feedback: message me on Twitter, send me an e-mail, or just comment here. I really love your messages and I’d love to hear what can be improved or what would you see in the next episodes ✌️

I'm proud..

Also I’ve improved the resources for RxSwift in our repository, so be sure to check them out. And subscribe(😎) to get the latest info about our series or RxSwift in general.

Read other articles about RxSwift

» RxSwift by Examples #2 – Observable and the Bind.
» RxSwift by Examples #4 – Multithreading.

Newsletter

The post is created by Droids On Roids Team Member.
We would love to take care of your business.

Leave comment

  • Dhvl B. Golakiya

    Hi, Thanx for this awesome tutorial.
    But I have one issue with this. Example project is not working in Latest Moya-ModelMapper . Can you please check after updating pod.

    • Łukasz Mróz

      Hey! Thank you for your kind words! 🙂
      There was a bug in the Mapper extension for Moya, there is a fix for it already on CocoaPods, just do “pod update” and you should be fine. Sorry for the inconvenience!

      • Techie I

        Hi,
        Do you know modelmapper issues have been resolved recently, I cant get RxMoyaExample to compile on cocoapods 0.39.0, any help will be appreciated

        • Łukasz Mróz

          Hey! Yeah, with version 2.0.1 of Moya-ModelMapper it should be alright, did you do “pod update”? If yes, can you tell me what is the error?

          • Techie I

            I did do pod update, I see this and many more in Result & ResultType, when I do clean build I get comp errors for ModelWrapper, building it again shows error for Result module

          • Łukasz Mróz

            Can’t really think of what could it be, it is really strange. Can you try deleting the project and downloading it again? Also what kind of errors these are?

  • PS

    Hi Lukasz,

    I will second that this is an awesome tutorial. It has given me the push to try and implement Moya for the first time in one of my own little applications.

    However I am having problems when trying to convert the response from the server into objects. I was wondering if you could possibly help me see what is going wrong. I have put a question up on Stackoverflow here: http://stackoverflow.com/questions/36516940/mapping-json-response-to-objects-using-rx-programming

    Cheers.

    • Łukasz Mróz

      Hey!

      Thank you so much for the kind words 🙂 It is always hard in the beginning, but I hope that with these tutorials you will get along with RxSwift.

      About the problem, we solved it with @prestonspalding:disqus in the SO thread given in the comment. Basically the response array, which was supposed to be parsed, was under the key in dictionary. We can parse it easily as well replacing method mapArrayOptional(User.class) with mapArrayOptional(User.class, keyPath: “user”), if the key in dictionary is “user”. More about parsing here: https://github.com/sunshinejr/Moya-ModelMapper.

      Cheers!

  • Ng

    It’s really an eye opener to start the Rx marathon on swift.:) recommended one for all beginners.

    • Łukasz Mróz

      Thank you so much! ☺️

  • Isuru Nanayakkara

    Thanks again for the great tutorial series. Please continue this RxSwift series.

    • Łukasz Mróz

      Thanks to YOU 🙇Really appreciate the support.

      Although I have less time for articles right now, the series ain’t ending for sure. The last with Multithreading was released this week 🙂

      • Isuru Nanayakkara

        I’m compiling a list of articles written on RxSwift here. I just added this series 🙂

        • Łukasz Mróz

          Awesome 💯🙌 Although my twitter was confused, mine is @thesunshinejr, not @mroz_lukasz 👌

          • Isuru Nanayakkara

            Oh crap. I’ll change it right away!

  • aznric3boi

    Thanks for your great work!! Please do more of these tutorials!
    Would you be able to do an JWT authentication wtih RxSwift + Moya Tutorial?

  • Robin

    Would love to see a realm integration into that. They both have different mappable protocol for some reason and it blocking me at the moment

    • Łukasz Mróz

      Hey @disqus_G9SL8VT66B:disqus, sorry for late answer. I believe you can use namespacing to use specific protocol/type. The same problem was with Error type that was in both Moya and Swift. The answer to that was to use Moya.Error or Swift.Error (now Moya has MoyaError instead). Hope it helps!

  • Siarhei Brazil

    Many thanks Łukasz for your tutorials, and hello from Katowice, Sląskie. 🙂
    I am not an expert in Rx, but I can suggest to move out Moya’s provider creation from VC. IssuesTrackerModel, should be initialized in AppDelegate, or in some coordinator, and propagated into VC. In such case the architecture of MVVM will be better supported.
    But apparently that the project is more connected with Rx, rather than architecture… ))

    • Łukasz Mróz

      Hey @siarheibrazil:disqus, thanks for the tip! And yeah, this blog post is not about architecture, but I’ve extracted the network logic from VC to keep it clean. There are many architectures good for Rx, one of them is indeed MVVM. I actually did a talk about MVVM+Coordinators with RxSwift on Mobilization, because it feels really natural!

  • Gustavo Perdomo

    Thanks a lot for this articles series… I have a question, Why you use ModelMapper instead ObjectMapper (https://github.com/Hearst-DD/ObjectMapper). Any particular reason for that? what advantages have ModelMapper over ObjectMaper or viceversa

    • Łukasz Mróz

      Hey @disqus_tD8311u6Lu:disqus! Mostly because of immutability in models (I can use let instead of vars, which in models is really important, at least for me).

      • Gustavo Perdomo

        @disqus_Np0jHs7JmF:disqus thanks… Other question, i wanna build some kind of Rest SDK to communicate with various servers (all the servers expose the same endpoints)… In the login form, you need to type the homeserver, so the baseURL needs that parameter, how can i do that?

  • Sev Razz

    What do you mean by cancelling the request here?
    > It isn’t really what we need because when we get a new text in the search bar, we want to cancel the previous request and start another. That’s what flatMapLatest() does.

    Do you mean that the MoyaProvider will cancel the request, and then proceed requesting the new one? Or will it just discard the result of the ongoing request and proceed with requesting the new one?

    • Łukasz Mróz

      Hey @severusrazzmatazz:disqus. Inner Observable (this in the flatMapLatest) will be disposed, and because RxMoya provider is cancelling the request on dispose, if the request didn’t finish, it should be canceled. If it did finish, the results will be discarded.

      This is a source code where Moya is cancelling request on dispose: https://github.com/Moya/Moya/blob/master/Sources/RxMoya/RxMoyaProvider.swift#L34-L36

  • Shihua Long

    Thanks for great tutorials, but I had a question? If I use Moya to make request like you did, how to write unit test for network ?

  • gollum

    Hi, thanks for the tutorial.

    I have a question for the GithubEndpoint, how can I pass parameters to it?

    I want for example to pass it a ‘MyModel’ to be used for the request parameters, or I want to tell him to use ‘.post’ instead of ‘.get’. I don’t see how I can do it in you code.