diff --git a/examples/ios/RealmExamples.xcodeproj/project.pbxproj b/examples/ios/RealmExamples.xcodeproj/project.pbxproj index 4605463f25..4dd5cc12e5 100644 --- a/examples/ios/RealmExamples.xcodeproj/project.pbxproj +++ b/examples/ios/RealmExamples.xcodeproj/project.pbxproj @@ -102,6 +102,10 @@ 917E73E726B8A9F80068242A /* MongoDBRemoteAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917E73E526B8A9F80068242A /* MongoDBRemoteAccess.swift */; }; 917E73E826B8A9F80068242A /* MongoDBRemoteAccessAggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917E73E626B8A9F80068242A /* MongoDBRemoteAccessAggregate.swift */; }; 9185DB1925E82C0300AF1389 /* LocalOnlyCompleteQuickStart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9185DB1825E82C0300AF1389 /* LocalOnlyCompleteQuickStart.swift */; }; + 918F1FD62BF7C32E00F43489 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD52BF7C32E00F43489 /* RealmSwift */; }; + 918F1FD82BF7C32E00F43489 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD52BF7C32E00F43489 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 918F1FDA2BF7C3D100F43489 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD92BF7C3D100F43489 /* RealmSwift */; }; + 918F1FDC2BF7C3D100F43489 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 918F1FD92BF7C3D100F43489 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 91AB031428981BF700A272E8 /* DeleteRealmFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB031328981BF700A272E8 /* DeleteRealmFiles.swift */; }; 91AB0316289965F800A272E8 /* CreateRealmObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB0315289965F800A272E8 /* CreateRealmObjects.swift */; }; 91AB03182899660A00A272E8 /* ReadRealmObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB03172899660A00A272E8 /* ReadRealmObjects.swift */; }; @@ -110,6 +114,7 @@ 91AB031E289B1E9700A272E8 /* AddSyncToApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AB031D289B1E9700A272E8 /* AddSyncToApp.swift */; }; 91AE237728AFC7A40027C3AC /* SyncOrLocalRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */; }; 91AE237828AFC7A40027C3AC /* SyncOrLocalRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */; }; + 91B4EBA32982CC4F0015FD7B /* HandleSyncErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */; }; 91B8A4D6278E24950018F72F /* TypeProjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B8A4D5278E24950018F72F /* TypeProjection.swift */; }; 91CEF5FE260E308100A029E0 /* Compacting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CEF5FD260E308100A029E0 /* Compacting.swift */; }; 91CEF606260E325000A029E0 /* Compacting.m in Sources */ = {isa = PBXBuildFile; fileRef = 91CEF605260E325000A029E0 /* Compacting.m */; }; @@ -139,6 +144,28 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 918F1FD72BF7C32E00F43489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 918F1FD82BF7C32E00F43489 /* RealmSwift in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 918F1FDB2BF7C3D100F43489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 918F1FDC2BF7C3D100F43489 /* RealmSwift in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 91F04E9B2BE290A80082EB2B /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -248,6 +275,7 @@ 91AB031B2899663600A272E8 /* DeleteRealmObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteRealmObjects.swift; sourceTree = ""; }; 91AB031D289B1E9700A272E8 /* AddSyncToApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSyncToApp.swift; sourceTree = ""; }; 91AE237628AFC7A40027C3AC /* SyncOrLocalRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncOrLocalRealm.swift; sourceTree = ""; }; + 91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleSyncErrors.swift; sourceTree = ""; }; 91B8A4D5278E24950018F72F /* TypeProjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeProjection.swift; sourceTree = ""; }; 91C68804274D8AFE001A5DBE /* SwiftUIExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIExamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; 91CEF5FD260E308100A029E0 /* Compacting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compacting.swift; sourceTree = ""; }; @@ -291,6 +319,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 918F1FD62BF7C32E00F43489 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -298,6 +327,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 918F1FDA2BF7C3D100F43489 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -501,6 +531,7 @@ 91713B3028AC41B700519F9D /* Authenticate.swift */, 915B8EE429258B4300150F01 /* CreateObjects.swift */, 916E7D4528B57B6B00758A85 /* FilterData.swift */, + 91B4EBA22982CC4F0015FD7B /* HandleSyncErrors.swift */, 91713B2428AC3DF300519F9D /* OpenRealm.swift */, 91713B3428AD2B0E00519F9D /* PassObjectsToView.swift */, 916E7D4228B5693100758A85 /* Previews.swift */, @@ -594,6 +625,7 @@ 91713AF528AC3D8200519F9D /* Sources */, 91713AF628AC3D8200519F9D /* Frameworks */, 91713AF728AC3D8200519F9D /* Resources */, + 918F1FD72BF7C32E00F43489 /* Embed Frameworks */, ); buildRules = ( ); @@ -601,6 +633,7 @@ ); name = SwiftUICatalog; packageProductDependencies = ( + 918F1FD52BF7C32E00F43489 /* RealmSwift */, ); productName = SwiftUICatalog; productReference = 91713AF928AC3D8200519F9D /* SwiftUICatalog.app */; @@ -613,6 +646,7 @@ 91713B0E28AC3D8300519F9D /* Sources */, 91713B0F28AC3D8300519F9D /* Frameworks */, 91713B1028AC3D8300519F9D /* Resources */, + 918F1FDB2BF7C3D100F43489 /* Embed Frameworks */, ); buildRules = ( ); @@ -621,6 +655,7 @@ ); name = SwiftUICatalogUITests; packageProductDependencies = ( + 918F1FD92BF7C3D100F43489 /* RealmSwift */, ); productName = SwiftUICatalogUITests; productReference = 91713B1228AC3D8300519F9D /* SwiftUICatalogUITests.xctest */; @@ -888,6 +923,7 @@ 91713AFC28AC3D8200519F9D /* SwiftUICatalogApp.swift in Sources */, 916E7D4928B5863A00758A85 /* QuickWrite.swift in Sources */, 91713B3528AD2B0E00519F9D /* PassObjectsToView.swift in Sources */, + 91B4EBA32982CC4F0015FD7B /* HandleSyncErrors.swift in Sources */, 91713B2528AC3DF300519F9D /* OpenRealm.swift in Sources */, 916E7D4328B5693100758A85 /* Previews.swift in Sources */, 915B8EE529258B4300150F01 /* CreateObjects.swift in Sources */, @@ -1464,7 +1500,7 @@ repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { kind = exactVersion; - version = 10.50.0; + version = 10.49.3; }; }; 917CA79427ECADC200F9BDDC /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { @@ -1508,6 +1544,16 @@ package = 917CA79427ECADC200F9BDDC /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; productName = FacebookLogin; }; + 918F1FD52BF7C32E00F43489 /* RealmSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 91508B752BE57EFA00817DBC /* XCRemoteSwiftPackageReference "realm-swift" */; + productName = RealmSwift; + }; + 918F1FD92BF7C3D100F43489 /* RealmSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 91508B752BE57EFA00817DBC /* XCRemoteSwiftPackageReference "realm-swift" */; + productName = RealmSwift; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 48DBFACE25101C3100391E2B /* Project object */; diff --git a/examples/ios/SwiftUICatalog/Views/Authenticate.swift b/examples/ios/SwiftUICatalog/Views/Authenticate.swift index e562566ec7..938489d272 100644 --- a/examples/ios/SwiftUICatalog/Views/Authenticate.swift +++ b/examples/ios/SwiftUICatalog/Views/Authenticate.swift @@ -55,6 +55,62 @@ struct FlexibleSyncContentView: View { } // :snippet-end: +// :snippet-start: flexible-sync-view-with-client-reset-handling +struct FlexSyncContentView: View { + // Observe the Realm App object in order to react to login state changes. + @ObservedObject var flexibleSyncApp: RealmSwift.App + // Use the error handler that you've injected into the environment + // to react to Device Sync errors. + // :uncomment-start: + //@EnvironmentObject var errorHandler: ErrorHandler + // :uncomment-end: + + var body: some View { + if let user = flexibleSyncApp.currentUser { + let config = user.flexibleSyncConfiguration( + // :emphasize-start: + clientResetMode: .recoverUnsyncedChanges( + beforeReset: { realm in + // A block called after a client reset error is detected, but before the + // client recovery process is executed. + // This block could be used for any custom logic, reporting, debugging etc. + // For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/ + print("Before client reset block") + }, afterReset: { before,after in + // A block called after the client recovery process has executed. + // This block could be used for custom recovery, reporting, debugging etc. + // For SwiftUI, you might modify a @State variable to trigger views to reload + // or advise the user to restart the app. + // For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/ + print("After client reset block") + }), + // :emphasize-end: + initialSubscriptions: { subs in + let peopleSubscriptionExists = subs.first(named: "people") + let dogSubscriptionExists = subs.first(named: "dogs") + // Check whether the subscription already exists. Adding it more + // than once causes an error. + if (peopleSubscriptionExists != nil) && (dogSubscriptionExists != nil) { + // Existing subscriptions found - do nothing + return + } else { + // Add queries for any objects you want to use in the app. + // Linked objects do not automatically get queried, so you + // must explicitly query for all linked objects you want to include. + subs.append(QuerySubscription(name: "people")) + subs.append(QuerySubscription(name: "dogs")) + } + } + ) + OpenFlexibleSyncRealmView() + .environment(\.realmConfiguration, config) + } else { + FlexibleSyncLoginView() + } + } +} +// :snippet-end: + struct PBSContentView: View { // Observe the Realm app object in order to react to login state changes. @ObservedObject var partitionBasedSyncApp: RealmSwift.App diff --git a/examples/ios/SwiftUICatalog/Views/HandleSyncErrors.swift b/examples/ios/SwiftUICatalog/Views/HandleSyncErrors.swift new file mode 100644 index 0000000000..5709346cc9 --- /dev/null +++ b/examples/ios/SwiftUICatalog/Views/HandleSyncErrors.swift @@ -0,0 +1,72 @@ +import SwiftUI +import RealmSwift +import Foundation + +// :snippet-start: swiftui-app-with-error-handler +let app = App(id: flexibleSyncAppId) + +// :uncomment-start: +// @main +// :uncomment-end: +struct realmSwiftUIApp: SwiftUI.App { + // Initialize the error handler + @StateObject var errorHandler = ErrorHandler(app: app) + + var body: some Scene { + WindowGroup { + NextView(app: app) + // Inject the error handler as an environment object + .environmentObject(errorHandler) + // Display an alert to the user containing the error when a Sync error occurs + .alert(Text("Error"), isPresented: .constant(errorHandler.error != nil)) { + Button("OK", role: .cancel) { errorHandler.error = nil } + } message: { + Text(errorHandler.error?.localizedDescription ?? "") + } + } + } +} +// :snippet-end: + +// :snippet-start: swiftui-error-handler +final class ErrorHandler: ObservableObject { + @Published var error: Swift.Error? + + init(app: RealmSwift.App) { + // Sync Manager listens for sync errors. + app.syncManager.errorHandler = { error, syncSession in + if let error = error as? SyncError { + /* Handle specific SyncError cases, or use a switch + * statement to handle all Sync error codes. + * In this case, ignore a .connectionFailed error and + * continue executing the app code. */ + if error.code == .connectionFailed { + return + } + self.error = error + } else if let error = error as? POSIXError { + /* The error handler may also report NSError types to + * allow for error handling in a platform-idiomatic way. + * In this case, handle a connection timeout error as + * an .ETIMEDOUT error in the POSIXError domain. */ + if error.code == .ETIMEDOUT { + return + } + self.error = error + } + } + } +} +// :snippet-end: + +// :snippet-start: use-app-and-error-handler-in-next-view +struct NextView: View { + @ObservedObject var app: RealmSwift.App + // Use the error handler that you injected into the environment + @EnvironmentObject var errorHandler: ErrorHandler + + var body: some View { + Text("You might log users in or handle errors in this view") + } +} +// :snippet-end: diff --git a/source/examples/generated/swiftui/Authenticate.snippet.flexible-sync-view-with-client-reset-handling.swift.rst b/source/examples/generated/swiftui/Authenticate.snippet.flexible-sync-view-with-client-reset-handling.swift.rst new file mode 100644 index 0000000000..bed4f0da17 --- /dev/null +++ b/source/examples/generated/swiftui/Authenticate.snippet.flexible-sync-view-with-client-reset-handling.swift.rst @@ -0,0 +1,52 @@ +.. code-block:: swift + :emphasize-lines: 11-25 + + struct FlexSyncContentView: View { + // Observe the Realm App object in order to react to login state changes. + @ObservedObject var app: RealmSwift.App + // Use the error handler that you've injected into the environment + // to react to Device Sync errors. + @EnvironmentObject var errorHandler: ErrorHandler + + var body: some View { + if let user = app.currentUser { + let config = user.flexibleSyncConfiguration( + clientResetMode: .recoverUnsyncedChanges( + beforeReset: { realm in + // A block called after a client reset error is detected, but before the + // client recovery process is executed. + // This block could be used for any custom logic, reporting, debugging etc. + // For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/ + print("Before client reset block") + }, afterReset: { before,after in + // A block called after the client recovery process has executed. + // This block could be used for custom recovery, reporting, debugging etc. + // For SwiftUI, you might modify a @State variable to trigger views to reload + // or advise the user to restart the app. + // For more information, refer to: https://www.mongodb.com/docs/realm/sdk/swift/sync/handle-sync-errors/ + print("After client reset block") + }), + initialSubscriptions: { subs in + let peopleSubscriptionExists = subs.first(named: "people") + let dogSubscriptionExists = subs.first(named: "dogs") + // Check whether the subscription already exists. Adding it more + // than once causes an error. + if (peopleSubscriptionExists != nil) && (dogSubscriptionExists != nil) { + // Existing subscriptions found - do nothing + return + } else { + // Add queries for any objects you want to use in the app. + // Linked objects do not automatically get queried, so you + // must explicitly query for all linked objects you want to include. + subs.append(QuerySubscription(name: "people")) + subs.append(QuerySubscription(name: "dogs")) + } + } + ) + OpenFlexibleSyncRealmView() + .environment(\.realmConfiguration, config) + } else { + LoginView() + } + } + } diff --git a/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-app-with-error-handler.swift b/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-app-with-error-handler.swift new file mode 100644 index 0000000000..37943e6578 --- /dev/null +++ b/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-app-with-error-handler.swift @@ -0,0 +1,21 @@ +let app = App(id: flexibleSyncAppId) + +@main +struct realmSwiftUIApp: SwiftUI.App { + // Initialize the error handler + @StateObject var errorHandler = ErrorHandler(app: app) + + var body: some Scene { + WindowGroup { + NextView(app: app) + // Inject the error handler as an environment object + .environmentObject(errorHandler) + // Display an alert to the user containing the error when a Sync error occurs + .alert(Text("Error"), isPresented: .constant(errorHandler.error != nil)) { + Button("OK", role: .cancel) { errorHandler.error = nil } + } message: { + Text(errorHandler.error?.localizedDescription ?? "") + } + } + } +} diff --git a/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-error-handler.swift b/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-error-handler.swift new file mode 100644 index 0000000000..230d05f1c2 --- /dev/null +++ b/source/examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-error-handler.swift @@ -0,0 +1,28 @@ +final class ErrorHandler: ObservableObject { + @Published var error: Swift.Error? + + init(app: RealmSwift.App) { + // Sync Manager listens for sync errors. + app.syncManager.errorHandler = { error, syncSession in + if let error = error as? SyncError { + /* Handle specific SyncError cases, or use a switch + * statement to handle all Sync error codes. + * In this case, ignore a .connectionFailed error and + * continue executing the app code. */ + if error.code == .connectionFailed { + return + } + self.error = error + } else if let error = error as? POSIXError { + /* The error handler may also report NSError types to + * allow for error handling in a platform-idiomatic way. + * In this case, handle a connection timeout error as + * an .ETIMEDOUT error in the POSIXError domain. */ + if error.code == .ETIMEDOUT { + return + } + self.error = error + } + } + } +} diff --git a/source/examples/generated/swiftui/HandleSyncErrors.snippet.use-app-and-error-handler-in-next-view.swift b/source/examples/generated/swiftui/HandleSyncErrors.snippet.use-app-and-error-handler-in-next-view.swift new file mode 100644 index 0000000000..016db35067 --- /dev/null +++ b/source/examples/generated/swiftui/HandleSyncErrors.snippet.use-app-and-error-handler-in-next-view.swift @@ -0,0 +1,9 @@ +struct NextView: View { + @ObservedObject var app: RealmSwift.App + // Use the error handler that you injected into the environment + @EnvironmentObject var errorHandler: ErrorHandler + + var body: some View { + Text("You might log users in or handle errors in this view") + } +} diff --git a/source/includes/swiftui-handle-client-reset-error.rst b/source/includes/swiftui-handle-client-reset-error.rst new file mode 100644 index 0000000000..9e8eda51ac --- /dev/null +++ b/source/includes/swiftui-handle-client-reset-error.rst @@ -0,0 +1,89 @@ +Handle Client Reset Errors +-------------------------- + +A client reset error is a specific type of Sync error that occurs when the +realm on the user's device can no longer sync with the App Services App. +For more information about the underlying error and recovery options, +refer to: :ref:`ios-client-reset`. + +Realm's SwiftUI property wrappers ``@AsyncOpen`` and ``@AutoOpen`` enable +you to handle a client reset error by adding a few things to your app: + +- Add client reset handling information to the :swift-sdk:`flexibleSyncConfiguration() + ` +- Force an ``@AsyncOpen`` or ``@AutoOpen`` view to reload after a client reset is complete + +Add Client Reset Handling Information to the Flexible Sync Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Swift SDK can handle a client reset with different behaviors depending +on the needs of your app. For a full list of client reset modes and behaviors, +refer to :ref:`ios-client-resets`. + +To automatically handle a client reset, set the ``clientResetMode`` parameter +in your :swift-sdk:`flexibleSyncConfiguration() +`. + +You can review the possible values and their behaviors in the +:swift-sdk:`ClientResetMode enum `. + +In this example, we set the ``clientResetMode`` to ``.recoverUnsyncedChanges``. +.. include:: /examples/generated/swiftui/Authenticate.snippet.flexible-sync-view-with-client-reset-handling.swift.rst + +Take Some Action after the Client Reset Completes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the Sync error handler detects that a client reset must occur, the SDK +executes the client reset using the behavior you specify in the +``flexibleSyncConfiguration``'s ``clientResetMode`` parameter. However, the +SwiftUI ``@AsyncOpen`` and ``@AutoOpen`` property wrappers do not detect +that a client reset has occurred. An app that :ref:`opens a synced realm by +switching on @AsyncOpen or @AutoOpen property wrapper states +` displays a progress spinner indefinitely. +It never progresses past the ``.connecting`` state. The next time the user +opens the app, it loads without issue. + +You can perform additional logic in the ``afterClientReset`` block to handle +this in your app. You could set a ``@State`` variable, modify its value +in the ``afterClientResetBlock``, and use that to instruct the user to +re-open the app when a client reset occurs. Or you could force the +``@AsyncOpen`` or ``@AutoOpen`` view to reload using the updated realm. + +Verify Client Reset Works as Expected +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To test that your client reset implementation is working, you trigger +a client reset by terminating and re-enabling Device Sync. + +.. warning:: + + Test client reset handling in a development app that does not have + external users. Triggering a client reset using this method causes a + client reset for *all* app users. + +.. procedure:: + + .. step:: Run the App and Add Data + + Open your app on the device. Add some data to your app. Then, stop your + app. + + .. step:: Trigger a Client Reset + + Follow the instructions in the Device Sync documentation to + :ref:`terminating-realm-sync`. You should see a + ``TranslatorFatalError Error`` message in the App Services logs, + indicating that Sync has terminated. Then, :ref:`re-enable-realm-sync`. + + .. step:: Run and Test the App + + When you run the app on your device again, it should trigger a + client reset error. Verify that you see any relevant log messages + that you added to your code while implementing the client reset logic, + and that the device resets the realm and successfully loads fresh data + from the backend. + + .. step:: Iterate as Needed + + Repeat until you are satisfied with the client reset implementation in your + SwiftUI app. diff --git a/source/sdk/swift/swiftui.txt b/source/sdk/swift/swiftui.txt index 7c9937a0fb..f18e803d9a 100644 --- a/source/sdk/swift/swiftui.txt +++ b/source/sdk/swift/swiftui.txt @@ -20,6 +20,7 @@ SwiftUI - Swift SDK Pass Realm Data Between Views Write Data Filter Data + Handle Sync Errors Sync Data in the Background Use Realm with SwiftUI Previews @@ -36,6 +37,7 @@ This documentation provides an overview of those features. - :doc:`Pass Realm Data Between Views ` - :doc:`Write Data ` - :doc:`Filter Data ` +- :doc:`Handle Sync Errors ` - :doc:`Sync Data in the Background ` - :doc:`Use Realm with SwiftUI Previews ` diff --git a/source/sdk/swift/swiftui/handle-sync-errors.txt b/source/sdk/swift/swiftui/handle-sync-errors.txt new file mode 100644 index 0000000000..7a6707dffd --- /dev/null +++ b/source/sdk/swift/swiftui/handle-sync-errors.txt @@ -0,0 +1,61 @@ +.. _swiftui-handle-sync-errors: + +=========================================== +Handle Sync Errors with SwiftUI - Swift SDK +=========================================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. _swiftui-register-sync-error-handler: + +Handle Sync Errors +------------------ + +While developing an application that uses Device Sync, you should set an error +handler. This error handler will detect and respond to any failed sync-related +API calls. + +.. seealso:: + + For a complete example app with a working Sync error handler implementation, + :ref:`create a template app ` and check out + :ref:`the SwiftUI client `. The error handler + implementation is in the :file:`App.swift` file. + +For a SwiftUI-friendly implementation of a Sync error handler, create +an ``ObservableObject`` with an optional ``@Published`` variable to contain +a potential error. This handler uses the :swift-sdk:`SyncManager +` to listen for errors. +The ``SyncManager`` reports errors of the type ``SyncError``, and it also +reports other connection issues. + +For more information, refer to the underlying Objective-C :objc-sdk:`RLMSyncError +`. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-error-handler.swift + :language: swift + +.. include:: /includes/sync-errors-in-app-services.rst + +Initialize the error handler as a ``@StateObject``. Inject it into the +view hierarchy as an environment object. In this example, we display an +``.alert`` to the user when a Sync error occurs. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-app-with-error-handler.swift + :language: swift + +Then, in the view where you are observing the Realm ``App``, you can use the +error handler as an ``@EnvironmentObject`` to react to Sync errors. An +error that occurs here pops up an alert for the user, using the ``.alert`` +set in the view above. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.use-app-and-error-handler-in-next-view.swift + :language: swift + +.. TODO: Re-test and add the section in the following file: +.. `includes/swiftui-handle-client-reset-error.rst` after realm-swift merges +.. this PR: https://github.com/realm/realm-swift/pull/8109