(dotSwift) Unidirectional data flow with RxSwift and RxRealm

In my talk at dotSwift 2017 I start with generic overview of some of the RxSwift basics and move to three complete code examples. In three posts I post the sample code and comment shortly why I chose to highlight these exact examples.

I already posted a write up on the GitHub API search example here: http://rx-marin.com/post/dotswift-search-github-json-api/.

The second post in the series, the one about presenting view controllers, is online here: http://rx-marin.com/post/dotswift-rxswift-view-controller/.

Let’s continue with example number three.

Modelling unidirectional data flow with RxRealm

RxSwift doesn’t neccessarily force you into one architecture or another, but it really helps you keep flows of data separte. For example, one class working on a background thread will fetch data from a network API and save it to disk, another class will read from disk and bind the UI on screen.

The two are completely separate and shouldn’t intermix.

In this example I used RxRealm with an online JSON API to show how to fetch JSON, store data on disk, and display it on screen easily with RxSwift and Realm.

Why I chose this example?

To make a point, namely that RxSwift allows you to simplify incredibly not only the code but also the logic of your app by defining clean dataflows.

The sample code

In this post I can go into a bit more detail about the complete code than it was reasonable in the talk at the conference. Still keep in mind this was written with the goal to fit in a single slide, so corners were cut big time :)

The sample uses both RxRealm and RxRealmDataSources (which is a simplified data source library to use with realm).

The completed project shows a table view, which updates any time new objects are added in the background. The table view displays a list of mocked repository activity items, like so:

completed unidirectional dataflow project

In the simple example included in the talk, all code is added in the view controller but of course in real-life this could be split across different classes, frameworks, etc.

The example defines two data flows:

  1. JSON from API, being converted to objects and stored on disk.
  2. Objects loaded from disk and bound to a UI table.

1. Converting and storing JSON

SourceControlAPI is a mocked API class, which periodically provides updates in JSON format. SourceControlAPI.updates() returns Observable<[String: Any]>.

The subscription begins like so:

SourceControlAPI.updates()
  .observeOn(SerialDispatchQueueScheduler(qos: .background))
  .map(Update.fromOrEmpty)

Via observeOn I switch the processing of the JSON data to a background thread, and map all the JSON objects to Update realm objects. fromOrEmpty is a static method, which takes in a dictionary and populates a new Update object.

Once I have the objects, I can bind the list of newly created objects to add(), which will add them to the default realm file:

.subscribe(Realm.rx.add())
.addDisposableTo(bag)

The data flow is simple and linear:

API -> JSON -> [Update] -> Realm

This happens on a background thread and perisist the data for possible offline use.

Now let’s move on to …

2. Binding objects from disk to UI

This one is even easier thanks to a simple helper library called RxRealmDataSources, which helps binding a Realm collection to a table or collection view on screen.

Let’s start by defining a data source object:

let dataSource = RxTableViewRealmDataSource<Update>(cellIdentifier: "Cell", cellType: UITableViewCell.self) {cell, ip, update in
    cell.detailTextLabel!.text = "[" + update.ago + "] " + update.name + " " + update.action
    cell.textLabel?.text = "Repo: " + update.repo
}
dataSource.headerTitle = "Source Control Activity"

This defines a data source object for Update objects, which is going to be producing table cells with identifier “Cell”. It also features a closure to configure each of the cells before it’s being used on screen.

Secondly, let’s create the Realm collection to bind:

let realm = try! Realm()
let updates = realm
  .objects(Update.self)
  .sorted(byKeyPath: "date", ascending: false)

updates gives access to all Update objects sorted in descending order by their date property.

Finally binding the updates to the table view, using the data source is a matter of:

Observable.changeset(from: updates)
  .bindTo(tableView.rx.realmChanges(dataSource))
  .addDisposableTo(bag)

This lets RxRealmDataSources piece everything together and drive the UI:

table animation

The complete example

// 1. store data
SourceControlAPI.updates()
    .observeOn(SerialDispatchQueueScheduler(qos: .background))
    .map(Update.fromOrEmpty)
    .subscribe(Realm.rx.add())
    .addDisposableTo(bag)

// 2. display data
let dataSource = RxTableViewRealmDataSource<Update>(cellIdentifier: "Cell", cellType: UITableViewCell.self) {cell, ip, update in
    cell.detailTextLabel!.text = "[" + update.ago + "] " + update.name + " " + update.action
    cell.textLabel?.text = "Repo: " + update.repo
}
dataSource.headerTitle = "Source Control Activity"

let realm = try! Realm()
let updates = realm.objects(Update.self).sorted(byKeyPath: "date", ascending: false)

Observable.changeset(from: updates)
    .bindTo(tableView.rx.realmChanges(dataSource))
    .addDisposableTo(bag)

Discussion

In real life you never have just two data flows in your whole app. But even in this simple example you can see the benefits of being able to clearly and simply define how data flows. Even as you scale your app, the complexity of defining these type of data flows does not increase.

Hope that was an inspirational read! You can dig into more details below…

Links

The complete demo app from my talk: https://github.com/icanzilb/RxSwiftoniOS

The talk slides: https://speakerdeck.com/icanzilb/rxswift-on-ios

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.