Building Reactive Apps with Realm: Episode 2

In this five-episode series, we’re going to have a look at how Realm facilitates building reactive applications from the ground up. In the second episode, we’ll take a look at a project called Inboxly, and how we can use Realm to keep a UI updated and responsive all the time, from different threads.

You can find the other episodes here:


 

Introduction

Hi, and welcome back to Building Reactive Apps with Realm. As we already discussed in the previous episode, there are a number of aspects that characterize reactive applications. We’ve been writing reactive codes for a long time now that exhibits these characteristics. In this series, we’re having a closer look at how exactly Realm empowers developers to write reactive apps.

Since we covered the basics in the last episode, let’s have a look at the first characteristic on the Reactive Manifesto list. In a typical iOS app, for example, you would focus on two areas to keep your app and your app’s UI responsive:

  • First, you will do the heavy work. Running blocking tasks like networking and fetching JSON or other network resources, and data conversion on a thread running with a background priority. You will keep the main thread free as much as possible for a snappy UI responses.
  • Secondly, you will observe your data storage so that you always show the latest state in your UI to your user. You will do that as soon as possible.

Next, we’re going to dive into a short coding session to see how Realm helps you in these two tasks. In this video, we’re going to have a look at a project called Inboxly.

Inboxly - Getting Messages

We’ve been using the Inboxly project for a number of workshops here at Realm. It’s a messaging app with three tabs. The first one is messages. The messages that are coming down from the server will show up. In the favorites tab, in the middle, we’ll see all the messages that are user favorites. The profile tab is mostly empty. That is the project we’re going to work with in this video.

We’re going to have a look at how Realm allows us to fetch JSON in a background thread very easily, process it, and store it to Realm. We don’t really care about threading. In this over-simplified app, we’re going to have a look at the DataController.swift where we have this fetch method that’s being repeatedly called over time:

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in

    })
}

I already have an empty dispatch_async block. Here I can run my code to fetch my JSON. It’s a simulated API so it will always provide a random number of JSON objects back just to showcase how to fetch JSON and store it into Realm.

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in
        self.api.getMessages {jsonObject in

        }
    })
}

My similar API has a method called getMessages. It has a completion block which gets us a parameter and array of JSON objects. I’m going to convert those JSON objects into Realm objects and store them into Realm:

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in
        self.api.getMessages {jsonObject in
            let newMessages = jsonObject.map { Message(value: $0) }
        }
    })
}

In here I use the convenient initializer that takes an array or a dictionary and tries to map the properties from it to the Message object. Message is my Realm object to store messages in the Realm database:

import Foundation
import RealmSwift

class Message: Object {
    dynamic var id = ""
    dynamic var message = ""
    dynamic var name = ""
    dynamic var photo: Photo?
    dynamic var favorite = false
    dynamic var createdAt = NSDate()

    // State
    dynamic var outgoing = false
    dynamic var sent = false
    
    override static func primaryKey() -> String? {
        return "id"
    }
    
    override class func indexedProperties() -> [String] {
        return ["outgoing", "favorite"]
    }
}

You can see it has properties like the id of the message, the text, the name of the sender, whether it’s a favorited message or not and so forth. Now I have an array of Realm objects. What I’m going to do is fetch the Users object and add all the messages to it.

Inboxly - Getting Users

The Inboxly Realm database contains two types of objects, User and message. There is one user created by default, so I’m going to fetch all user objects and get the first one of them. The User object has a list property called messages. I’m going to add these new messages to it:

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in
        self.api.getMessages {jsonObject in
            let newMessages = jsonObject.map { Message(value: $0) }

            let realm = try! Realm()
        }
    })
}

Now what we’re looking for specifically is that this is code that is being executed on a background thread. Also, the completion block that is being provided to self.api.getMessages, we don’t know on which thread this code ends up because it might be on the same thread. It might be on a different thread depending on how our getMessages work. We don’t really need to know that though because we can from any thread say, let realm = try! Realm(), and then read and write to our Realm.

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in
        self.api.getMessages {jsonObject in
            let newMessages = jsonObject.map { Message(value: $0) }

            let realm = try! Realm()
            let me = realm.objects(User.self).first!
        }
    })
}

Now I can write to Realm. What I’m going to do is add the new messages to the existing list of messages for this user. List has a method called insertContentsOf, which takes a collection, which is handy for me because I have an array of objects so I can provide it here and say, insert it at the beginning of the list. It will show up at the top of my feed. I’m going to also add a print in here, for testing.

@objc private func fetch(timer: NSTimer) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), {[weak self] in
        self.api.getMessages {jsonObject in
            let newMessages = jsonObject.map { Message(value: $0) }

            let realm = try! Realm()
            let me = realm.objects(User.self).first!
            
            try! realm.write {
                me.messages.insertContentsOf(newMessages, at: 0)
            }

            print("saved \(newMessages.count) messages")
        }
    })
}

Again what we’re looking at is thread switching. We don’t know where our dispatch_async block is going to end up on, but we don’t an care because Realm allows us to read and write from any thread we want to do that from.

