Skip to content

Commit bfa23dd

Browse files
committed
(DOCSP-26313): Swift: Add a Handle Sync Errors page for SwiftUI (#2524)
Moved the client reset handling details off the page so we can merge this PR. When PR #8109 in the realm-swift repository gets released, we can re-test and add the client reset handling details to the page. - https://jira.mongodb.org/browse/DOCSP-26313 - [Handle Sync Errors](https://preview-mongodbdacharyc.gatsbyjs.io/realm/DOCSP-26313/sdk/swift/swiftui/handle-sync-errors/) If your PR modifies the docs, you might need to also update some corresponding pages. Check if completed or N/A. - [x] Create Jira ticket for corresponding docs-app-services update(s), if any - [x] Checked/updated Admin API - [x] Checked/updated CLI reference - Swift SDK - SwiftUI/Handle Sync Errors: New page demonstrating how to set and use a sync error handler in SwiftUI. [REVIEWING.md](https://github.com/mongodb/docs-realm/blob/master/REVIEWING.md)
1 parent 7cf0f51 commit bfa23dd

10 files changed

+437
-1
lines changed

examples/ios/RealmExamples.xcodeproj/project.pbxproj

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@
102102
917E73E726B8A9F80068242A /* MongoDBRemoteAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917E73E526B8A9F80068242A /* MongoDBRemoteAccess.swift */; };
103103
917E73E826B8A9F80068242A /* MongoDBRemoteAccessAggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917E73E626B8A9F80068242A /* MongoDBRemoteAccessAggregate.swift */; };
104104
9185DB1925E82C0300AF1389 /* LocalOnlyCompleteQuickStart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9185DB1825E82C0300AF1389 /* LocalOnlyCompleteQuickStart.swift */; };
105+
918F1FD62BF7C32E00F43489 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD52BF7C32E00F43489 /* RealmSwift */; };
106+
918F1FD82BF7C32E00F43489 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD52BF7C32E00F43489 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
107+
918F1FDA2BF7C3D100F43489 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD92BF7C3D100F43489 /* RealmSwift */; };
108+
918F1FDC2BF7C3D100F43489 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD92BF7C3D100F43489 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
105109
91AB031428981BF700A272E8 /* DeleteRealmFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB031328981BF700A272E8 /* DeleteRealmFiles.swift */; };
106110
91AB0316289965F800A272E8 /* CreateRealmObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB0315289965F800A272E8 /* CreateRealmObjects.swift */; };
107111
91AB03182899660A00A272E8 /* ReadRealmObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB03172899660A00A272E8 /* ReadRealmObjects.swift */; };
@@ -110,6 +114,7 @@
110114
91AB031E289B1E9700A272E8 /* AddSyncToApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB031D289B1E9700A272E8 /* AddSyncToApp.swift */; };
111115
91AE237728AFC7A40027C3AC /* SyncOrLocalRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */; };
112116
91AE237828AFC7A40027C3AC /* SyncOrLocalRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */; };
117+
91B4EBA32982CC4F0015FD7B /* HandleSyncErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */; };
113118
91B8A4D6278E24950018F72F /* TypeProjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B8A4D5278E24950018F72F /* TypeProjection.swift */; };
114119
91CEF5FE260E308100A029E0 /* Compacting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CEF5FD260E308100A029E0 /* Compacting.swift */; };
115120
91CEF606260E325000A029E0 /* Compacting.m in Sources */ = {isa = PBXBuildFile; fileRef = 91CEF605260E325000A029E0 /* Compacting.m */; };
@@ -139,6 +144,28 @@
139144
/* End PBXContainerItemProxy section */
140145

