Tip
Obtenga más información sobre los restablecimientos de clientes
Para conocer las causas y las estrategias para manejar los restablecimientos de clientes, consulte Sincronización Página de restablecimiento de cliente.
El SDK lee y escribe en un archivo de dominio en el dispositivo. Al usar Atlas Device Sync, este dominio local se sincroniza con el backend de la aplicación. Algunas condiciones pueden impedir que el dominio se sincronice con el backend. En este caso, se produce un error de restablecimiento del cliente.
Este error significa que debe restablecer el archivo de dominio en la aplicación cliente. Los clientes en este estado pueden seguir ejecutándose y guardando datos localmente. Hasta que restablezca el cliente, el dominio no se sincronizará con el 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 dominio no sincronizable y descargue una nueva copia. Invalida los detectores de cambios.
Ambas opciones permiten escribir lógica personalizada para recuperar los cambios locales. Ninguna de las dos opciones puede recuperar los cambios locales automáticamente.
Descartar los cambios no sincronizados es una alternativa menos compleja a la recuperación manual. Sin embargo, esta estrategia no puede gestionar todos los errores de restablecimiento de cliente. Debe mantener un controlador de restablecimiento manual de cliente como alternativa.
Descartar cambios no sincronizados
Nuevo en la versión 10.10.0.
Descartar cambios no sincronizados es una estrategia de restablecimiento de cliente proporcionada por el SDK. Esta estrategia requiere un código mínimo y realiza un restablecimiento sin cerrar el dominio ni perder notificaciones.
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.
La opción Descartar cambios no sincronizados no permite gestionar cambios de esquema disruptivos o destructivos. Cuando se producen cambios disruptivos, el SDK recurre al modo de recuperación manual.
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 reinicio del cliente desde el backend. Esto ocurre antes de que el SDK ejecute la estrategia de reinicio del cliente.onAfterReset()El SDK llama a este bloque tras ejecutar correctamente esta estrategia. Este bloque proporciona una copia congelada del dominio original. También devuelve una instancia activa del dominio en estado sincronizable.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() )
Descartar cambios no sincronizados después de interrumpir los cambios de esquema
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:
Todos los clientes deben realizar un reinicio del cliente.
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() )
Recuperar manualmente los cambios no sincronizados
Tip
La recuperación manual reemplaza el controlador obsoleto SyncSession.ClientResetHandler. Los clientes que usan el controlador obsoleto pueden actualizar a la recuperación manual sin cambios lógicos.
No recomendamos la recuperación manual del cliente. Requiere:
Cantidades sustanciales de código
Concesiones de esquema
Lógica de resolución de conflictos complejos.
Para obtener más información, consulte la Guía avanzada para la recuperación de datos del restablecimiento manual del cliente.
Manejo del restablecimiento del cliente de prueba
Puede probar manualmente el manejo de restablecimiento del cliente de su aplicación finalizando y volviendo a habilitar la sincronización del dispositivo.
Al finalizar y reactivar Sync, los clientes que se conectaron previamente con Sync no podrán hacerlo hasta que restablezcan el cliente. Al finalizar Sync, se eliminan los metadatos del servidor que permiten la sincronización. El cliente debe descargar una nueva copia del dominio desde el servidor. El servidor envía un error de restablecimiento de cliente a estos clientes. Por lo tanto, al finalizar Sync, se activa la condición de restablecimiento de cliente.
Para probar el manejo del restablecimiento del cliente:
Escriba datos desde una aplicación cliente y espere a que se sincronicen.
Termina y vuelve a habilitar Device Sync.
Ejecute la aplicación cliente nuevamente. La aplicación debería mostrar un error de restablecimiento de cliente al intentar 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.