If we run the app right now to make sure that everything works fine, and observe the console, we’re going to see saved 3 messages. Our similar API gave us back three JSON objects that were being written from some thread, maybe the background, maybe not. If we wait a little bit there is another two messages that came in from the similar API, and so forth, and so forth.

Notifications / Conclusion

Now in our FeedTableviewController, the one that’s been showing the feed of messages in the app, let’s see how we’re going to keep the UI updated with the inserts that our data controller is doing in the background:

import UIKit
import RealmSwift

class FeedTableViewController: UITableViewController {

    var messages: Results<Message>?
    var notifications: NotificationToken?
    
    override func viewDidLoad() {
        let realm = try! Realm()
        messages = realm.objects(User.self).first!.messages.filter("outgoing = false")
        
        
    }
}

extension FeedTableViewController {
...

As in the previous episodes, I have a Results and a NotificationToken to provide my UI with updates. I have a realm object in my viewDidLoad method, and I have my messages already loaded with all the messages from the messages list from a user. What I’m going to do here is subscribe for updates from Realm and update my UI, inside of viewDidLoad:

override func viewDidLoad() {
    let realm = try! Realm()
    messages = realm.objects(User.self).first!.messages.filter("outgoing = false")

    notifications = messages?.addNotificationBlock {[weak self] changes in
        
        guard let tableView = self?.tableView else {
            return
        }
    }
}

addNotificationBlock adds a closure to the results object and that closure is being executed every time there is any kind of change in the line so that you can update your UI. addNotificationBlock closure gets one parameter which provides the changes that happen to your results. Let me switch over changes because this is enumeration.

override func viewDidLoad() {
    let realm = try! Realm()
    messages = realm.objects(User.self).first!.messages.filter("outgoing = false")

    notifications = messages?.addNotificationBlock {[weak self] changes in
        
        guard let tableView = self?.tableView else {
            return
        }
        
        switch changes {
        case .Initial:
            tableView.reloadData()
        case .Update(_, let deletions, let insertions, let modifications):
        
        
        
        }
    }
}

The very first time in the .Initial case when the results are loaded, I’m going to call reloadData() on the tableView to make sure that it shows the latest data. For any change that happens in the .Update case, I’m going to get the indexes in the collection that were inserted later on data and reflect those changes in my table view.

These arrays, let deletions, let insertions, and let modifications, are integer indexes, so I’m going to convert them to index paths and then call their respective methods on my tableView, and also add a default case:

override func viewDidLoad() {
    let realm = try! Realm()
    messages = realm.objects(User.self).first!.messages.filter("outgoing = false")

    notifications = messages?.addNotificationBlock {[weak self] changes in
        
        guard let tableView = self?.tableView else {
            return
        }
        
        switch changes {
        case .Initial:
            tableView.reloadData()
        case .Update(_, let deletions, let insertions, let modifications):
        
            tableView.beginUpdates()
            
            tableView.insertRowsAtIndexPaths(insertions.map {NSIndexPath(forRow: $0, inSection: 0)}, withRowAnimation: .Automatic)
            
            tableView.endUpdates()
          default: break
        }
    }
}

Here I use a map to convert all the integer indexes to index paths and then I feed them to insertRowsAtIndexPath which is a built-in method on my tableView. Now there is a very, very similar method for deleting and reloading row. I’m going to copy over and make the necessary changes:

override func viewDidLoad() {
    let realm = try! Realm()
    messages = realm.objects(User.self).first!.messages.filter("outgoing = false")

    notifications = messages?.addNotificationBlock {[weak self] changes in
        
        guard let tableView = self?.tableView else {
            return
        }
        
        switch changes {
        case .Initial:
            tableView.reloadData()
        case .Update(_, let deletions, let insertions, let modifications):
        
            tableView.beginUpdates()
            
            tableView.insertRowsAtIndexPaths(insertions.map {NSIndexPath(forRow: $0, inSection: 0)}, withRowAnimation: .Automatic)
            
            tableView.deleteRowsAtIndexPaths(deletions.map {NSIndexPath(forRow: $0, inSection: 0)}, withRowAnimation: .Automatic)
            
            tableView.reloadRowsAtIndexPaths(modifications.map {NSIndexPath(forRow: $0, inSection: 0)}, withRowAnimation: .Automatic)
            
            tableView.endUpdates()
          default: break
        }
    }
}

That’s it. Now I have a fully reactive view controller, which observes a “viewport” to the data storage looking at the list of messages that were only outgoing = false. Every time my data controller, from a different thread, inserts a message into my Realm, the addNotificationBlock will fire and will reflect all these changes into my UI. Let’s fire up the app and have a look. You should be able to see some messages that are already stored. If we wait a little bit for some new messages to come in, we will see them show up with an animation because we call insertRowAtIndexPath with animations. Now isn’t that just beautiful. Very, very easy. Very, very quick. From different threads you can work with your Realm and keep your UI responsive all the time.

Resources


Marin Todorov

Marin Todorov

Marin Todorov is an independent iOS consultant and publisher. He’s co-author on the book "RxSwift: Reactive programming with Swift" the author of “iOS Animations by Tutorials”. He's part of Realm and raywenderlich.com. Besides crafting code, Marin also enjoys blogging, writing books, teaching, and speaking. He sometimes open sources his code. He walked the way to Santiago.