141146
/* Begin PBXCopyFilesBuildPhase section */
147+
918F1FD72BF7C32E00F43489 /* Embed Frameworks */ = {
148+
isa = PBXCopyFilesBuildPhase;
149+
buildActionMask = 2147483647;
150+
dstPath = "";
151+
dstSubfolderSpec = 10;
152+
files = (
153+
918F1FD82BF7C32E00F43489 /* RealmSwift in Embed Frameworks */,
154+
);
155+
name = "Embed Frameworks";
156+
runOnlyForDeploymentPostprocessing = 0;
157+
};
158+
918F1FDB2BF7C3D100F43489 /* Embed Frameworks */ = {
159+
isa = PBXCopyFilesBuildPhase;
160+
buildActionMask = 2147483647;
161+
dstPath = "";
162+
dstSubfolderSpec = 10;
163+
files = (
164+
918F1FDC2BF7C3D100F43489 /* RealmSwift in Embed Frameworks */,
165+
);
166+
name = "Embed Frameworks";
167+
runOnlyForDeploymentPostprocessing = 0;
168+
};
142169
91F04E9B2BE290A80082EB2B /* Embed Frameworks */ = {
143170
isa = PBXCopyFilesBuildPhase;
144171
buildActionMask = 2147483647;
@@ -248,6 +275,7 @@
248275
91AB031B2899663600A272E8 /* DeleteRealmObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteRealmObjects.swift; sourceTree = "<group>"; };
249276
91AB031D289B1E9700A272E8 /* AddSyncToApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSyncToApp.swift; sourceTree = "<group>"; };
250277
91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncOrLocalRealm.swift; sourceTree = "<group>"; };
278+
91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleSyncErrors.swift; sourceTree = "<group>"; };
251279
91B8A4D5278E24950018F72F /* TypeProjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeProjection.swift; sourceTree = "<group>"; };
252280
91C68804274D8AFE001A5DBE /* SwiftUIExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIExamples.app; sourceTree = BUILT_PRODUCTS_DIR; };
253281
91CEF5FD260E308100A029E0 /* Compacting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compacting.swift; sourceTree = "<group>"; };
@@ -291,13 +319,15 @@
291319
isa = PBXFrameworksBuildPhase;
292320
buildActionMask = 2147483647;
293321
files = (
322+
918F1FD62BF7C32E00F43489 /* RealmSwift in Frameworks */,
294323
);
295324
runOnlyForDeploymentPostprocessing = 0;
296325
};
297326
91713B0F28AC3D8300519F9D /* Frameworks */ = {
298327
isa = PBXFrameworksBuildPhase;
299328
buildActionMask = 2147483647;
300329
files = (
330+
918F1FDA2BF7C3D100F43489 /* RealmSwift in Frameworks */,
301331
);
302332
runOnlyForDeploymentPostprocessing = 0;
303333
};
@@ -501,6 +531,7 @@
501531
91713B3028AC41B700519F9D /* Authenticate.swift */,
502532
915B8EE429258B4300150F01 /* CreateObjects.swift */,
503533
916E7D4528B57B6B00758A85 /* FilterData.swift */,
534+
91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */,
504535
91713B2428AC3DF300519F9D /* OpenRealm.swift */,
505536
91713B3428AD2B0E00519F9D /* PassObjectsToView.swift */,
506537
916E7D4228B5693100758A85 /* Previews.swift */,
@@ -594,13 +625,15 @@
594625
91713AF528AC3D8200519F9D /* Sources */,
595626
91713AF628AC3D8200519F9D /* Frameworks */,
596627
91713AF728AC3D8200519F9D /* Resources */,
628+
918F1FD72BF7C32E00F43489 /* Embed Frameworks */,
597629
);
598630
buildRules = (
599631
);
600632
dependencies = (
601633
);
602634
name = SwiftUICatalog;
603635
packageProductDependencies = (
636+
918F1FD52BF7C32E00F43489 /* RealmSwift */,
604637
);
605638
productName = SwiftUICatalog;
606639
productReference = 91713AF928AC3D8200519F9D /* SwiftUICatalog.app */;
@@ -613,6 +646,7 @@
613646
91713B0E28AC3D8300519F9D /* Sources */,
614647
91713B0F28AC3D8300519F9D /* Frameworks */,
615648
91713B1028AC3D8300519F9D /* Resources */,
649+
918F1FDB2BF7C3D100F43489 /* Embed Frameworks */,
616650
);
617651
buildRules = (
618652
);
@@ -621,6 +655,7 @@
621655
);
622656
name = SwiftUICatalogUITests;
623657
packageProductDependencies = (
658+
918F1FD92BF7C3D100F43489 /* RealmSwift */,
624659
);
625660
productName = SwiftUICatalogUITests;
626661
productReference = 91713B1228AC3D8300519F9D /* SwiftUICatalogUITests.xctest */;
@@ -888,6 +923,7 @@
888923
91713AFC28AC3D8200519F9D /* SwiftUICatalogApp.swift in Sources */,
889924
916E7D4928B5863A00758A85 /* QuickWrite.swift in Sources */,
890925
91713B3528AD2B0E00519F9D /* PassObjectsToView.swift in Sources */,
926+
91B4EBA32982CC4F0015FD7B /* HandleSyncErrors.swift in Sources */,
891927
91713B2528AC3DF300519F9D /* OpenRealm.swift in Sources */,
892928
916E7D4328B5693100758A85 /* Previews.swift in Sources */,
893929
915B8EE529258B4300150F01 /* CreateObjects.swift in Sources */,
@@ -1464,7 +1500,7 @@
14641500
repositoryURL = "https://github.com/realm/realm-swift.git";
14651501
requirement = {
14661502
kind = exactVersion;
1467-
version = 10.50.0;
1503+
version = 10.49.3;
14681504
};
14691505
};
14701506
917CA79427ECADC200F9BDDC /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
@@ -1508,6 +1544,16 @@
15081544
package = 917CA79427ECADC200F9BDDC /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
15091545
productName = FacebookLogin;
15101546
};
1547+
918F1FD52BF7C32E00F43489 /* RealmSwift */ = {
1548+
isa = XCSwiftPackageProductDependency;
1549+
package = 91508B752BE57EFA00817DBC /* XCRemoteSwiftPackageReference "realm-swift" */;
1550+
productName = RealmSwift;
1551+
};
1552+
918F1FD92BF7C3D100F43489 /* RealmSwift */ = {
1553+
isa = XCSwiftPackageProductDependency;
1554+
package = 91508B752BE57EFA00817DBC /* XCRemoteSwiftPackageReference "realm-swift" */;
1555+
productName = RealmSwift;
1556+
};
15111557
/* End XCSwiftPackageProductDependency section */
15121558
};
15131559
rootObject = 48DBFACE25101C3100391E2B /* Project object */;

