Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4402

Introducing Realm: Building Modern Swift Apps with Realm Database

$
0
0

This is an excerpt taken from Chapter 2, “Your First Realm App” of our book Realm: Building Modern Swift Apps with Realm Database. In the book, you’ll take a deep dive into the Realm Database, learn how to set up your first Realm database, see how to persist and read data, find out how to perform migrations and more. You’ll also take a look at the synchronization features of Realm Cloud to perform real-time sync of your data across all devices. Enjoy!

The idea of this chapter is to get you started with Realm without delving too much into the details of the APIs you’re going to use. This will hopefully inspire you to try and find the right APIs, figure out what they do, and perhaps even browse through RealmSwift’s source code.

Now you’ll take a leap of faith and dive right into creating an iOS app that uses Realm to persist data on disk while following this tutorial-style chapter.

Have no fear though, as the rest of this book will teach you just about everything there is to learn about Realm in detail. You’re going to learn the mechanics behind everything you’re about to do in this chapter and much, much more.

I invite you to work through this chapter’s exercises with an open mind. This chapter is simply about getting a feeling for using Realm in your Swift code.

Getting Started

The theme of to-do apps as an educational tool might be getting old by now, but the simple truth is that a to-do app does a great job of demonstrating how to build an app based on data persistence. A to-do app includes features like fetching a list of to-do items from disk, adding, modifying and deleting items, and using the data with some of the common UIKit components such as a table view, an alert view, buttons, and more.

In this chapter, you’re going to work on a to-do app that’s built on the Realm Database. This will be very useful as you learn the basics of CRUD operations with Realm.

Note: Create, Read, Update and Delete are the basic persistence operations you can perform on mostly any data entity. Often times, this set of operations will be referred by the acronym CRUD. Just don’t mix up a CRUD app with a crude app!

To get started, open the macOS Terminal app (or another similar app of your choice), navigate to the current chapter’s starter project folder, run pod install (as described in Chapter 1) and open the newly created Xcode workspace.

Note: The starter projects in this book contain some UI boilerplate and other non-database related code. Sometimes Xcode might show code warnings since the starter code “misses” some parts which you will add while working through the tasks in the respective chapter.

Open Main.storyboard to get an idea of the app’s basic structure:

The project consist of a single table view controller with a custom to-do item cell.

The source code structure follows a general pattern for all of the projects in this book:

  • The Classes folder is a catch-all location for code that doesn’t fall under one of the other folders mentioned below. In this project, it includes an extension on UIViewController to add a simple API to present alerts on screen, as well as a handy extension on UITableView.
  • Assets is where you’ll find the app’s launch screen, meta information plist file, and the asset catalog.
  • Scenes contains all of the app’s scenes; including their view controller, view and view model code, when available. In this project, you have a single view controller and a custom table cell class.
  • Entities contains the Realm object classes you’ll be persisting to disk. This is practically your data models, but backed by Realm. You have a single class called ToDoItem in this project. In later chapters, you’ll be working on more complex database schemas, but this simple schema will suffice for now.

The projects in this book all follow a similar code structure, but you aren’t forced to use this structure in your own work. We’ve provided it as a guideline so you’ll know where to look for files as later projects in this book become more complicated.

If you run the starter app right now, you’ll see that it compiles and displays an empty to-do list on screen:

Realm Objects

Realm objects are basically a standard data model, much like any other standard data model you’ve defined in your apps. The only difference is they’re backed by Realm persistence and abilities.

You won’t dive into Realm objects at this stage, as you’ll be going into more detail on how to define Realm models and persist them in the next section of this book. In this chapter, you’re going to waltz through these model definitions, and get straight into action.

In fact, the MyToDo starter project already includes a class that will serve as the data model for storing to-do items. Open Entities/ToDoItem.swift and notice the ToDoItem class.

There are a few interesting points to note here, but you’ll be learning more about these subjects in the next chapters.

Dynamic Properties

First and foremost, you should recognize that the ToDoItem class subclasses Object. Object is a base class all of your Realm-backed data entities must inherit from. This allows Realm to introspect them and persist them to disk.

Another interesting oddity in this class is that all of its properties are defined as dynamic:

