Skip to content

Commit ccb45ee

Browse files
authored
(DOCSP-39546): Port SwiftUI content for consolidated docs (#3365)
## Pull Request Info - SDK Docs Consolidation This PR ports the existing SwiftUI content over to the Frameworks section, updates Realm naming, and removes PBS mentions and examples. Jira ticket: https://jira.mongodb.org/browse/DOCSP-39546 ### Staging Links - [Object Models - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/model-data/define-an-object-model/) - [Change an Object Model - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/model-data/change-an-object-model/) - [Configure and Open a Database - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/configure-and-open-database/) - [React to Changes - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/react-to-changes/) - [Pass Data Between SwiftUI Views](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/pass-data-between-views/) - [Write Data - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/write/) - [Filter Data - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/filter-data/) - [Handle Sync Errors - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/handle-sync-errors/) - [Sync Data in the Background - SwiftUI](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/background-sync/) - [Use the SDK with SwiftUI Previews](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-39546/frameworks/swiftui/swiftui-previews/) *Page Source* Add links to every SDK's pages where you got the SDK-specific information: - [SwiftUI section](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/swiftui/) ### PR Author Checklist Before requesting a review for your PR, please check these items: - [x] Open the PR against the `feature-consolidated-sdk-docs` branch instead of `master` - [x] Tag the consolidated page for: - genre - meta.keywords - meta.description #### Naming - [x] Update Realm naming and the language around persistence layer/local/device per [this document](https://docs.google.com/document/d/126OczVxBWAwZ4P5ZsSM29WI3REvONEr1ald-mAwPtyQ/edit?usp=sharing) - [ ] Include `.rst` files comply with [the naming guidelines](https://docs.google.com/document/d/1h8cr66zoEVeXytVfvDxlCSsUS5IZwvUQvfSCEXNMpek/edit#heading=h.ulh8b5f2hu9) #### Links and Refs - [ ] Create new consolidated SDK ref targets starting with "_sdks-" for relevant sections - [ ] Remove or update any SDK-specific refs to use the new consolidated SDK ref targets - [ ] [Update any Kotlin API links](https://jira.mongodb.org/browse/DOCSP-32519) to use the new Kotlin SDK roles #### Content - [ ] Shared code boxes have snippets or placeholders for all 9 languages - [ ] API description sections have API details or a generic placeholder for all 9 languages - [ ] Check related pages for relevant content to include - [ ] Create a ticket for missing examples in each relevant SDK: Consolidation Gaps epic ### Reviewer Checklist As a reviewer, please check these items: - [ ] Shared code example boxes contain language-specific snippets or placeholders for every language - [ ] API reference details contain working API reference links or generic content - [ ] Realm naming/language has been updated - [ ] All relevant content from individual SDK pages is present on the consolidated page
1 parent c2b6210 commit ccb45ee

19 files changed

+1725
-81
lines changed

source/frameworks/swiftui.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ Build with SwiftUI
88
:titlesonly:
99

1010
Quick Start </frameworks/swiftui/quick-start>
11+
Model Data </frameworks/swiftui/model-data>
12+
Configure and Open a Database </frameworks/swiftui/configure-and-open-database>
13+
React to Changes </frameworks/swiftui/react-to-changes>
14+
Pass Data Between Views </frameworks/swiftui/pass-data-between-views>
15+
Write Data </frameworks/swiftui/write>
16+
Filter Data </frameworks/swiftui/filter-data>
17+
Handle Sync Errors </frameworks/swiftui/handle-sync-errors>
18+
Sync Data in the Background </frameworks/swiftui/background-sync>
19+
Use the SDK with SwiftUI Previews </frameworks/swiftui/swiftui-previews>
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
.. _swiftui-background-sync:
2+
3+
=====================================
4+
Sync Data in the Background - SwiftUI
5+
=====================================
6+
7+
.. meta::
8+
:description: Learn how to use a SwiftUI BackgroundTask to sync data in the background.
9+
:keywords: Realm, Swift SDK, code example
10+
11+
.. facet::
12+
:name: genre
13+
:values: tutorial
14+
15+
.. facet::
16+
:name: programming_language
17+
:values: swift
18+
19+
.. contents:: On this page
20+
:local:
21+
:backlinks: none
22+
:depth: 2
23+
:class: singlecol
24+
25+
Overview
26+
--------
27+
28+
You can use a SwiftUI :apple:`BackgroundTask <documentation/SwiftUI/BackgroundTask>`
29+
to update a synced database when your app is in the background. This example
30+
demonstrates how to configure and perform background syncing in an iOS app.
31+
32+
You can follow along with the example on this page using the SwiftUI Device
33+
Sync Template App. To get your own copy of the SwiftUI Device Sync
34+
Template App, check out the :ref:`Device Sync SwiftUI tutorial
35+
<swift-swiftui-tutorial>` and go through the :guilabel:`Prerequisites`
36+
and :guilabel:`Start with the Template` sections.
37+
38+
Enable Background Modes for Your App
39+
------------------------------------
40+
41+
To enable background tasks for your app:
42+
43+
.. procedure::
44+
45+
.. step:: Add Background Modes Capability
46+
47+
Select your app Target, go to the :guilabel:`Signing & Capabilities`
48+
tab, and click :guilabel:`+ Capability` to add the capability.
49+
50+
.. figure:: /images/xcode-select-target-add-capability.png
51+
:alt: Screenshot of Xcode with app Target selected, Signing & Capabilities tab open, and arrow pointing to add Capabilities.
52+
:lightbox:
53+
54+
Search for "background", and select :guilabel:`Background Modes`.
55+
56+
.. step:: Select Background Modes
57+
58+
Now you should see a :guilabel:`Background Modes` section in your
59+
:guilabel:`Signing & Capabilities` tab. Expand this section, and
60+
click the checkboxes to enable :guilabel:`Background fetch` and
61+
:guilabel:`Background processing`.
62+
63+
.. step:: Update the Info.plist
64+
65+
Go to your project's :file:`Info.plist`, and add a new row for
66+
``Permitted background task scheduler identifiers``. If you are
67+
viewing raw keys and values, the key is
68+
``BGTaskSchedulerPermittedIdentifiers``. This field is an array.
69+
Add a new item to it for your background task identifier. Set the
70+
new item's value to the string you intend to use as the identifier
71+
for your background task. For example: ``refreshTodoRealm``.
72+
73+
Schedule a Background Task
74+
--------------------------
75+
76+
After enabling background processes for your app, you can start adding the
77+
code to the app to schedule and execute a background task. First, import
78+
``BackgroundTasks`` in the files where you will write this code:
79+
80+
.. code-block:: swift
81+
:emphasize-lines: 3
82+
83+
import SwiftUI
84+
import RealmSwift
85+
import BackgroundTasks
86+
87+
Now you can add a scheduled background task. If you're following along
88+
via the Template App, you can update your ``@main`` view:
89+
90+
.. code-block:: swift
91+
:emphasize-lines: 3, 9-14
92+
93+
@main
94+
struct realmSwiftUIApp: SwiftUI.App {
95+
@Environment(\.scenePhase) private var phase
96+
97+
var body: some Scene {
98+
WindowGroup {
99+
ContentView(app: realmApp)
100+
}
101+
.onChange(of: phase) { newPhase in
102+
switch newPhase {
103+
case .background: scheduleAppRefresh()
104+
default: break
105+
}
106+
}
107+
}
108+
109+
You can add an environment variable to store a change to the ``scenePhase``:
110+
``@Environment(\.scenePhase) private var phase``.
111+
112+
Then, you can add the ``.onChange(of: phase)`` block that calls the
113+
``scheduleAppRefresh()`` function when the app goes into the background.
114+
115+
Create the ``scheduleAppRefresh()`` function:
116+
117+
.. code-block:: swift
118+
119+
func scheduleAppRefresh() {
120+
let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm")
121+
backgroundTask.earliestBeginDate = .now.addingTimeInterval(10)
122+
try? BGTaskScheduler.shared.submit(backgroundTask)
123+
}
124+
125+
This schedules the work to execute the background task whose identifier you
126+
added to the Info.plist above when you enabled Background Modes. In this
127+
example, the identifier ``refreshTodoRealm`` refers to this task.
128+
129+
Create the Background Task
130+
--------------------------
131+
132+
Now that you've scheduled the background task, you need to create the background
133+
task that will run to update the synced realm.
134+
135+
If you're following along with the Template App, you can add this
136+
``backgroundTask`` to your ``@main`` view, after the ``.onChange(of: phase)``:
137+
138+
.. code-block:: swift
139+
:emphasize-lines: 7-23
140+
141+
.onChange(of: phase) { newPhase in
142+
switch newPhase {
143+
case .background: scheduleAppRefresh()
144+
default: break
145+
}
146+
}
147+
.backgroundTask(.appRefresh("refreshTodoRealm")) {
148+
guard let user = realmApp.currentUser else {
149+
return
150+
}
151+
let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in
152+
if let foundSubscription = subs.first(named: "user_tasks") {
153+
foundSubscription.updateQuery(toType: Item.self, where: {
154+
$0.owner_id == user.id
155+
})
156+
} else {
157+
subs.append(QuerySubscription<Item>(name: "user_tasks") {
158+
$0.owner_id == user.id
159+
})
160+
}
161+
}, rerunOnOpen: true)
162+
await refreshSyncedRealm(config: config)
163+
}
164+
165+
This background task first checks that your app has a logged-in user. If so,
166+
it sets a :swift-sdk:`.flexibleSyncConfiguration
167+
<Extensions/User.html#/s:So7RLMUserC10RealmSwiftE25flexibleSyncConfiguration15clientResetMode20initialSubscriptions11rerunOnOpenAC0B0V0F0VAC06ClienthI0O_yAC0E15SubscriptionSetVcSbtF>`
168+
with a :ref:`subscription <sdks-manage-sync-subscriptions>` the
169+
app can use to sync the realm.
170+
171+
This is the same configuration used in the Template App's ``ContentView``.
172+
However, to use it here you need access to it farther up the view hierarchy.
173+
You could refactor this to a function you can call from either view that
174+
takes a :swift-sdk:`User <Typealiases.html#/s:10RealmSwift4Usera>` as a
175+
parameter and returns a :swift-sdk:`Realm.configuration
176+
<Structs/Realm/Configuration.html>`.
177+
178+
Finally, this task awaits the result of a function that actually syncs the
179+
database. Add this function:
180+
181+
.. code-block:: swift
182+
183+
func refreshSyncedRealm(config: Realm.Configuration) async {
184+
do {
185+
try await Realm(configuration: config, downloadBeforeOpen: .always)
186+
} catch {
187+
print("Error opening the Synced realm: \(error.localizedDescription)")
188+
}
189+
}
190+
191+
By opening this synced database and using the ``downloadBeforeOpen`` parameter
192+
to specify that you want to download updates, you load the fresh data into
193+
the database in the background. Then, when your app opens again, it already
194+
has the updated data on the device.
195+
196+
.. important::
197+
198+
Do not try to write to the database directly in this background task. You
199+
may encounter threading-related issues due to the SDK's thread-confined
200+
architecture.
201+
202+
Test Your Background Task
203+
-------------------------
204+
205+
When you schedule a background task, you are setting the earliest time that
206+
the system could execute the task. However, the operating system factors in
207+
many other considerations that may delay the execution of the background task
208+
long after your scheduled ``earliestBeginDate``. Instead of waiting for a
209+
device to run the background task to verify it does what you intend, you can
210+
set a breakpoint and use LLDB to invoke the task.
211+
212+
.. procedure::
213+
214+
.. step:: Configure a Device to Run Your App
215+
216+
To test that your background task is updating the synced database in the
217+
background, you'll need a physical device running at minimum iOS 16.
218+
Your device must be configured to run in :apple:`Developer Mode
219+
<documentation/xcode/enabling-developer-mode-on-a-device>`. If you get an
220+
``Untrusted Developer`` notification, go to :guilabel:`Settings`,
221+
:guilabel:`General`, and :guilabel:`VPN & Device Management`. Here, you
222+
can verify that you want to run the app you're developing.
223+
224+
Once you can successfully run your app on your device, you can test the
225+
background task.
226+
227+
.. step:: Set a Breakpoint
228+
229+
Start by setting a breakpoint in your ``scheduleAppRefresh()`` function.
230+
Set the breakpoint *after* the line where you submit the task to
231+
``BGTaskScheduler``. For this example, you might add a ``print`` line and
232+
set the breakpoint at the print line:
233+
234+
.. code-block:: swift
235+
:emphasize-lines: 5
236+
237+
func scheduleAppRefresh() {
238+
let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm")
239+
backgroundTask.earliestBeginDate = .now.addingTimeInterval(10)
240+
try? BGTaskScheduler.shared.submit(backgroundTask)
241+
print("Successfully scheduled a background task") // Set a breakpoint here
242+
}
243+
244+
.. step:: Run the App
245+
246+
Now, run the app on the connected device. Create or sign into an account
247+
in the app. If you're using the SwiftUI Template App, create some Items.
248+
You should see the Items sync to the ``Item`` collection linked to your
249+
Atlas App Services app.
250+
251+
Then, while leaving the app running in Xcode, send the app to the background
252+
on your device. You should see the console print "Successfully scheduled a
253+
background task" and then get an LLDB prompt.
254+
255+
.. step:: Add or Change Data in Atlas
256+
257+
While the app is in the background but still running in Xcode, Insert a new
258+
document in the relevant Atlas collection that should sync to the device.
259+
Alternately, change a value of an existing document that you created from
260+
the device. After successfully running the background task, you should
261+
see this data synced to the device from the background process.
262+
263+
If you're using the SwiftUI Template App, you can find relevant documents
264+
in your Atlas cluster's ``Item`` collection. For more information on how
265+
to add or change documents in Atlas, see: :atlas:`MongoDB Atlas: Create,
266+
View, Update, and Delete Documents </atlas-ui/documents/>`.
267+
268+
.. step:: Invoke the Background Task in LLDB
269+
270+
Use this command to manually execute the background task in LLDB:
271+
272+
.. code-block:: shell
273+
274+
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"refreshTodoRealm"]
275+
276+
If you have used a different identifier for your background task, replace
277+
``refreshTodoRealm`` with your task's identifier. This causes the task to
278+
immediately begin executing.
279+
280+
If successful, you should see something like:
281+
282+
.. code-block:: shell
283+
284+
2022-11-11 15:09:10.403242-0500 App[1268:196548] Simulating launch for task with identifier refreshTodoRealm
285+
2022-11-11 15:09:16.530201-0500 App[1268:196811] Starting simulated task
286+
287+
After you have kicked off the task, use the :guilabel:`Continue program execution`
288+
button in the Xcode debug panel to resume running the app.
289+
290+
.. step:: Turn on Airplane Mode on the Device
291+
292+
After waiting for the background task to complete, but before you open the
293+
app again, turn on Airplane Mode on the device. Make sure you have turned
294+
off WiFi. This ensures that when you open the app again, it doesn't
295+
start a fresh Sync and you see only the values that are now in the database
296+
on the device.
297+
298+
.. step:: Open the App
299+
300+
Open the app on the device. You should see the updated data that you changed
301+
in Atlas.
302+
303+
To verify the updates came through the background task, confirm you have
304+
successfully disabled the network.
305+
306+
Create a new task using the app. You should see the task in the app, but
307+
it should not sync to Atlas. Alternately, you could create or change data
308+
in Atlas, but should not see it reflected on the device.
309+
310+
This tells you that the network has successfully been disabled,
311+
and the updated data that you see came through the background task.

0 commit comments

Comments
 (0)