examples/ios/SwiftUICatalog/Views/Authenticate.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,62 @@ struct FlexibleSyncContentView: View {
5555
}
5656
// :snippet-end:
5757

58+
// :snippet-start: flexible-sync-view-with-client-reset-handling
59+
struct FlexSyncContentView: View {
60+
// Observe the Realm App object in order to react to login state changes.
61+
@ObservedObject var flexibleSyncApp: RealmSwift.App
62+
// Use the error handler that you've injected into the environment
63+
// to react to Device Sync errors.
64+
// :uncomment-start:
65+
//@EnvironmentObject var errorHandler: ErrorHandler
66+
// :uncomment-end:
67+
68+
var body: some View {
69+
if let user = flexibleSyncApp.currentUser {
70+
let config = user.flexibleSyncConfiguration(
71+
// :emphasize-start:
72+
clientResetMode: .recoverUnsyncedChanges(
73+
beforeReset: { realm in
74+
// A block called after a client reset error is detected, but before the
75+
// client recovery process is executed.
76+
// This block could be used for any custom logic, reporting, debugging etc.
77+
// For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/
78+
print("Before client reset block")
79+
}, afterReset: { before,after in
80+
// A block called after the client recovery process has executed.
81+
// This block could be used for custom recovery, reporting, debugging etc.
82+
// For SwiftUI, you might modify a @State variable to trigger views to reload
83+
// or advise the user to restart the app.
84+
// For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/
85+
print("After client reset block")
86+
}),
87+
// :emphasize-end:
88+
initialSubscriptions: { subs in
89+
let peopleSubscriptionExists = subs.first(named: "people")
90+
let dogSubscriptionExists = subs.first(named: "dogs")
91+
// Check whether the subscription already exists. Adding it more
92+
// than once causes an error.
93+
if (peopleSubscriptionExists != nil) && (dogSubscriptionExists != nil) {
94+
// Existing subscriptions found - do nothing
95+
return
96+
} else {
97+
// Add queries for any objects you want to use in the app.
98+
// Linked objects do not automatically get queried, so you
99+
// must explicitly query for all linked objects you want to include.
100+
subs.append(QuerySubscription<SwiftUI_Person>(name: "people"))
101+
subs.append(QuerySubscription<SwiftUI_Dog>(name: "dogs"))
102+
}
103+
}
104+
)
105+
OpenFlexibleSyncRealmView()
106+
.environment(\.realmConfiguration, config)
107+
} else {
108+
FlexibleSyncLoginView()
109+
}
110+
}
111+
}
112+
// :snippet-end:
113+
58114
struct PBSContentView: View {
59115
// Observe the Realm app object in order to react to login state changes.
60116
@ObservedObject var partitionBasedSyncApp: RealmSwift.App
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import SwiftUI
2+
import RealmSwift
3+
import Foundation
4+
5+
// :snippet-start: swiftui-app-with-error-handler
6+
let app = App(id: flexibleSyncAppId)
7+
8+
// :uncomment-start:
9+
// @main
10+
// :uncomment-end:
11+
struct realmSwiftUIApp: SwiftUI.App {
12+
// Initialize the error handler
13+
@StateObject var errorHandler = ErrorHandler(app: app)
14+
15+
var body: some Scene {
16+
WindowGroup {
17+
NextView(app: app)
18+
// Inject the error handler as an environment object
19+
.environmentObject(errorHandler)
20+
// Display an alert to the user containing the error when a Sync error occurs
21+
.alert(Text("Error"), isPresented: .constant(errorHandler.error != nil)) {
22+
Button("OK", role: .cancel) { errorHandler.error = nil }
23+
} message: {
24+
Text(errorHandler.error?.localizedDescription ?? "")
25+
}
26+
}
27+
}
28+
}
29+
// :snippet-end:
30+
31+
// :snippet-start: swiftui-error-handler
32+
final class ErrorHandler: ObservableObject {
33+
@Published var error: Swift.Error?
34+
35+
init(app: RealmSwift.App) {
36+
// Sync Manager listens for sync errors.
37+
app.syncManager.errorHandler = { error, syncSession in
38+
if let error = error as? SyncError {
39+
/* Handle specific SyncError cases, or use a switch
40+
* statement to handle all Sync error codes.
41+
* In this case, ignore a .connectionFailed error and
42+
* continue executing the app code. */
43+
if error.code == .connectionFailed {
44+
return
45+
}
46+
self.error = error
47+
} else if let error = error as? POSIXError {
48+
/* The error handler may also report NSError types to
49+
* allow for error handling in a platform-idiomatic way.
50+
* In this case, handle a connection timeout error as
51+
* an .ETIMEDOUT error in the POSIXError domain. */
52+
if error.code == .ETIMEDOUT {
53+
return
54+
}
55+
self.error = error
56+
}
57+
}
58+
}
59+
}
60+
// :snippet-end:
61+
62+
// :snippet-start: use-app-and-error-handler-in-next-view
63+
struct NextView: View {
64+
@ObservedObject var app: RealmSwift.App
65+
// Use the error handler that you injected into the environment
66+
@EnvironmentObject var errorHandler: ErrorHandler
67+
68+
var body: some View {
69+
Text("You might log users in or handle errors in this view")
70+
}
71+
}
72+
// :snippet-end:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.. code-block:: swift
2+
:emphasize-lines: 11-25
3+
4+
struct FlexSyncContentView: View {
5+
// Observe the Realm App object in order to react to login state changes.
6+
@ObservedObject var app: RealmSwift.App
7+
// Use the error handler that you've injected into the environment
8+
// to react to Device Sync errors.
9+
@EnvironmentObject var errorHandler: ErrorHandler
10+
11+
var body: some View {
12+
if let user = app.currentUser {
13+
let config = user.flexibleSyncConfiguration(
14+
clientResetMode: .recoverUnsyncedChanges(
15+
beforeReset: { realm in
16+
// A block called after a client reset error is detected, but before the
17+
// client recovery process is executed.
18+
// This block could be used for any custom logic, reporting, debugging etc.
19+
// For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/
20+
print("Before client reset block")
21+
}, afterReset: { before,after in
22+
// A block called after the client recovery process has executed.
23+
// This block could be used for custom recovery, reporting, debugging etc.
24+
// For SwiftUI, you might modify a @State variable to trigger views to reload
25+
// or advise the user to restart the app.
26+
// For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/
27+
print("After client reset block")
28+
}),
29+
initialSubscriptions: { subs in
30+
let peopleSubscriptionExists = subs.first(named: "people")
31+
let dogSubscriptionExists = subs.first(named: "dogs")
32+
// Check whether the subscription already exists. Adding it more
33+
// than once causes an error.
34+
if (peopleSubscriptionExists != nil) && (dogSubscriptionExists != nil) {
35+
// Existing subscriptions found - do nothing
36+
return
37+
} else {
38+
// Add queries for any objects you want to use in the app.
39+
// Linked objects do not automatically get queried, so you
40+
// must explicitly query for all linked objects you want to include.
41+
subs.append(QuerySubscription<Person>(name: "people"))
42+
subs.append(QuerySubscription<Dog>(name: "dogs"))
43+
}
44+
}
45+
)
46+
OpenFlexibleSyncRealmView()
47+
.environment(\.realmConfiguration, config)
48+
} else {
49+
LoginView()
50+
}
51+
}
52+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
let app = App(id: flexibleSyncAppId)
2+
3+
@main
4+
struct realmSwiftUIApp: SwiftUI.App {
5+
// Initialize the error handler
6+
@StateObject var errorHandler = ErrorHandler(app: app)
7+
8+
var body: some Scene {
9+
WindowGroup {
10+
NextView(app: app)
11+
// Inject the error handler as an environment object
12+
.environmentObject(errorHandler)
13+
// Display an alert to the user containing the error when a Sync error occurs
14+
.alert(Text("Error"), isPresented: .constant(errorHandler.error != nil)) {
15+
Button("OK", role: .cancel) { errorHandler.error = nil }
16+
} message: {
17+
Text(errorHandler.error?.localizedDescription ?? "")
18+
}
19+
}
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
final class ErrorHandler: ObservableObject {
2+
@Published var error: Swift.Error?
3+
4+
init(app: RealmSwift.App) {
5+
// Sync Manager listens for sync errors.
6+
app.syncManager.errorHandler = { error, syncSession in
7+
if let error = error as? SyncError {
8+
/* Handle specific SyncError cases, or use a switch
9+
* statement to handle all Sync error codes.
10+
* In this case, ignore a .connectionFailed error and
11+
* continue executing the app code. */
12+
if error.code == .connectionFailed {
13+
return
14+
}
15+
self.error = error
16+
} else if let error = error as? POSIXError {
17+
/* The error handler may also report NSError types to
18+
* allow for error handling in a platform-idiomatic way.
19+
* In this case, handle a connection timeout error as
20+
* an .ETIMEDOUT error in the POSIXError domain. */
21+
if error.code == .ETIMEDOUT {
22+
return
23+
}
24+
self.error = error
25+
}
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
struct NextView: View {
2+
@ObservedObject var app: RealmSwift.App
3+
// Use the error handler that you injected into the environment
4+
@EnvironmentObject var errorHandler: ErrorHandler
5+
6+
var body: some View {
7+
Text("You might log users in or handle errors in this view")
8+
}
9+
}

0 commit comments

Comments
 (0)