dynamic var id = UUID().uuidString
dynamic var text = ""
dynamic var isCompleted = false

This allows Realm to implement some custom, behind-the-scenes logic, to automatically map these properties to the data persisted to disk.

With the help of the dynamic keyword, a model class serves as a loose proxy between your app’s code and the data stored on disk, as seen in this simple schema:

Primary Key

ToDoItem also features a convenience init(_) and overrides a static method called primaryKey() from its parent class. Realm will call primaryKey() to decide which property will be used as the object’s primary key.

The primary key stores unique values that are used to identify objects in the database. For example, if you’re storing Car objects in the database, their uniquely identifying primary key can be their registration plate:

To make things easy, the id property, which is the primary key of ToDoItem, is automatically given a unique string UUID value. The UUID Foundation class lets you easily generate unique identifiers like these:

DB4722D0-FF33-408D-B79F-6F5194EF018E
409BC9B9-3BD2-42F0-B59D-4A5318EB3195
D9B541AF-16BF-41AC-A9CF-F5F43E5B1D9B

Ordinary Code

You’ll find that the class looks very much like a common Swift class. This is one of the greatest things about Realm! You don’t have to go out of your way to adapt your code to work with the persistence layer. You always work with native classes like ToDoItem, while Realm does the heavy-lifting behind the scenes automatically.

To recap: ToDoItem is a class you can persist on disk because it inherits from Realm’s base Object class. The class is pretty much ready to go, so you can start adding the code to read and write to-do items from and to disk.

Reading Objects From Disk

In this section, you’re going to write code to retrieve any persisted ToDoItem objects and display their data in the main scene of your app.

Let’s start off by adding a method to fetch all to-do items from the Realm file on disk. Open Entities/ToDoItem.swift and insert in the extension at the bottom:

static func all(in realm: Realm = try! Realm()) -> Results<ToDoItem> {
  return realm.objects(ToDoItem.self)
    .sorted(byKeyPath: ToDoItem.Property.isCompleted.rawValue)
}

You just added a static all(in:) method which, by default, fetches all to-do items from your default Realm file. Having a realm parameter with a default value allows you to easily work with the default Realm, but also leaves room for using an arbitrary Realm, if needed.

You can actually have more than a single Realm file in your app, as you might want to separate out the data your app uses into different “buckets”. You’ll learn more about this in Chapter 7, “Multiple Realms/Shared Realms”.

Note: You maybe outraged by the use of try! and of course you will have right to be. For brevity’s sake the book code will only focus on Realm’s APIs but if you’d like to learn more about error handling or other Swift related topics do check the “Swift Apprentice” book on raywenderlich.com where we cover Swift itself in great detail.

If you look further down the code, you’ll spot the objects(_) and sorted(byKeyPath:) methods, which are some of the APIs you’re going to use throughout this book. objects(_) fetches objects of a certain type from disk, and sorted(byKeyPath:) sorts them by the value of a given property or key path.

In your code, you ask Realm to return all persisted objects of type ToDoItem and sort them by their isCompleted property. This will sort incomplete items to the start of the list and completed ones to the end. The method returns a Results, a generic results type which gives you dynamic access to the result set.

Next up will be updating your view controller to use this new method to fetch and display the to-do items.

Open Scenes/ToDoListController.swift and spot the items property towards the top of the file. It is an Optional type where you’ll store the result fetched from Realm.

Next, append to viewDidLoad():

items = ToDoItem.all()

This code uses your new all(in:) method to ask Realm for all persisted ToDoItems.

Currently, the app doesn’t display any items when launched, since you don’t have any data stored. You’ll fix that by adding some default to-do items in case the user hasn’t created any.

Creating Some Test Data

Open AppDelegate.swift and add inside the empty initializeRealm() method:

let realm = try! Realm()
guard realm.isEmpty else { return }

You start by getting an instance of the default Realm by initializing it without any arguments. Then, you check if the Realm is empty using the handy isEmpty property. If the Realm isn’t empty, you simply return since there’s no need to add test data.

