/
write-to-synced-realm.txt
335 lines (246 loc) · 13 KB
/
write-to-synced-realm.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
.. _swift-write-synced-realm:
========================================
Write Data to a Synced Realm - Swift SDK
========================================
.. contents:: On this page
:local:
:backlinks: none
:depth: 3
:class: singlecol
Overview
--------
When writing data to a synced realm using Flexible Sync, you can use the
same APIs as writing to a local realm. However, there are some differences
in behavior to keep in mind as you develop your application.
When you write to a synced realm, your write operations must match *both*
of the following:
- **The sync subscription query.**
- If your write operation doesn't match the query in the subscription,
the write reverts with a non-fatal compensating write error (ErrorCompensatingWrite).
- **The permissions** in your App Services App.
- If your try to write data that doesn't match the permissions expression,
the write reverts with a non-fatal permission denied error. In the client,
this shows as an error (ErrorCompensatingWrite). On the server, you can see more
details about how the write was denied was by a write filter in the role.
- To learn more about configuring permissions for your app, see
:ref:`sync-rules` and the :ref:`flexible-sync-permissions-guide` in the
App Services documentation.
.. warning:: Multiprocess Sync is Not Supported
Device Sync does not currently support opening or writing to a synced
realm from more than one process. For more information, including
suggested alternatives, refer to: :ref:`multiprocess-sync-not-supported`.
Determining What Data Syncs
---------------------------
The data that you can write to a synced realm is the intersection of your Device
Sync configuration, your permissions, and the Flexible Sync subscription query
that you use when you open the realm.
The examples on this page use the following configurations and models:
App Services Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~
Device Sync is configured with the following queryable fields:
- ``_id`` (always included)
- ``miles``
- ``ownerId``
The App Services App has permissions configured to let users read and write only their own
data:
.. code-block:: json
{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "ownerId": "%%user.id" },
"write": { "ownerId": "%%user.id" }
},
"read": true,
"write": true
}
Client Data Model and Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The examples on this page use the following object model:
.. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.flexible-sync-crud-model.swift
:language: swift
Using that object model, the synced realm configuration syncs objects that
match the subscription query where the ``complexity`` property's value
is less than or equal to ``4``:
.. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.flexible-sync-subscription-setup.swift
:language: swift
What Data Syncs?
~~~~~~~~~~~~~~~~
The subscription query combined with the permissions mean that the synced realm
only syncs objects where:
- The ``ownerId`` matches the ``user.id`` of the logged-in user (from the permissions)
- The ``complexity`` property's value is less than or equal to ``4`` (from the subscription query)
Any object in the Atlas collection where the ``ownerId`` does not match
the ``user.id`` of the logged-in user, or the ``complexity`` property's
value is greater than ``4``, cannot sync to this realm.
Write to a Synced Realm
-----------------------
Writes to Flexible Sync realms may broadly fall into one of two categories:
- **Successful writes**: The written object matches both the query subscription
and the user's permissions. The object writes successfully to the realm,
and syncs successfully to the App Services backend and other devices.
- **Compensating writes**: When the written object does not match
the subscription query, or where the user does not have sufficient
permissions to perform the write, Realm reverts the illegal write.
.. _swift-successful-writes:
Successful Writes
~~~~~~~~~~~~~~~~~
When the write matches both the :ref:`permissions <permissions>` and the
:ref:`Flexible Sync subscription query
<swift-manage-flexible-sync-subscriptions>` in the client, the Realm Swift SDK
can successfully write the object to the synced realm. This object syncs with
the App Services backend when the device has a network connection.
.. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.successful-write.swift
:language: swift
.. _swift-compensating-writes:
Compensating Writes
~~~~~~~~~~~~~~~~~~~
In some cases, a write that initially appears to succeed is actually an
illegal write. In these cases, the object writes to the database, but when
the database syncs to the backend, Realm reverts the write in a non-fatal
error operation called a compensating write. Compensating writes can occur
when:
- **Writes that don't match the query subscription**: The written object matches
the user's permissions, but does not match the query subscription.
- **Writes that don't match permissions**: The written object matches the query
subscription, but does not match the user's permissions.
In more detail, when you write data that is outside the bounds of a query
subscription or does not match the user's permissions, the following occurs:
#. Because the client realm has no concept of "illegal" writes,
the write initially succeeds until realm resolves the changeset
with the App Services backend.
#. Upon sync, the server applies the rules and permissions.
The server determines that the user does not have authorization to perform the write.
#. The server sends a revert operation, called a "compensating write", back to the client.
#. The client's realm reverts the illegal write operation by deleting the illegal object from the realm.
Any client-side writes to a given object between an illegal write to that object
and the corresponding compensating write will be lost.
In practice, this may look like an object being written to the realm, and
then disappearing after the server sends the compensating write back to
the client.
To learn more about permission denied errors, compensating write errors
and other Device Sync error types, refer to :ref:`sync-errors` in the App Services documentation.
.. _swift-writes-outside-subscription:
Writes that Don't Match the Query Subscription
``````````````````````````````````````````````
You can only write objects to a Flexible Sync realm if they match the
subscription query. If you perform a write that does not match the
subscription query, Realm initially writes the object, but then performs
a compensating write. This is a non-fatal operation that
reverts an illegal write that does not match the subscription query.
In practice, this may look like the write succeeding, but then the
object "disappears" when Realm syncs with the App Services backend and
performs the compensating write.
If you want to write an object that does not match the query subscription,
you must open a different realm where the object matches the query
subscription. Alternately, you could write the object to a :ref:`non-synced
realm <swift-synced-realms-vs-non-synced-realms>` that does not enforce
permissions or subscription queries. There is no way to write an object
to a Flexible Sync realm that does not match the query subscription.
Code Example
++++++++++++
Given the configuration for the Flexible Sync realm above, attempting to
write this object does not match the query subscription:
.. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.write-outside-flexible-sync-query.swift
:language: swift
Client Error
++++++++++++
The error message in the client-side logs in this scenario is:
.. code-block:: console
Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)
App Services Error
++++++++++++++++++
The error message in the App Services logs in this scenario is:
.. code-block:: console
"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\"
in table \"FlexibleSync_Item\" not allowed; object is outside of
the current query view"
}
.. _swift-writes-outside-permissions:
Writes That Don't Match Permissions
```````````````````````````````````
Attempting to write to the client can also trigger a compensating write
error when the object does not match the user's server-side write permissions.
On the client, this type of write behaves the same as a write that doesn't
match the query subscription. In practice, this may look like the write
succeeding, but then the object "disappears" when Realm syncs with the
App Services backend and performs the compensating write.
Code Example
++++++++++++
Given the permissions in the Device Sync Configuration detailed above,
attempting to write an object where the ``ownerId`` property does not match
the ``user.id`` of the logged-in user is not a legal write:
.. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.write-outside-permissions.swift
:language: swift
Client Error
++++++++++++
The client error in this scenario is the same as when you attempt to write
an object that is outside the query filter:
.. code-block:: console
Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)
App Services Error
++++++++++++++++++
The error message in the App Services logs provides some additional
information to help you determine that it is a permissions issue,
and not a query subscription issue. In this example, the error message shows
that the the object does not match the user's role:
.. code-block:: console
"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b8": "write to \"63bdfc40f16be7b1e8c7e4b8\"
in table \"FlexibleSync_Item\" was denied by write filter in role
\"owner-read-write\""
}
.. _multiprocess-sync-not-supported:
Don't Write to a Synced Realm in an App Extension
-------------------------------------------------
If you are developing an app that uses App Extensions, such as a Share
Extension, avoid writing to a synced realm in that extension. Device Sync
supports opening a synced realm in at most one process. In practice, this
means that if your app uses a synced realm in an App Extension, it may
crash intermittently.
Crashes Related to Opening a Synced Realm in Multiple Processes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you attempt to open a synced realm in a Share Extension or other App
Extension, and that realm is not open in the main app, a write from a Share
Extension may succeed. However, if the synced realm is already open in the
main app, or is syncing data in the background, you may see a crash related
to ``Realm::MultiSyncAgents``. In this scenario, you may need to restart
the device.
Alternatives to Writing to a Synced Realm in an App Extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to read from or write to a synced realm from an App Extension,
there are a few recommended alternatives:
- **Offline-first**: pass data on disk to or from the main app
- **Always up-to-date**: communicate directly with the backing
Atlas collection across a network connection
Pass Data On Disk
`````````````````
If offline-first functionality is the most important consideration for your app,
you can pass data on disk to or from your main app. You could copy objects
to a non-synced realm and read and share it between apps in an App Group.
Or you could use an on-disk queue to send the data to or from the main app and
only write to the synced realm from there. Then, regardless of the device's
network connectivity, information can be shared any time to or from the App
Extension.
Communicate Directly with the Backing Atlas Collection
``````````````````````````````````````````````````````
If having the information always up-to-date across all devices is the most
important consideration for your app, you can read or write data directly
to or from the backing Atlas collection across the network. Depending on your
needs, you may want to use one of these tools to communicate directly with
Atlas:
- Query Atlas with the :ref:`Realm Swift SDK MongoClient <ios-mongodb-data-access>`
- Pass data to an :ref:`App Services Function <ios-call-a-function>`
- Make HTTPS calls with the :ref:`Data API <data-api>` or :ref:`GraphQL API <graphql-api>`
Then, any device that has a network connection is always getting the most
up-to-date information, without waiting for the user to open your main app
as in the option above.
This option does require your user's device to have a network connection
when using the App Extension. As a fallback, you could check for a network
connection. Then, use the on-disk option above in the event that the user's
device lacks network connectivity.