Introducing Realm: Building Modern Swift Apps with Realm Database

Get started quickly with using Realm in your Swift apps with this free preview chapter from our new book: Realm: Building Modern Swift Apps with Realm Database! By Marin Todorov.

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Reacting to Data Changes

One sub-optimal way to do this is to refresh the table view from addItem(). But going down this path means you’ll need to refresh the table view from every other method that modifies your data, such as when you delete an item, or set its completion status, and so on. Leaving that aside, the real issue is how to refresh the table if you commit a change from another class, which runs somewhere in the background, and is completely decoupled from the view controller?

Fortunately, Realm provides a powerful solution to this. Realm’s own change notifications mechanism lets your classes read and write data independently and be notified, in real-time, about any changes that occurred.

Realm’s own notification system is incredibly useful, because it lets you cleanly separate your data persistence code. Let’s look at an example which uses two classes:

  • A networking class which persists JSON as Realm objects on a background queue.
  • A view controller displaying the fetched objects in a collection view.

Without Realm and change notifications, you’ll need to make one class a delegate of the other (in a way that inevitably couples them) or use NotificationCenter to broadcast update notifications.

With Realm, the two classes can cleanly separate their concerns. One will only write to the database the other only reads and observes changes:


With this setup, it would be trivial to do something like test the class that writes objects to the database without creating a view controller. In addition, in the event the app goes offline, the view controller won’t care at all about the fact the class responsible for writing objects isn’t doing any work at the moment.

Relying on Realm’s built-in change notifications lets you separate concerns extremely well and keeps your app’s architecture simple and clean.

Let’s see how that looks in practice.

Open Scenes/ToDoListController.swift and add a new property at the top of the class:

private var itemsToken: NotificationToken?

A notification token keeps a reference to a subscription for change notifications. You’ll be using notifications throughout the book so you’ll get to learn all about them, starting in Chapter 6, “Notifications and Reactive Apps”.

Continue in viewWillAppear(_) where you’ll set your notification token:

itemsToken = items?.observe { [weak tableView] changes in
  guard let tableView = tableView else { return }

  switch changes {
  case .initial:
    tableView.reloadData()
  case .update(_, let deletions, let insertions, let updates):
    tableView.applyChanges(deletions: deletions, insertions: insertions, updates: updates)
  case .error: break
  }
}

You call observe(_) on the to-do items result set, which lets Realm know that you want to receive updates any time the result set changes.

For example, if you add a to-do item, Realm will call your observe callback. If you remove a to-do item, Realm will call your callback. If you change a property on one of your to-do items … yes, you guessed right — Realm will call your callback.

The observe(_) callback closure is the place to implement any UI code that will reflect the latest data changes in your app’s UI. In the code above, you receive detailed information about what items have been inserted, modified, or deleted. If any changes occurred in your result set, you call the applyChanges(_) extension method to apply them on screen, and you also take care of simply reloading the table view with the initial data at the time of observing changes.

Note: The callback is called on the same thread you create the subscription on. In your code above you create the subscription in viewWillAppear(_) and is therefore safe to update the app’s UI without any extra checks.

That’s as far as you’ll take this right now. Later on, you’ll learn about the notification data in greater detail.

Next, since you start observing items in viewWillAppear(_), it makes sense to stop the observation in viewWillDisappear(_).

Add the following to viewWillDisappear(_):

itemsToken?.invalidate()

invalidate() invalidates the token and cancels the data observation.

Run the project again. This time, as soon as you enter a new to-do item, it’ll appear in your list with a nice accompanying animation:

Now that you have the table all reactive and animated, you can add the remaining CRUD operations. Thanks to Realm’s change notifications, the table will reflect any changes automatically. Isn’t that great?

Modifying a Persisted Object

You just learned how to add new objects to your app’s Realm, but how would you modify an object that has already been persisted?

Obviously, you first need to fetch the object from the database and then modify it somehow. In this section of the chapter, you’re going to add code to complete (and un-complete?) a to-do task.

Open Entities/ToDoItem.swift and add a new method below add(text:in:):

func toggleCompleted() {
  guard let realm = realm else { return }
  try! realm.write {
    isCompleted = !isCompleted
  }
}

toggleCompleted() is a new method which allows you to easily toggle the status of a to-do item from incomplete to completed and vice-versa.

Every object persisted to a Realm has a realm property which provides you with quick access to the Realm where the object is currently persisted on.

You start by unwrapping the ToDoItems’ realm and start a new write transaction, just like you did before.

From within the transaction, you toggle isCompleted. As soon as the transaction has been successfully committed, that change is persisted on disk and propagated throughout your observation to change notifications. You can now add the code to toggle the item whenever the user taps the right button on each to-do item cell.

Switch back to Scenes/ToDoListController.swift and add the following method below addItem():

func toggleItem(_ item: ToDoItem) {
  item.toggleCompleted()
}

This method calls your newly created toggleCompleted() on a given to-do item object. You can use toggleItem(_) in the code that configures each individual to-do cell.

Scroll down to tableView(_:cellForRowAt:) and insert inside the callback closure for cell.configureWith:

self?.toggleItem(item)

When the user taps the cell button, it will call your code back and invoke toggleItem(item), which will toggle that item’s status.

That should take care of toggling to-do items in your project.

Run the app one more time and try tapping the status button of some of those to-do items:

As soon as you modify any of the to-do objects, the view controller is being notified about the change and the table view reflects the latest persisted data.

You’ll also notice that items you mark as completed will animate towards the bottom of the list. This is because Realm’s Results class reorders the objects in the collection according to the sorting you applied when you initially started observing changes. If you look back to ToDoItem.all(in:), you’ll see you’re sorting the results by their isCompleted property — incomplete tasks first, and completed last.