-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(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
- Loading branch information
Showing
19 changed files
with
1,725 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,311 @@ | ||
.. _swiftui-background-sync: | ||
|
||
===================================== | ||
Sync Data in the Background - SwiftUI | ||
===================================== | ||
|
||
.. meta:: | ||
:description: Learn how to use a SwiftUI BackgroundTask to sync data in the background. | ||
:keywords: Realm, Swift SDK, code example | ||
|
||
.. facet:: | ||
:name: genre | ||
:values: tutorial | ||
|
||
.. facet:: | ||
:name: programming_language | ||
:values: swift | ||
|
||
.. contents:: On this page | ||
:local: | ||
:backlinks: none | ||
:depth: 2 | ||
:class: singlecol | ||
|
||
Overview | ||
-------- | ||
|
||
You can use a SwiftUI :apple:`BackgroundTask <documentation/SwiftUI/BackgroundTask>` | ||
to update a synced database when your app is in the background. This example | ||
demonstrates how to configure and perform background syncing in an iOS app. | ||
|
||
You can follow along with the example on this page using the SwiftUI Device | ||
Sync Template App. To get your own copy of the SwiftUI Device Sync | ||
Template App, check out the :ref:`Device Sync SwiftUI tutorial | ||
<swift-swiftui-tutorial>` and go through the :guilabel:`Prerequisites` | ||
and :guilabel:`Start with the Template` sections. | ||
|
||
Enable Background Modes for Your App | ||
------------------------------------ | ||
|
||
To enable background tasks for your app: | ||
|
||
.. procedure:: | ||
|
||
.. step:: Add Background Modes Capability | ||
|
||
Select your app Target, go to the :guilabel:`Signing & Capabilities` | ||
tab, and click :guilabel:`+ Capability` to add the capability. | ||
|
||
.. figure:: /images/xcode-select-target-add-capability.png | ||
:alt: Screenshot of Xcode with app Target selected, Signing & Capabilities tab open, and arrow pointing to add Capabilities. | ||
:lightbox: | ||
|
||
Search for "background", and select :guilabel:`Background Modes`. | ||
|
||
.. step:: Select Background Modes | ||
|
||
Now you should see a :guilabel:`Background Modes` section in your | ||
:guilabel:`Signing & Capabilities` tab. Expand this section, and | ||
click the checkboxes to enable :guilabel:`Background fetch` and | ||
:guilabel:`Background processing`. | ||
|
||
.. step:: Update the Info.plist | ||
|
||
Go to your project's :file:`Info.plist`, and add a new row for | ||
``Permitted background task scheduler identifiers``. If you are | ||
viewing raw keys and values, the key is | ||
``BGTaskSchedulerPermittedIdentifiers``. This field is an array. | ||
Add a new item to it for your background task identifier. Set the | ||
new item's value to the string you intend to use as the identifier | ||
for your background task. For example: ``refreshTodoRealm``. | ||
|
||
Schedule a Background Task | ||
-------------------------- | ||
|
||
After enabling background processes for your app, you can start adding the | ||
code to the app to schedule and execute a background task. First, import | ||
``BackgroundTasks`` in the files where you will write this code: | ||
|
||
.. code-block:: swift | ||
:emphasize-lines: 3 | ||
|
||
import SwiftUI | ||
import RealmSwift | ||
import BackgroundTasks | ||
|
||
Now you can add a scheduled background task. If you're following along | ||
via the Template App, you can update your ``@main`` view: | ||
|
||
.. code-block:: swift | ||
:emphasize-lines: 3, 9-14 | ||
|
||
@main | ||
struct realmSwiftUIApp: SwiftUI.App { | ||
@Environment(\.scenePhase) private var phase | ||
|
||
var body: some Scene { | ||
WindowGroup { | ||
ContentView(app: realmApp) | ||
} | ||
.onChange(of: phase) { newPhase in | ||
switch newPhase { | ||
case .background: scheduleAppRefresh() | ||
default: break | ||
} | ||
} | ||
} | ||
|
||
You can add an environment variable to store a change to the ``scenePhase``: | ||
``@Environment(\.scenePhase) private var phase``. | ||
|
||
Then, you can add the ``.onChange(of: phase)`` block that calls the | ||
``scheduleAppRefresh()`` function when the app goes into the background. | ||
|
||
Create the ``scheduleAppRefresh()`` function: | ||
|
||
.. code-block:: swift | ||
|
||
func scheduleAppRefresh() { | ||
let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm") | ||
backgroundTask.earliestBeginDate = .now.addingTimeInterval(10) | ||
try? BGTaskScheduler.shared.submit(backgroundTask) | ||
} | ||
|
||
This schedules the work to execute the background task whose identifier you | ||
added to the Info.plist above when you enabled Background Modes. In this | ||
example, the identifier ``refreshTodoRealm`` refers to this task. | ||
|
||
Create the Background Task | ||
-------------------------- | ||
|
||
Now that you've scheduled the background task, you need to create the background | ||
task that will run to update the synced realm. | ||
|
||
If you're following along with the Template App, you can add this | ||
``backgroundTask`` to your ``@main`` view, after the ``.onChange(of: phase)``: | ||
|
||
.. code-block:: swift | ||
:emphasize-lines: 7-23 | ||
|
||
.onChange(of: phase) { newPhase in | ||
switch newPhase { | ||
case .background: scheduleAppRefresh() | ||
default: break | ||
} | ||
} | ||
.backgroundTask(.appRefresh("refreshTodoRealm")) { | ||
guard let user = realmApp.currentUser else { | ||
return | ||
} | ||
let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in | ||
if let foundSubscription = subs.first(named: "user_tasks") { | ||
foundSubscription.updateQuery(toType: Item.self, where: { | ||
$0.owner_id == user.id | ||
}) | ||
} else { | ||
subs.append(QuerySubscription<Item>(name: "user_tasks") { | ||
$0.owner_id == user.id | ||
}) | ||
} | ||
}, rerunOnOpen: true) | ||
await refreshSyncedRealm(config: config) | ||
} | ||
|
||
This background task first checks that your app has a logged-in user. If so, | ||
it sets a :swift-sdk:`.flexibleSyncConfiguration | ||
<Extensions/User.html#/s:So7RLMUserC10RealmSwiftE25flexibleSyncConfiguration15clientResetMode20initialSubscriptions11rerunOnOpenAC0B0V0F0VAC06ClienthI0O_yAC0E15SubscriptionSetVcSbtF>` | ||
with a :ref:`subscription <sdks-manage-sync-subscriptions>` the | ||
app can use to sync the realm. | ||
|
||
This is the same configuration used in the Template App's ``ContentView``. | ||
However, to use it here you need access to it farther up the view hierarchy. | ||
You could refactor this to a function you can call from either view that | ||
takes a :swift-sdk:`User <Typealiases.html#/s:10RealmSwift4Usera>` as a | ||
parameter and returns a :swift-sdk:`Realm.configuration | ||
<Structs/Realm/Configuration.html>`. | ||
|
||
Finally, this task awaits the result of a function that actually syncs the | ||
database. Add this function: | ||
|
||
.. code-block:: swift | ||
|
||
func refreshSyncedRealm(config: Realm.Configuration) async { | ||
do { | ||
try await Realm(configuration: config, downloadBeforeOpen: .always) | ||
} catch { | ||
print("Error opening the Synced realm: \(error.localizedDescription)") | ||
} | ||
} | ||
|
||
By opening this synced database and using the ``downloadBeforeOpen`` parameter | ||
to specify that you want to download updates, you load the fresh data into | ||
the database in the background. Then, when your app opens again, it already | ||
has the updated data on the device. | ||
|
||
.. important:: | ||
|
||
Do not try to write to the database directly in this background task. You | ||
may encounter threading-related issues due to the SDK's thread-confined | ||
architecture. | ||
|
||
Test Your Background Task | ||
------------------------- | ||
|
||
When you schedule a background task, you are setting the earliest time that | ||
the system could execute the task. However, the operating system factors in | ||
many other considerations that may delay the execution of the background task | ||
long after your scheduled ``earliestBeginDate``. Instead of waiting for a | ||
device to run the background task to verify it does what you intend, you can | ||
set a breakpoint and use LLDB to invoke the task. | ||
|
||
.. procedure:: | ||
|
||
.. step:: Configure a Device to Run Your App | ||
|
||
To test that your background task is updating the synced database in the | ||
background, you'll need a physical device running at minimum iOS 16. | ||
Your device must be configured to run in :apple:`Developer Mode | ||
<documentation/xcode/enabling-developer-mode-on-a-device>`. If you get an | ||
``Untrusted Developer`` notification, go to :guilabel:`Settings`, | ||
:guilabel:`General`, and :guilabel:`VPN & Device Management`. Here, you | ||
can verify that you want to run the app you're developing. | ||
|
||
Once you can successfully run your app on your device, you can test the | ||
background task. | ||
|
||
.. step:: Set a Breakpoint | ||
|
||
Start by setting a breakpoint in your ``scheduleAppRefresh()`` function. | ||
Set the breakpoint *after* the line where you submit the task to | ||
``BGTaskScheduler``. For this example, you might add a ``print`` line and | ||
set the breakpoint at the print line: | ||
|
||
.. code-block:: swift | ||
:emphasize-lines: 5 | ||
|
||
func scheduleAppRefresh() { | ||
let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm") | ||
backgroundTask.earliestBeginDate = .now.addingTimeInterval(10) | ||
try? BGTaskScheduler.shared.submit(backgroundTask) | ||
print("Successfully scheduled a background task") // Set a breakpoint here | ||
} | ||
|
||
.. step:: Run the App | ||
|
||
Now, run the app on the connected device. Create or sign into an account | ||
in the app. If you're using the SwiftUI Template App, create some Items. | ||
You should see the Items sync to the ``Item`` collection linked to your | ||
Atlas App Services app. | ||
|
||
Then, while leaving the app running in Xcode, send the app to the background | ||
on your device. You should see the console print "Successfully scheduled a | ||
background task" and then get an LLDB prompt. | ||
|
||
.. step:: Add or Change Data in Atlas | ||
|
||
While the app is in the background but still running in Xcode, Insert a new | ||
document in the relevant Atlas collection that should sync to the device. | ||
Alternately, change a value of an existing document that you created from | ||
the device. After successfully running the background task, you should | ||
see this data synced to the device from the background process. | ||
|
||
If you're using the SwiftUI Template App, you can find relevant documents | ||
in your Atlas cluster's ``Item`` collection. For more information on how | ||
to add or change documents in Atlas, see: :atlas:`MongoDB Atlas: Create, | ||
View, Update, and Delete Documents </atlas-ui/documents/>`. | ||
|
||
.. step:: Invoke the Background Task in LLDB | ||
|
||
Use this command to manually execute the background task in LLDB: | ||
|
||
.. code-block:: shell | ||
|
||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"refreshTodoRealm"] | ||
|
||
If you have used a different identifier for your background task, replace | ||
``refreshTodoRealm`` with your task's identifier. This causes the task to | ||
immediately begin executing. | ||
|
||
If successful, you should see something like: | ||
|
||
.. code-block:: shell | ||
|
||
2022-11-11 15:09:10.403242-0500 App[1268:196548] Simulating launch for task with identifier refreshTodoRealm | ||
2022-11-11 15:09:16.530201-0500 App[1268:196811] Starting simulated task | ||
|
||
After you have kicked off the task, use the :guilabel:`Continue program execution` | ||
button in the Xcode debug panel to resume running the app. | ||
|
||
.. step:: Turn on Airplane Mode on the Device | ||
|
||
After waiting for the background task to complete, but before you open the | ||
app again, turn on Airplane Mode on the device. Make sure you have turned | ||
off WiFi. This ensures that when you open the app again, it doesn't | ||
start a fresh Sync and you see only the values that are now in the database | ||
on the device. | ||
|
||
.. step:: Open the App | ||
|
||
Open the app on the device. You should see the updated data that you changed | ||
in Atlas. | ||
|
||
To verify the updates came through the background task, confirm you have | ||
successfully disabled the network. | ||
|
||
Create a new task using the app. You should see the task in the app, but | ||
it should not sync to Atlas. Alternately, you could create or change data | ||
in Atlas, but should not see it reflected on the device. | ||
|
||
This tells you that the network has successfully been disabled, | ||
and the updated data that you see came through the background task. |
Oops, something went wrong.