(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h2"> <div> <p>This days almost every application have some kind of server connections. In this small tutorial for beginners I will show you how to handle network communications using RxSwift.</p> </div> </div></span>)

Networking with RxSwift

Photo of Maciej Matuszewski

Maciej Matuszewski

Updated Jan 10, 2023 • 8 min read
william-bout-264826

This days almost every application have some kind of server connections. In this small tutorial for beginners I will show you how to handle network communications using RxSwift.

For the purposes of this guide we will create a small app that search universities using Hipolabs API. The core of network communication will be based on URLSession. I assume that you know basics of iOS programing, so I will focus to explain only Rx parts of the project.

Prepare project

First step in our journey will be preparing the project, after creating it in Xcode, we need to add two external libraries:

I used for that Cocoapods, but feel free to import libraries via Carthage or manually. For Instructions head to RxSwift

Simple Layout

When our project is ready for coding we need to create place where received data will be presented. For this I created simple UITableView and UISearchController in main ViewController which should be embed in UINavigationController

Here you have code that do the work:

private let tableView = UITableView()
private let cellIdentifier = "cellIdentifier"

private let searchController: UISearchController = {
  let searchController = UISearchController(searchResultsController: nil)
  searchController.searchBar.placeholder = "Search for university"
  return searchController
}()

private func configureProperties() {
    tableView.register(TableViewCell.self, forCellReuseIdentifier: cellIdentifier)
    navigationItem.searchController = searchController
    navigationItem.title = "University finder"
    navigationItem.hidesSearchBarWhenScrolling = false
    navigationController?.navigationBar.prefersLargeTitles = true
}

private func configureLayout() {
    tableView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(tableView)
    NSLayoutConstraint.activate([
        tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        tableView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
        tableView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    tableView.contentInset.bottom = view.safeAreaInsets.bottom
}

And here is the result:

What we want to receive? How we want to receive it?

If our layout is ready we can try to handle some data from REST API. For that we will use Hipolabs API. We want to get informations about universities which names contain search phrase from our UISearchController.

Example

Here you have example of request and response for finding universities with middle as name parameter.

Request
http://universities.hipolabs.com/search?name=middle
Response
[{"name": "Middlesex University", "domains": ["mdx.ac.uk"], "web_pages": ["http://www.mdx.ac.uk/"], "alpha_two_code": "GB", "state-province": null, "country": "United Kingdom"}, ...]

Now when we know how API works we can create request and model objects.

Model

For working on data that came from server we can use JSON dictionary like [String: Any], but I prefer to create data model which is much clearer and easier to use. For purpose of receiving universities objects I created struct UniversityModel, which conform to Codable protocol and because of that we don't need to be bothered by parsing data, let's leave that to swift engine.

struct UniversityModel: Codable {
    let name: String
    let webPages: [String]?
    let country: String

    private enum CodingKeys: String, CodingKey {
        case name
        case webPages = "web_pages"
        case country
    }
}

Requests

For making this more universal we need to create APIRequest protocol, so different requests could be handle by the same APIClient.

APIRequest class consists of two parts:

Protocol itself where are defined necessary properties:

protocol APIRequest {
    var method: RequestType { get }
    var path: String { get }
    var parameters: [String : String] { get }
}

Protocol extension that will create URLRequest from instance of APIRequest:

extension APIRequest {
    func request(with baseURL: URL) -> URLRequest {
        guard var components = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false) else {
            fatalError("Unable to create URL components")
        }

        components.queryItems = parameters.map {
            URLQueryItem(name: String($0), value: String($1))
        }

        guard let url = components.url else {
            fatalError("Could not get url")
        }

        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        return request
    }
}

I also created a small enum inside APIRequest class to improve declaring httpMethod:

public enum RequestType: String {
    case GET, POST
}

When APIRequest protocol is ready we can make real request specify for searching universities by names. For that we need to create another class inherited from APIRequest protocol where is defined method, endpoint path and parameters.

class UniversityRequest: APIRequest {
    var method = RequestType.GET
    var path = "search"
    var parameters = [String: String]()

    init(name: String) {
        parameters["name"] = name
    }
}

Ok, there was a lot of it, but where is this RxSwift?

Time for magic

Now it is time for the most important piece of this puzzle, part that will change our request for data from server. Now it is time for APIClient!

APIClient is a class where we will make a request by using rx extension on URLSession from RxCocoa and then map the response data to already parsed model of data if only model is Codable. The .data(request: URLRequest) function will make sure for us that status code is 200..<300 and only then return data, if not then it will throw an error.

class APIClient {
    private let baseURL = URL(string: "http://universities.hipolabs.com/")!

    func send<T: Codable>(apiRequest: APIRequest) -> Observable<T> {
        let request = apiRequest.request(with: baseURL)
        return URLSession.shared.rx.data(request: request)
            .map { 
                try JSONDecoder().decode(T.self, from: data)
            }
            .observeOn(MainScheduler.asyncInstance)
    }
}

One more last thing...

After creating APIClient the last part is connecting everything together.

Result that we expect:

Typing search phrase in search fieldInstance of request created with search phraseArray of models of universityRefreshed UITableView filled by new data and all of that in 10 lines!!!

searchController.searchBar.rx.text.asObservable()
  .map { ($0 ?? "").lowercased() }
  .map { UniversityRequest(name: $0) }
  .flatMapLatest { [unowned self] request -> Observable<[UniversityModel]> in
    return self.apiClient.send(apiRequest: request)
  }
  .bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { index, model, cell in
    cell.textLabel?.text = model.name
  }
  .disposed(by: disposeBag)

Please remember to import RxSwift and RxCocoa and create two variable:

private let apiClient = APIClient()
private let disposeBag = DisposeBag()

Extra feature

If you would like to present a website of university when user will tap on cell you can do this in 7 lines, by taking advantage from model and reactive binding.

tableView.rx.modelSelected(UniversityModel.self)
.map { URL(string: $0.webPages?.first ?? "")! } .compactMap { URL(string: $0) } .map { SFSafariViewController(url: $0) } .subscribe(onNext: { [weak self] safariViewController in self?.present(safariViewController, animated: true) }) .disposed(by: disposeBag)

Final effect


That is all for today. You can find the whole project at this repository. I hope that you enjoyed.

Tags

Photo of Maciej Matuszewski

More posts by this author

Maciej Matuszewski

Maciej has always been keen on technology. When he received his first iPhone, he dove head first...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: