Tip
Learn More About Client Resets
Para conocer las causas y las estrategias para manejar los restablecimientos de clientes, consulte Sincronización Página de restablecimiento de cliente.
The SDK reads and writes to a realm file on the device. When you use Atlas Device Sync, this local realm syncs with the application backend. Some conditions can cause the realm to be unable to sync with the backend. When this occurs, you get a client reset error.
This error means you must reset the realm file in the client application. Clients in this state may continue to run and save data locally. Until you perform the client reset, the realm does not sync with the backend.
Elija una estrategia de restablecimiento de cliente para gestionar los errores de restablecimiento. Estas estrategias restauran el dominio a un estado sincronizable, pero tienen desventajas:
Descartar cambios no sincronizados. Restaurar la sincronización descartando los cambios locales desde la última sincronización. Mantiene los detectores de cambios.
Recuperar manualmente cambios no sincronizados: Mueva el realm no sincronizable y descargue una copia nueva. Invalida los escuchadores de cambios.
Both options let you write custom logic to recover local changes. Neither option can recover local changes for you.
Discard unsynced changes is a less complex alternative to manual recovery. However, this strategy cannot handle every client reset error. You must maintain a manual client reset handler as a fallback.
Descartar cambios no sincronizados
New in version 10.10.0.
Discard unsynced changes is a client reset strategy provided by the SDK. This strategy requires minimal code. This strategy performs a reset without closing the realm or missing notifications.
Elimina todos los cambios locales realizados desde la última sincronización exitosa. Esto incluye cualquier dato ya escrito en el dominio, pero aún no sincronizado con el backend de la aplicación. No utilice esta estrategia si su aplicación no puede eliminar los datos no sincronizados.
Discard unsynced changes cannot handle breaking or destructive schema changes. When breaking changes occur, the SDK falls back to manual recovery mode.
Para usar esta estrategia, pase una instancia de DiscardUnsyncedChangesStrategy al método de construcción defaultSyncClientResetStrategy() cuando cree una instancia de su AppSu instancia DiscardUnsyncedChangesStrategy debe implementar los siguientes métodos:
onBeforeReset(). El SDK llama a este bloque cuando recibe un error de restablecimiento del cliente desde el backend. Esto ocurre antes de que el SDK ejecute la estrategia de restablecimiento del cliente.onAfterReset(). The SDK calls this block after successfully executing this strategy. This block provides a frozen copy of the original realm. It also returns a live instance of the realm in a syncable state.onError()El SDK llama a este método durante un cambio de esquema importante. Su comportamiento es similar al de defaultClientResetStrategy().
El siguiente ejemplo implementa esta estrategia:
String appID = YOUR_APP_ID; // replace this with your App ID App app = new App(new AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(new DiscardUnsyncedChangesStrategy() { public void onBeforeReset(Realm realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.getPath()); } public void onAfterReset(Realm before, Realm after) { Log.w("EXAMPLE", "Finished client reset for " + before.getPath()); } public void onError(SyncSession session, ClientResetRequiredError error) { Log.e("EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual recovery: " + error.getErrorMessage()); handleManualReset(session.getUser().getApp(), session, error); } }) .build());
val appID: String = YOUR_APP_ID // replace this with your App ID val app = App( AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { override fun onBeforeReset(realm: Realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.path) } override fun onAfterReset(before: Realm, after: Realm) { Log.w("EXAMPLE", "Finished client reset for " + before.path) } override fun onError(session: SyncSession, error: ClientResetRequiredError) { Log.e( "EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual recovery: " + error.errorMessage ) handleManualReset(session.user.app, session, error) } }) .build() )
Discard Unsynced Changes after Breaking Schema Changes
Importante
Los cambios importantes en el esquema requieren una actualización del esquema de la aplicación
Después de un cambio de esquema importante:
All clients must perform a client reset.
Debe actualizar los modelos de cliente afectados por el cambio de esquema importante.
La estrategia de descartar cambios no sincronizados no puede gestionar cambios importantes. Debe gestionar manualmente el restablecimiento del cliente en el método onError(). Este ejemplo descarta manualmente los cambios no sincronizados para gestionar el restablecimiento del cliente:
String appID = YOUR_APP_ID; // replace this with your App ID App app = null; AtomicReference<App> globalApp = new AtomicReference<>(app); // accessing the app from within the lambda below requires an effectively final object app = new App(new AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(new DiscardUnsyncedChangesStrategy() { public void onBeforeReset(Realm realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.getPath()); } public void onAfterReset(Realm before, Realm after) { Log.w("EXAMPLE", "Finished client reset for " + before.getPath()); } public void onError(SyncSession session, ClientResetRequiredError error) { Log.e("EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual client reset execution: " + error.getErrorMessage()); // close all instances of your realm -- this application only uses one globalRealm.close(); try { Log.w("EXAMPLE", "About to execute the client reset."); // execute the client reset, moving the current realm to a backup file error.executeClientReset(); Log.w("EXAMPLE", "Executed the client reset."); } catch (IllegalStateException e) { Log.e("EXAMPLE", "Failed to execute the client reset: " + e.getMessage()); // The client reset can only proceed if there are no open realms. // if execution failed, ask the user to restart the app, and we'll client reset // when we first open the app connection. AlertDialog restartDialog = new AlertDialog.Builder(activity) .setMessage("Sync error. Restart the application to resume sync.") .setTitle("Restart to Continue") .create(); restartDialog.show(); } // open a new instance of the realm. This initializes a new file for the new realm // and downloads the backend state. Do this in a background thread so we can wait // for server changes to fully download. ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { Realm newRealm = Realm.getInstance(globalConfig); // ensure that the backend state is fully downloaded before proceeding try { globalApp.get().getSync().getSession(globalConfig).downloadAllServerChanges(10000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } Log.w("EXAMPLE", "Downloaded server changes for a fresh instance of the realm."); newRealm.close(); }); // execute the recovery logic on a background thread try { executor.awaitTermination(20000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }) .build()); globalApp.set(app);
val appID = YOUR_APP_ID // replace this with your App ID var app: App? = null app = App( AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { override fun onBeforeReset(realm: Realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.path) } override fun onAfterReset(before: Realm, after: Realm) { Log.w("EXAMPLE", "Finished client reset for " + before.path) } override fun onError(session: SyncSession, error: ClientResetRequiredError) { Log.e( "EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual client reset execution: " + error.errorMessage ) // close all instances of your realm -- this application only uses one globalRealm!!.close() try { Log.w("EXAMPLE", "About to execute the client reset.") // execute the client reset, moving the current realm to a backup file error.executeClientReset() Log.w("EXAMPLE", "Executed the client reset.") } catch (e: java.lang.IllegalStateException) { Log.e("EXAMPLE", "Failed to execute the client reset: " + e.message) // The client reset can only proceed if there are no open realms. // if execution failed, ask the user to restart the app, and we'll client reset // when we first open the app connection. val restartDialog = AlertDialog.Builder(activity) .setMessage("Sync error. Restart the application to resume sync.") .setTitle("Restart to Continue") .create() restartDialog.show() } // open a new instance of the realm. This initializes a new file for the new realm // and downloads the backend state. Do this in a background thread so we can wait // for server changes to fully download. val executor = Executors.newSingleThreadExecutor() executor.execute { val newRealm = Realm.getInstance(globalConfig) // ensure that the backend state is fully downloaded before proceeding try { app!!.sync.getSession(globalConfig) .downloadAllServerChanges( 10000, TimeUnit.MILLISECONDS ) } catch (e: InterruptedException) { e.printStackTrace() } Log.w( "EXAMPLE", "Downloaded server changes for a fresh instance of the realm." ) newRealm.close() } // execute the recovery logic on a background thread try { executor.awaitTermination(20000, TimeUnit.MILLISECONDS) } catch (e: InterruptedException) { e.printStackTrace() } } }) .build() )
Manually Recover Unsynced Changes
Tip
Manual recovery replaces the deprecated SyncSession.ClientResetHandler. Clients using the deprecated handler can update to manual recovery with no logic changes.
No recomendamos la recuperación manual del restablecimiento del cliente. Se requiere:
Substantial amounts of code
Concesiones de esquema
Complex conflict resolution logic.
To learn more, see the Advanced Guide to Manual Client Reset Data Recovery.
Test Client Reset Handling
Puede probar manualmente el manejo de restablecimiento del cliente de su aplicación finalizando y volviendo a habilitar la sincronización del dispositivo.
When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.
To test client reset handling:
Write data from a client application and wait for it to synchronize.
Termina y vuelve a habilitar Device Sync.
Ejecuta la aplicación cliente nuevamente. La aplicación debería obtener un error de restablecimiento del cliente cuando intente conectarse al servidor.
Advertencia
Mientras itera sobre la gestión del restablecimiento de clientes en su aplicación cliente, es posible que tenga que finalizar y volver a habilitar la sincronización repetidamente. Esto impide que todos los clientes existentes puedan sincronizar hasta después de completar un restablecimiento. Para evitar esto en producción, pruebe la gestión del restablecimiento de clientes en un entorno de desarrollo.