Tutorial: Atlas Device Sync for Swift with Swift UI
On this page
- Learning Objectives
- Prerequisites
- Start with the Template
- Explore the Template App
- Open the App
- Explore the App Structure
- Run the App
- Check the Backend
- Modify the Application
- Add a New Property
- Add a Property to the Model
- Set the Priority when Creating a New Item
- Run and Test
- Change the Subscription
- Update the subscription
- Run and Test
- Conclusion
- What's Next?
Estimated time to complete: 30 minutes, depending on your experience with SwiftUI
Realm provides a Swift SDK that allows you to create a native iOS
mobile application with Swift or Objective-C. This tutorial is based on the
SwiftUI Flexible Sync Template App, named swiftui.todo.flex
, which illustrates
the creation of a to-do list application. This application enables users to:
Register their email as a new user account.
Sign in to their account with their email and password (and sign out later).
View, create, modify, and delete their own tasks.
View all tasks, even where the user is not the owner.
The template app also provides a toggle that simulates the device being in "Offline Mode." This toggle lets you quickly test Device Sync functionality on the simulator, emulating the user having no internet connection. However, you would likely remove this toggle in a production application.
This tutorial builds on the Template App. You will add a new priority
field
to the existing Item
model and update the
Flexible Sync subscription to only show items within
a range of priorities.
Learning Objectives
This tutorial illustrates how you might adapt the template app for your own needs. You would not necessarily make this change given the current structure of the template app.
In this tutorial, you will learn how to:
Update a Realm object model with a non-breaking change.
Update a Device Sync subscription.
Add a queryable field to the Device Sync configuration on the server to change which data is synchronized.
Tip
If you prefer to get started with your own application rather than follow a guided tutorial, check out the Swift Quick Start. It includes copyable code examples and the essential information that you need to set up an Atlas App Services backend.
For a SwiftUI-specific get started experience, refer to the Realm with SwiftUI QuickStart.
Prerequisites
Ensure that you have the necessary software installed. The Swift SDK requires Xcode version 13.1 or newer.
This tutorial starts with a Template App. You need an Atlas Account, an API key, and App Services CLI to create a Template App.
You can learn more about creating an Atlas account in the Atlas Getting Started documentation. For this tutorial, you need an Atlas account with a free-tier cluster.
You also need an Atlas API key for the MongoDB Cloud account you wish to log in with. You must be a Project Owner to create a Template App using App Services CLI.
To learn more about installing App Services CLI, see Install App Services CLI. After installing, run the login command using the API key for your Atlas project.
Start with the Template
This tutorial is based on the SwiftUI Flexible Sync Template App named
swiftui.todo.flex
. We start with the default app and build new features
on it.
To learn more about the Template Apps, see Template Apps.
If you don't already have an Atlas account, sign-up to deploy a Template App.
Follow the procedure described in the Create an App Services App guide, and select Create App from Template. Select the Real-time Sync template. This creates an App Services App pre-configured to use with one of the Device Sync template app clients.
After you create a template app, the UI displays a modal labeled
Get the Front-end Code for your Template. This modal
provides instructions for downloading the template app client code
as a .zip
file or using App Services CLI to get the client.
After selecting the .zip
or App Services CLI method, follow the on-screen
instructions to get the client code. For this tutorial, select the
SwiftUI (iOS + SwiftUI) client code.
Note
The default Windows ZIP utility may show the .zip file as empty. If you encounter this, use one of the third-party zip programs that are available.
The appservices apps create command sets up the backend and creates a SwiftUI template app for you to use as a base for this tutorial.
Run the following command in a terminal window to create an app
named "MyTutorialApp" that is deployed in the US-VA
region
with its environment set to "development" (instead of production
or QA).
appservices app create \ --name MyTutorialApp \ --template swiftui.todo.flex \ --deployment-model global \ --environment development
The command creates a new directory in your current path with the
same name as the value of the --name
flag.
You can fork and clone a GitHub repository that contains the Device Sync client code. The SwiftUI client code is available at https://github.com/mongodb/template-app-swiftui-todo.
If you use this process to get the client code, you must create a template app to use with the client. Follow the instructions at Create a Template App to use the Atlas App Services UI, App Services CLI, or Admin API to create a Device Sync template app.
Explore the Template App
Open the App
Open the frontend client's App.xcodeproj
in Xcode.
If you downloaded the client as a .zip
file or cloned the client
GitHub repository, you must manually insert the App Services App ID
in the appropriate place in your client. Follow the
Configuration instructions in the client README.md
to learn where to insert your App ID.
Explore the App Structure
Take a few minutes to explore how the project is organized while Swift Package Manager downloads the latest version of the Realm Swift SDK. Within the App directory, you can see a few files worth noting:
File | Purpose |
---|---|
AppConfig.swift | This file contains the logic to read the appId and baseUrl
from the Realm.plist . This is pre-populated with the
appId for your Template App. |
App.swift | This file uses the values from To learn more about how you can customize your app configuration, see: Connect to an Atlas App Services Backend. This file is also the entrypoint to the SwiftUI app. We pass the
|
In this tutorial, you'll be working in the following files:
File | Purpose |
---|---|
Item.Swift | This file, located at the root of the project, defines the
Realm object we store in the database. |
CreateItemView.swift | This file, located in the Views directory, provides the
functionality to add a new item to the list. |
ContentView.Swift | This file, located in the Views directory, defines the
Flexible Sync subscription. |
Check the Backend
Log in to Atlas App Services. In the Data Services tab, click on Browse Collections. In the list of databases, find and expand the todo database, and then the Item collection. You should see the document you created in this collection.
Modify the Application
Add a New Property
Add a Property to the Model
Now that you have confirmed everything is working as expected, we can add changes. In this tutorial, we have decided that we want to add a "priority" property to each Item so that we can filter Items by their priorities. The priority property uses a PriorityLevel enum to constrain the possible values.
To do this, follow these steps:
Open the
App.xcodeproj
in Xcode.Open the
Item.swift
class file.Add the following property to the
Item
class:var priority: PriorityLevel Also add a PriorityLevel
PersistableEnum
below theItem
class:class Item: Object, ObjectKeyIdentifiable { true) var _id: ObjectId (primaryKey:var isComplete = false var summary: String var owner_id: String var priority: PriorityLevel } enum PriorityLevel: Int, PersistableEnum, CaseIterable { case severe = 0 case high = 1 case medium = 2 case low = 3 var description: String { switch self { case .severe: return "Severe" case .high: return "High" case .medium: return "Medium" case .low: return "Low" } } } PersistableEnum is the protocol that marks enum types as persistable directly in Realm. We set the enum's type as
Int
here instead ofString
so we can query based on a numeric priority level later. We use adescription
computed property to display a string representation of the priority in the UI.
Set the Priority when Creating a New Item
In the
Views
directory, go toCreateItemView.swift
. Add a new@State
property under the existingitemSummary
property. For now, set the default value to medium priority:var itemSummary = "" var priority = PriorityLevel.medium Now, in the
Form
body, add a Picker that enables the user to choose which priority level to set on the new Item. Locate theSection
that contains the buttons, and insert the following code above it:Section(header: Text("Priority")) { Picker(selection: $priority, label: Text("Set priority")) { ForEach(PriorityLevel.allCases, id: \.self) { priority in Text(priority.description) } } } Now, move down to the
Button(action:
that sets the values of thenewItem
when the user presses theSave
button. Add a line belownewItem.summary
to also set thepriority
property:newItem.summary = itemSummary newItem.priority = priority
Run and Test
At this point, you can run the application again. Log in using the account
you created earlier in this tutorial. You will see the one Item you
previously created. Add a new Item, and you will see that you can now
set the priority. Choose High
for the priority and save the Item.
Now switch back to the Atlas data page in your browser, and refresh the
Item
collection. You should now see the new Item with the priority
field added and set to 1. The existing Item does not have a priority
field.
Note
Why Didn't This Break Sync?
Adding a property to a Realm object is not a breaking change and therefore does not require a client reset. The template app has Development Mode enabled, so changes to the client Realm object are reflected in the server-side schema. For more information, see Development Mode and Update Your Data Model.
Change the Subscription
Update the subscription
In the ContentView.swift
file, we create the Flexible Sync subscription
that defines which documents we sync with the user's device & account.
Look for the let config = user.flexibleSyncConfiguration(initialSubscriptions:
variable where we set the initial subscriptions. Within the subscriptions.append()
method, you can see that we are currently subscribing to all documents where
the owner_id
property matches the authenticated user's id. We want to maintain
that, but only sync Items that are marked as High or Severe priority.
This is why we set the PriorityLevel
enum to type Int
, where the highest
priority (severe) has a value of 0, and the lowest priority (low) has
a value of 3. We can make direct comparisons between an Int and the
priority property. To do so, update the query statement to include documents
where the priority is equal to or less than PriorityLevel.High (or 1), as
shown here.
We'll also add the reRunOnOpen
bool, and set it to true
,
to force the subscription query to recalculate which documents to sync
every time we open the app.
let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in if let foundSubscription = subs.first(named: Constants.myItems) { foundSubscription.updateQuery(toType: Item.self, where: { $0.owner_id == user.id && $0.priority <= PriorityLevel.high }) } else { // No subscription - create it subs.append(QuerySubscription<Item>(name: Constants.myItems) { $0.owner_id == user.id && $0.priority <= PriorityLevel.high }) } }, rerunOnOpen: true)
Run and Test
Run the application again. Log in using the account you created earlier
in this tutorial. Because we added reRunOnOpen
, the app
should re-sync only the documents that match the Flexible Sync query.
After an initial moment when Realm resyncs the document collection, you
will only see the new Item of High priority that you created.
The Item document you initially created is not synced,
because it does not have a priority
field. If you want this Item to
be synced, you can edit the document in the Atlas UI and add a value for
the priority field.
Tip
Changing Subscriptions with Developer Mode Enabled
In this tutorial, when you change the subscription and query on the priority field for the first time, the field is automatically added to the Device Sync Collection Queryable Fields. This occurs because the template app has Development Mode enabled by default. If Development Mode was not enabled, you would have to manually add the field as a queryable field to use it in a client-side Sync query.
For more information, refer to Queryable Fields.
If you want to further test the functionality, you can create Items of various priorities. You will see that a new Item with a lower priority briefly appears in the list of Items and then disappears. The Sync error handler helpfully provides a message describing this behavior:
ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted"
You can also see this message in the console log.
In this scenario, Realm creates the Item locally, syncs it with the backend, and then reverts the write because it doesn't meet the subscription rules.
Conclusion
Adding a property to an existing Realm object is a non-breaking change, and Development Mode ensures that the schema change is reflected server-side.
What's Next?
Consider adding the new
Priority
property to theItemList
,ItemRow
, andItemDetail
Views.Find developer-oriented blog posts and integration tutorials on the MongoDB Developer Hub.
Join the MongoDB Community forum to learn from other MongoDB developers and technical experts.
Explore engineering and expert-provided example projects.
Note
Share Feedback
How did it go? Use the Rate this page widget at the bottom right of the page to rate its effectiveness. Or file an issue on the GitHub repository if you had any issues.