Don’t worry about the creation of a new Realm in the first line. Initializing a Realm object simply creates a handle to the file on disk. Furthermore, this handle is shared across your app, and returned each time you use Realm() on the same thread. Therefore, you’re not duplicating your data, or consuming any extra memory — all pieces of code in this app work with the same Realm instance and the same file on disk.

Next, add code to create some test data, right after your guard statement:

try! realm.write {
  realm.add(ToDoItem("Buy Milk"))
  realm.add(ToDoItem("Finish Book"))
}

This quick piece of code persists two objects to disk. And by quick, I mean that it’s literally only four lines of code!

You start a write transaction by using realm.write and add two new ToDoItem objects from within the transaction body. To persist objects, you simply create them as you would any other class, and hand them off to Realm.add(_) which adds them to your Realm.

Last but not least, call initializeRealm from application(_:didFinishLaunchingWithOptions:). Just before return true, add the following:

initializeRealm()

Build and run the project to see your new code in action:

That was easier than expected, wasn’t it? To be fair, the starter project did include some code to make your life a tad less complicated, but it’s clear how easy it is to fetch items from disk and persist new ones when needed.

Open Scenes/ToDoListController.swift one more time and look for the UITableViewDataSource implementations in the bottom of the file:

  • tableView(_:numberOfRowsInSection:) uses items?.count to return the number of objects fetched in the items result set.
  • tableView(_:cellForRowAt:) uses an index subscript to get the object at the required index path items?[indexPath.row] and use its properties to configure the cell.

Additionally, the next class extension defines some delegate methods from the UITableViewDelegate protocol that enable swipe-to-delete on table cells. You’ll write the code to actually delete items a bit later in this chapter.

Adding an Item

Next, you’ll add code to allow the user to add new to-do items to their list.

Since this is one of the CRUD operations, let’s add the relevant method to the ToDoItem class. Open Entities/ToDoItem.swift and add right below your all(in:) method:

@discardableResult
static func add(text: String, in realm: Realm = try! Realm()) 
  -> ToDoItem {
  let item = ToDoItem(text)
  try! realm.write {
    realm.add(item)
  }
  return item
}

add(text:in:) lets you create a new ToDoItem instance and persist it to a Realm of your choice. This is a useful shortcut when you don’t intend to use an object outside of the context of the database.

You’re already familiar with the type of code above. You create a new ToDoItem instance, open a write transaction, and use Realm.add(_) to persist the object.

You can now add some UI code to your view controller to let the user input new to-do items and add them to the app’s Realm.

Back in Scenes/ToDoListController.swift, scroll to addItem(), which is a method already connected to the + button in your navigation bar. Add inside addItem():

userInputAlert("Add Todo Item") { text in
  ToDoItem.add(text: text)
}

userInputAlert(_) is a UIViewController extension method (found in the Classes folder of the starter project) that presents a new alert controller on the screen and asks the user to enter some text. Once the user taps OK, you’ll receive the user-provided text in a closure.

In the callback closure, you use the new method you just created to create and persist a new to-do item to disk: ToDoItem.add(text: text).

Run the project one more time and tap on the + button. userInputAlert(_:_:) will display an alert on screen and let you enter the text for a new to-do.

Tapping OK will execute your callback and save the new to-do item to disk.

As you might have noticed, the table view still only displays the two items you fetched when initially loading the view controller.

Use Realm Studio to open the app database default.realm from the Simulator folders and check its contents. For this, you can use the SimPholders tool as mentioned in Chapter 1, “Hello Realm”:

Realm Studio displays a list of all classes stored in your file on the left-hand side and a spreadsheet-like UI on the right side letting you browse all the data persisted in the file:

Hey, that new to-do item has been successfully added – you can find it at the bottom of the list!

In fact, if you re-run the project, you’ll see it appear in your app as well:

It seems like you need a way to refresh the table view whenever the database changes.

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.

Deleting Items

Last but not least, you’re going to let the user delete items from their list.

This is quite similar to adding and modifying items: you’re going to add a new method on ToDoItem and then add the relevant code in the view controller to react to user events.

Thanks to the two UITableViewDelegate methods already included in the starter code of ToDoListController, the table view already reacts to left-swipes and displays a red Delete button:

