From d023606313d7d7d89f8d7088e9f0a2fcc19059e1 Mon Sep 17 00:00:00 2001 From: Dachary Carey Date: Mon, 30 Oct 2023 11:16:02 -0400 Subject: [PATCH 1/3] Add SyncSession.reconnect() for Swift --- examples/ios/Examples/Sync.swift | 87 +++++++++++-------- .../RealmExamples.xcodeproj/project.pbxproj | 2 +- .../Sync.snippet.open-synced-realm.swift | 17 ++-- ...nc.snippet.specify-download-behavior.swift | 14 +++ .../Sync.snippet.sync-session-reconnect.swift | 4 + source/sdk/swift/sync/sync-session.txt | 32 +++++++ 6 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift create mode 100644 source/examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift diff --git a/examples/ios/Examples/Sync.swift b/examples/ios/Examples/Sync.swift index bb8dcc1d95..b41680faf4 100644 --- a/examples/ios/Examples/Sync.swift +++ b/examples/ios/Examples/Sync.swift @@ -26,8 +26,8 @@ class Sync: AnonymouslyLoggedInTestCase { if app.currentUser != nil { return app.currentUser! } else { - // Instantiate the app using your Realm app ID - let app = App(id: YOUR_APP_SERVICES_APP_ID) + // Instantiate the app using your App Services App ID + let app = App(id: APPID) // Authenticate with the instance of the app that points // to your backend. Here, we're using anonymous login. let loggedInUser = try await app.login(credentials: Credentials.anonymous) @@ -40,16 +40,12 @@ class Sync: AnonymouslyLoggedInTestCase { func getRealm() async throws -> Realm { // Get a logged-in app user let user = try await getUser() - // Specify which data this authenticated user should - // be able to access. - let partitionValue = "some partition value" // Store a configuration that consists of the current user, - // authenticated to this instance of your app, who should be - // able to access this data (partition). - var configuration = user.configuration(partitionValue: partitionValue) - // :remove-start: - configuration.objectTypes = [SyncExamples_Task.self] - // :remove-end: + // authenticated to this instance of your app, + // and what object types this database should manage. + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + // Open a Realm with this configuration. let realm = try await Realm(configuration: configuration) print("Successfully opened realm: \(realm)") @@ -58,44 +54,43 @@ class Sync: AnonymouslyLoggedInTestCase { // Get a realm let realm = try await getRealm() - // Do something with the realm print("The open realm is: \(realm)") + // Add subscriptions and work with the realm // :snippet-end: expectation.fulfill() - wait(for: [expectation], timeout: 10) + await fulfillment(of: [expectation], timeout: 10, enforceOrder: true) } - // :snippet-start: specify-download-behavior func testSpecifyDownloadBehavior() async throws { // :remove-start: let expectation = XCTestExpectation(description: "it completes") // :remove-end: - let app = App(id: YOUR_APP_SERVICES_APP_ID) - let user = try await app.login(credentials: Credentials.anonymous) - let partitionValue = "some partition value" - var configuration = user.configuration(partitionValue: partitionValue) - // :remove-start: - configuration.objectTypes = [SyncExamples_Task.self] - // :remove-end: - let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) // :emphasize: - print("Successfully opened realm after downloading: \(realm)") - // :remove-start: + // :snippet-start: specify-download-behavior + func getRealmAfterDownloadingUpdates() async throws -> Realm { + let app = App(id: APPID) + let user = try await app.login(credentials: Credentials.anonymous) + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + + let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) // :emphasize: + print("Successfully opened realm after downloading: \(realm)") + return realm + } + + let realm = try await getRealmAfterDownloadingUpdates() + print("The open realm is: \(realm)") + // Add subscription and work with the realm + // :snippet-end: expectation.fulfill() - wait(for: [expectation], timeout: 10) - // :remove-end: + await fulfillment(of: [expectation], timeout: 10, enforceOrder: true) } - // :snippet-end: - func testPauseResumeSyncSession() { - let app = App(id: YOUR_APP_SERVICES_APP_ID) - // Log in... - let user = app.currentUser - let partitionValue = "some partition value" - var configuration = user!.configuration(partitionValue: partitionValue) - // :remove-start: - configuration.objectTypes = [SyncExamples_Task.self] - // :remove-end: - let syncedRealm = try! Realm(configuration: configuration) + func testPauseResumeSyncSession() async throws { + let app = App(id: APPID) + let user = try await app.login(credentials: Credentials.anonymous) + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + let syncedRealm = try! await Realm(configuration: configuration) // :snippet-start: pause-resume-sync-session let syncSession = syncedRealm.syncSession! @@ -138,6 +133,8 @@ class Sync: AnonymouslyLoggedInTestCase { // :snippet-end: } + // Leaving this example using Partition-Based Sync + // since progress notifications are currently only supported in PBS func testCheckProgress() { let app = App(id: YOUR_APP_SERVICES_APP_ID) let user = app.currentUser @@ -223,5 +220,21 @@ class Sync: AnonymouslyLoggedInTestCase { } // :snippet-end: } + + func testSyncSessionReconnect() async throws { + let app = App(id: YOUR_APP_SERVICES_APP_ID) + let user = try await app.login(credentials: Credentials.anonymous) + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + let realm = try! await Realm(configuration: configuration) + + // :snippet-start: sync-session-reconnect + let syncSession = realm.syncSession! + + // Work with the realm. When you need to force the sync session to reconnect... + syncSession.reconnect() + // :snippet-end: + + } } // :replace-end: diff --git a/examples/ios/RealmExamples.xcodeproj/project.pbxproj b/examples/ios/RealmExamples.xcodeproj/project.pbxproj index d79ec99354..9db65a97d4 100644 --- a/examples/ios/RealmExamples.xcodeproj/project.pbxproj +++ b/examples/ios/RealmExamples.xcodeproj/project.pbxproj @@ -1471,7 +1471,7 @@ repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { kind = exactVersion; - version = 10.43.0; + version = 10.44.0; }; }; 917CA79427ECADC200F9BDDC /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { diff --git a/source/examples/generated/code/start/Sync.snippet.open-synced-realm.swift b/source/examples/generated/code/start/Sync.snippet.open-synced-realm.swift index 8217521b34..f075227ad3 100644 --- a/source/examples/generated/code/start/Sync.snippet.open-synced-realm.swift +++ b/source/examples/generated/code/start/Sync.snippet.open-synced-realm.swift @@ -5,8 +5,8 @@ func getUser() async throws -> User { if app.currentUser != nil { return app.currentUser! } else { - // Instantiate the app using your Realm app ID - let app = App(id: YOUR_APP_SERVICES_APP_ID) + // Instantiate the app using your App Services App ID + let app = App(id: APPID) // Authenticate with the instance of the app that points // to your backend. Here, we're using anonymous login. let loggedInUser = try await app.login(credentials: Credentials.anonymous) @@ -19,13 +19,12 @@ func getUser() async throws -> User { func getRealm() async throws -> Realm { // Get a logged-in app user let user = try await getUser() - // Specify which data this authenticated user should - // be able to access. - let partitionValue = "some partition value" // Store a configuration that consists of the current user, - // authenticated to this instance of your app, who should be - // able to access this data (partition). - var configuration = user.configuration(partitionValue: partitionValue) + // authenticated to this instance of your app, + // and what object types this database should manage. + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + // Open a Realm with this configuration. let realm = try await Realm(configuration: configuration) print("Successfully opened realm: \(realm)") @@ -34,5 +33,5 @@ func getRealm() async throws -> Realm { // Get a realm let realm = try await getRealm() -// Do something with the realm print("The open realm is: \(realm)") +// Add subscriptions and work with the realm diff --git a/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift b/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift new file mode 100644 index 0000000000..22f7568bdb --- /dev/null +++ b/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift @@ -0,0 +1,14 @@ +func getRealmAfterDownloadingUpdates() async throws -> Realm { + let app = App(id: APPID) + let user = try await app.login(credentials: Credentials.anonymous) + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + + let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) + print("Successfully opened realm after downloading: \(realm)") + return realm +} + +let realm = try await getRealmAfterDownloadingUpdates() +print("The open realm is: \(realm)") +// Add subscription and work with the realm diff --git a/source/examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift b/source/examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift new file mode 100644 index 0000000000..add227e96b --- /dev/null +++ b/source/examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift @@ -0,0 +1,4 @@ +let syncSession = realm.syncSession! + +// Work with the realm. When you need to force the sync session to reconnect... +syncSession.reconnect() diff --git a/source/sdk/swift/sync/sync-session.txt b/source/sdk/swift/sync/sync-session.txt index a16eeacc22..af5f01be21 100644 --- a/source/sdk/swift/sync/sync-session.txt +++ b/source/sdk/swift/sync/sync-session.txt @@ -160,3 +160,35 @@ Check Upload & Download Progress for a Sync Session .. literalinclude:: /examples/generated/code/start/Sync.snippet.check-progress.m :language: objectivec + +.. _ios-reconnect-sync-sessions: + +Manually Reconnect All Sync Sessions +------------------------------------ + +.. versionadded:: 10.44.0 + +Realm automatically detects when a device regains connectivity after being +offline and attempts to reconnect using an incremental backoff strategy. + +In Swift SDK version 10.44.0 and later, you can choose to manually trigger a +reconnect attempt with ``SyncSession.reconnect()`` +instead of waiting for the duration of the incremental backoff. This is +useful if you have a more accurate understanding of +the network conditions and don't want to rely on Realm's automatic +reconnect detection. The SDK also automatically calls this method when a device +toggles off airplane mode. + +.. literalinclude:: /examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift + :language: swift + +When you call this method, the SDK forces all sync sessions to attempt to +reconnect immediately and resets any timers used for incremental +backoff. + +.. important:: Cannot Reconnect Within Socket Read Timeout Duration + + Realm has an internal default socket read timeout of 2 minutes, where + Realm will time out if a read operation does not receive any data + within a 2-minute window. If you call ``SyncSession.reconnect()`` + within that window, the Swift SDK does *not* attempt to reconnect. From 42323525af077131701c246fb9fafb579924e2f2 Mon Sep 17 00:00:00 2001 From: Dachary Carey Date: Mon, 30 Oct 2023 11:43:40 -0400 Subject: [PATCH 2/3] Generate the snippet with emphasize/rst --- .../Sync.snippet.specify-download-behavior.swift | 14 -------------- ...c.snippet.specify-download-behavior.swift.rst | 16 +++++++++++----- 2 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift diff --git a/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift b/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift deleted file mode 100644 index 22f7568bdb..0000000000 --- a/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift +++ /dev/null @@ -1,14 +0,0 @@ -func getRealmAfterDownloadingUpdates() async throws -> Realm { - let app = App(id: APPID) - let user = try await app.login(credentials: Credentials.anonymous) - var configuration = user.flexibleSyncConfiguration() - configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] - - let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) - print("Successfully opened realm after downloading: \(realm)") - return realm -} - -let realm = try await getRealmAfterDownloadingUpdates() -print("The open realm is: \(realm)") -// Add subscription and work with the realm diff --git a/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift.rst b/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift.rst index ee2654b692..1b2dd1e1f2 100644 --- a/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift.rst +++ b/source/examples/generated/code/start/Sync.snippet.specify-download-behavior.swift.rst @@ -1,11 +1,17 @@ .. code-block:: swift - :emphasize-lines: 6 + :emphasize-lines: 7 - func testSpecifyDownloadBehavior() async throws { - let app = App(id: YOUR_REALM_APP_ID) + func getRealmAfterDownloadingUpdates() async throws -> Realm { + let app = App(id: APPID) let user = try await app.login(credentials: Credentials.anonymous) - let partitionValue = "some partition value" - var configuration = user.configuration(partitionValue: partitionValue) + var configuration = user.flexibleSyncConfiguration() + configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] + let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) print("Successfully opened realm after downloading: \(realm)") + return realm } + + let realm = try await getRealmAfterDownloadingUpdates() + print("The open realm is: \(realm)") + // Add subscription and work with the realm From 3e6814b081b18c993ec3f8049b2d17c7771ec948 Mon Sep 17 00:00:00 2001 From: Dachary Carey Date: Thu, 2 Nov 2023 16:40:20 -0400 Subject: [PATCH 3/3] Incorporate tech review feedback --- source/sdk/swift/sync/sync-session.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/source/sdk/swift/sync/sync-session.txt b/source/sdk/swift/sync/sync-session.txt index af5f01be21..7ca74315d8 100644 --- a/source/sdk/swift/sync/sync-session.txt +++ b/source/sdk/swift/sync/sync-session.txt @@ -170,21 +170,26 @@ Manually Reconnect All Sync Sessions Realm automatically detects when a device regains connectivity after being offline and attempts to reconnect using an incremental backoff strategy. +For example, on Apple platforms, Realm listens for network change notifications +and automatically triggers a reconnect immediately after receiving one. In Swift SDK version 10.44.0 and later, you can choose to manually trigger a reconnect attempt with ``SyncSession.reconnect()`` instead of waiting for the duration of the incremental backoff. This is useful if you have a more accurate understanding of the network conditions and don't want to rely on Realm's automatic -reconnect detection. The SDK also automatically calls this method when a device -toggles off airplane mode. +reconnect detection. .. literalinclude:: /examples/generated/code/start/Sync.snippet.sync-session-reconnect.swift :language: swift -When you call this method, the SDK forces all sync sessions to attempt to -reconnect immediately and resets any timers used for incremental -backoff. +When you call this method, the SDK forces all sync sessions to *attempt* to +reconnect immediately. This resets any timers used for incremental +backoff. + +Calling this method does not guarantee the device can reconnect. If the SDK +gets a fatal error, or if the device is already connected or is trying to +connect, calling this method has no effect. .. important:: Cannot Reconnect Within Socket Read Timeout Duration