3

Basically, I want to handle a case where any device got SyncError with type ClientResetError then, want my device to re-login to realm again. but as per documentation, we have to closeRealmSafely before I login to realm again, but I am not sure how to close realm safely.

I am going through the doc (https://docs.realm.io/sync/using-synced-realms/errors#client-reset) to handle client reset error and found it's very confusing . I want help to understand about the following code.

  1. First there is no method available to closeRealmsafely. Please help me understand how can I close the realm safely?
  2. How can I backup and when I will use it? Should I skip the reset error because in documentation it's mentions if the client reset process is not manually initiated, it will instead automatically take place after the next time the app is launched, upon first accessing the SyncManager singleton. It is the app’s responsibility to persist the location of the backup copy if needed, so that the backup copy can be found later."

Below is the error handler sample code from the doc.

 let syncError = error as! SyncError
 switch syncError.code {
 case .clientResetError:
 if let (path, clientResetToken) = syncError.clientResetInfo() {
 closeRealmSafely()
 saveBackupRealmPath(path)
 SyncSession.immediatelyHandleError(clientResetToken)
 }
 default:
 // Handle other errors...
 ()
 }
}```
Uma_Shanker_Tiwari
  • 453
  • 1
  • 3
  • 16
  • Are we talking about a manual, controlled reset from code or one where the device shut down without warning kind of reset? – Jay Aug 13 '20 at 17:00
  • 1
    yes, I am talking about manual, controlled reset from code. Basically, I want to handle a case where any device got SyncError with type ClientResetError then, want my device to re-login to realm again. but as per documentation, we have to closeRealmSafely before I login to realm again, but I am not sure how to close realm safely. – Uma_Shanker_Tiwari Aug 14 '20 at 07:37
  • In that case, when you app is disconnected you must delete your local realm (files) and then the next time your app starts, the data from the server will re-download and sync. The important bit is that any references to realm objects will keep the realm alive and you won't be able to delete it. So best to ensure your app is not connected to realm before deleting those files. – Jay Aug 14 '20 at 18:19
  • Cross post to the [same question](https://developer.mongodb.com/community/forums/t/how-to-handle-client-reset-ios/7899/2) in the realm forums – Jay Aug 15 '20 at 13:36
  • thanks Jay. Deleting local realm may lead to data loss. How can avoid that? – Uma_Shanker_Tiwari Aug 17 '20 at 11:39
  • Why would it lead to data loss? If the data on the server is newer that's what's local and you are controlling when the reset occurs (per your above comment) then there would be no data loss. – Jay Aug 17 '20 at 17:13
  • 1
    Hi Jay, The data loss may happen in case user is offline, using the app and got reset error immediately after coming back online. If I delete the local realm and connect again in above case then data can't be synced for the offline period. Right now I am not controlling the reset and I want help to understand how I can do it as per realm documentation. – Uma_Shanker_Tiwari Aug 18 '20 at 14:15
  • Not sure I really follow. The process is outlined in the link provided in the question; noting the following **If the client reset process is manually initiated, all instances of the Realm in question must first be invalidated and destroyed** along with **The next time the app connects to the Realm Object Server and opens that Realm, a fresh copy will be downloaded. Changes that were made after the Realm Object Server was backed up but weren’t synced back to the server will be preserved in the backup copy** – Jay Aug 18 '20 at 17:10
  • 1
    Hi Jay, In order to handle a client reset the developer must have user code in the client reset callback that takes the old realm, compares it to the realm just downloaded from ROS, and inserts the additive objects to the new realm. How to insert additive objects to the new realm from the backup realm? is there any sample code that I can refer to do it properly? – Uma_Shanker_Tiwari Aug 20 '20 at 09:07

1 Answers1

4

Finally we figured out how to handle the client reset error. We have taken following steps To avoid the data loss incase user is offline and came online and got reset error.

  1. Save the local realm to another directory

  2. Invalidate and nil the realm

  3. Initiate realm manual reset - Call SyncSession.immediatelyHandleError with clientResetToken passed and it will delete the existing realm from directory

  4. Show client reset alert - This will intimate user to relaunch the app.

  5. On next launch realm creates a fresh realm from ROS.

  6. After new realm connects, restore the realm records (if any) from the old realm saved in backup directory above.

  7. Delete the backup realm(old realm) from directory.

     switch syncError.code {
     case .clientResetError:
     if let (path, clientResetToken) = syncError.clientResetInfo() {
     // taking backup
     backUpRealm(realm: yourLocalRealm)
     // making realm nil and invalidating
     yourLocalRealm?.invalidate()
     yourLocalRealm = nil
    //Initiate realm manual reset  - Call `SyncSession.immediatelyHandleError` with `clientResetToken` passed and it will delete the existing realm from directory
     SyncSession.immediatelyHandleError(clientResetToken)
     // can show alert to user to relaunch the app
     showAlertforAppRelaunch()
     }
     default:
     // Handle other errors...
     ()
     }
    }```
    
    

The back up realm code look like this:

func backUpRealm(realm: Realm?) {
        do {
            try realm?.writeCopy(toFile: backupUrl)
        } catch {
            print("Error backing up data")
        }
    }

After doing this backup will be available at backup path. On next launch device will connect and download a fresh realm from ROS so after device connects restore the realm records from the backup realm saved in the backup path.

The restore merge backup code will look like this. place the below method when realm connects after relauch.The ```restoredRealm`` is fresh downloaded realm on launch

func restoreAndMergeFromBackup(restoredRealm: Realm?) {
        let realmBackUpFilePath = isRealmBackupExits()
        // check if backup exists or not
        if realmBackUpFilePath.exists {
            let config = Realm.Configuration(
                fileURL: URL(fileURLWithPath: realmBackUpFilePath.path),
                readOnly: true)
            
            let realm = try? Realm(configuration: config)
            
            guard let backupRealm = realm else { return }
            
            //Get your realm Objects
            let objects = backupRealm.objects(YourRealmObject.self)
            
           try? restoredRealm?.safeWrite {
        
                for object in objects {
                // taking local changes to the downloaded realm if it has
                    restoredRealm?.create(YourRealmObject.self, value: object, update: .modified)
                }

                self.removeRealmFiles(path: realmBackUpFilePath.path)
            }
        } else {
            debug("backup realm does not exists")
        }
    }
    
    private func isRealmBackupExits() -> (exists: Bool, path: String) {
        let documentsPath = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
        
        let realmPathComponent =  documentsPath.appendingPathComponent("your_backup.realm")
        
        let filePath = realmPathComponent.path
        let fileManager = FileManager.default
        
        if fileManager.fileExists(atPath: filePath) {
            return (true, filePath)
        }
        
        return (false, "")
    }


    private func removeRealmFiles(path: String) {
        let realmURL = URL(fileURLWithPath: path)
        
        let realmURLs = [
            realmURL,
            realmURL.appendingPathExtension("lock"),
            realmURL.appendingPathExtension("realm"),
            realmURL.appendingPathExtension("management")
        ]
        for URL in realmURLs {
            do {
                try FileManager.default.removeItem(at: URL)
            } catch {
                debug("error while deleting realm urls")
            }
        }
    }```

In our testing we have found that there is a backup made by realm automatically so we deleted it for safety purpose. the path argument you will get in the if let (path, clientResetToken) = syncError.clientResetInfo()

func removeAutoGeneratedRealmBackUp(path: String) {
    do {
        try FileManager.default.removeItem(at: URL(fileURLWithPath: path))
    } catch {
        debug("error while deleting realm backUp path \(path)")
    }
} 
Uma_Shanker_Tiwari
  • 453
  • 1
  • 3
  • 16