This provides a good starting point for this chapter’s last task. Let’s get down to business!

Open Entities/ToDoItem.swift and add one last method to the extension, below toggleCompleted():

func delete() {
  guard let realm = realm else { return }
  try! realm.write {
    realm.delete(self)
  }
}

Just like before, you get a reference to the object’s Realm and then start a write transaction to perform your updates.

Note: As you’ll learn later on, if you try modifying a persisted object without starting a write transaction, your code will throw an exception. You can only modify managed objects inside a Realm write transaction.

Since the class is a Realm object, you can simply call realm.delete(self) to delete the current object from the Realm.

Finally, you need to add a few more lines in your view controller to call your new delete() method. Back in Scenes/ToDoListController.swift add below toggleItem(_:):

func deleteItem(_ item: ToDoItem) {
  item.delete()
}

Then, scroll down to tableView(_:commit:forRowAt:) and append at the bottom:

deleteItem(item)

This will call your new deleteItem(_) method, which in turn invokes the delete() method on the to-do item.

Run the app one last time, swipe left on a to-do item, and tap Delete:

Just like the previous features you added, the deletion of the object from the Realm is reflected in the table, accompanied by a pleasant animation.

With that last piece of code, your simple CRUD application is complete. You’ve learned a bit about fetching objects from a Realm file, adding and modifying existing objects, and how to react to data changes and keeping your read and write code separate.

In fact, you already possess the knowledge to create simple Realm apps! However, since working with Realm has so many advantages, you’ll want to expand your knowledge as soon as possible. Worry not, we’ve got you covered. The rest of this book provides everything you’ll need to learn about Realm in detail.

Challenges

Challenge 1: Enhance Your To-Do app With More Features

To warm up for the next chapter, work through a few small tasks to polish your to-do application.

Start by modifying the existing code so it only allows the deletion of completed tasks. Way to simulate ticking items off the list!

Finally, add a feature which allows the user to tap a cell and be presented with an alert where they can edit the current to-do item’s text. Once they close the alert, the text change will be persisted, and the UI should be updated accordingly.

This chapter didn’t go into much detail in regards to the various available APIs, so don’t worry too much if you can’t figure out how to complete this challenge . You can open the challenge folder of this chapter and peek at the completed solution code. At this point, it’s not expected you can figure out everything on your own.

These challenges might not be very complex, but they’ll get you writing some simple Realm code to warm up for the grand tour of Realm’s object features in the next chapter.

Where to Go From Here?

If you enjoyed what you learned in this tutorial, why not check out the complete Realm: Building Modern Swift Apps with Realm Database book?

Realm finds the sweet spot between the simplicity of storing data as JSON on disk and using heavy, slow ORMs like Core Data or similar that are built on top of SQLite. The Realm Database aims to be fast, performant and provide the commodities that mobile developers need such as working with objects, type-safety, and native notifications.

In this book, you’ll do the following:

  • Learn how easy it is to set up your first Realm database.
  • See how to persist and read data under the CRUD model.
  • Discover how to work with Realm configurations.
  • Design smart and responsive migrations for your Realms.
  • Create a Realm Cloud instance and sync your data in real time, across all devices, anywhere.

Realm Database has been under active development for several years. It powers apps by some of the biggest names in the App Store, including Adidas, Amazon, Nike, Starbucks, BBC, GoPro, Virgin, Cisco, Groupon and many more who have chosen to develop their mobile apps with Realm.

Realm Platform is a relatively new commercial product which allows developers to automatically synchronize data not only across Apple devices but also between any combination of Android, iPhone, Windows, or macOS apps. Realm Platform allows you to run the server software on your own infrastructure and keep your data in-house which more often suits large enterprises. Alternatively, you can use Realm Cloud which runs a Platform for you and you start syncing data very quickly and only pay for what you use.

To celebrate the launch of the book, it’s currently on sale as part of our Advanced Swift Spring Bundle for a massive 40% off. But don’t wait too long, as this deal is only on until Friday, April 27.

If you have any questions or comments on this tutorial, feel free to join the discussion below!

The post Introducing Realm: Building Modern Swift Apps with Realm Database appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4402

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>