Android: Realm + Retrofit 2 + Gson

Tengo un problema al usar Retrofit + Gson y Realm . Sé que hay un problema con la combinación de estas 3 bibliotecas. Algunas respuestas sugieren que establecer una ExclusionStrategy para Gson puede resolver este problema, y ​​lo probé pero no funcionó.

Mi código parece:

 public class ObjectList { public List<AnotherObject> anotherObject; } public class AnotherObject extends RealmObject { private String propA; public void setPropA(String propA){ this.setPropA = propA } public String getPropA(){ return propA } } Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { @Override public boolean shouldSkipField(FieldAttributes f) { return f.getDeclaringClass().equals(RealmObject.class); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } }).create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost/api/") .addConverterFactory(GsonConverterFactory.create(gson)) .build(); ObjectAPI objectAPI = retrofit.create(ObjectAPI.class); call.enqueue(new Callback<ObjectList>() { @Override public void onResponse(Response<ObjectList> response, Retrofit retrofit) { objectList = response.body().anotherObject; onRefreshComplete(); } @Override public void onFailure(Throwable t) { Toast.makeText(context, "Connection to server failed, please check your connection", Toast.LENGTH_LONG).show(); } }); 

Con el código actual, sigo recibiendo la pérdida de memoria. ¿Hay alguna sugerencia para este código?

Mi estructura de json se parece a:

 {"anotherObject":[{"propA": "someValue"}]} 

¿Por qué escribir todos estos serializadores personalizados cuando puede hacer que Gson y Realm trabajen juntos con sólo UNA LÍNEA DE CÓDIGO ?

TL; DR.

Puede solucionar esto simplemente pasando RealmObject no RealmObject a sus llamadas de Retrofit .

Si no quiere pasar por toda esta respuesta, vaya a la sección "Soluciones recomendadas" para ver cómo se puede hacer esto.

Versión detallada

Esto no tiene nada que ver con Retrofit . Si ha configurado Gson como el convertidor de datos para su instancia actual de Retrofit , asegúrese de que es Gson quien falla aquí.

Digamos que tenemos este modelo:

 public class Model extends RealmObject { @PrimaryKey long id; boolean happy; public Model() {/* Required by both Realm and Gson*/} public Model(long id, boolean happy) { this.id = id; this.happy = happy; } public long getId() { return id; } public boolean isHappy() { return happy; } } 

Para este código, no tendremos ningún problema:

 Model unmanagedModel = new Model(5, true); // unmanagedModel new Gson().toJson(unmanagedModel); // {id : 5, happy : true} 

Pero para este:

 Realm realm = /*...*/; Model managedModel = realm.copyToRealm(unmanagedModel); new Gson().toJson(managedModel); // {id : 0, happy : false} // We'll get the samething for this code Model anotherManagedModel = realm.where(Model.class).equalTo("id",5).findFirst(); new Gson().toJson(anotherManagedModel); // {id : 0, happy : false} 

Nos sorprenderemos. Estamos viendo nulls todas partes !.

¿Por qué?

Gson falla en la serialización de un RealmObject sólo si se trata de una gestión . Lo que significa que actualmente hay una instancia de Realm abierta que asegura que RealmObject refleja lo que actualmente se mantiene en la capa de persistencia (la base de datos Realm ).

La razón por la que esto sucede se debe a la naturaleza conflictiva de cómo trabajan tanto Gson como Realm . Citando Zhuinden sobre por qué Gson ve null todas partes:

… es porque GSON intenta leer los campos del objeto Realm a través de la reflexión, pero para obtener los valores, necesitas usar métodos accessor – que se aplican automáticamente a todos los campos de acceso en el código a través del Realm-transformador, pero la reflexión Todavía ve nulos en todas partes …

Christian Melchior propone una solución a este conflicto escribiendo un JsonSerializers personalizado para cada Model creemos. Esta es la solución que has utilizado pero no lo recomendaría porque como te has dado cuenta, requiere escribir un montón de código que es propenso a errores y lo peor de todo, mata lo que Gson se trata (que está haciendo nuestra vida menos dolorosa) .

Soluciones recomendadas

Si de alguna manera podemos asegurarnos de que el realmObject que pasamos a Gson no es una gestión, no tendremos ningún problema.

Solución 1

Obtenga una copia en memoria del RealmObject administrado y envíelo a Gson

 new Gson().toJson(realm.copyFromRealm(managedModel)); 

Solución 2

(Envoltura de la primera solución). Si la primera solución es demasiado detallada para usted, haga que sus modelos se parezcan a éste:

 public class Model extends RealmObject { @PrimaryKey long id; boolean happy; // Some methods ... public Model toUnmanaged(Realm realm) { return isManaged() ? realm.copyFromRealm(this) : this; } } 

Y entonces usted puede hacer algo como esto:

 // You can pass null when you're sure your model is unmanged new Gson().toJson(model.toUnmanaged(realm)); 

Solución 3

Si no te gusta pasar nulls o requerir un Realm al hacer alguna serialización, puedes ir con la clonación de tus modelos. ¡No necesita instancia de Realm !

Ya se ha publicado aquí (desplácese hacia abajo y busque el post de @AnixPasBesoin).

1 – Crear una interfaz genérica CloneableRealmObject:

 interface CloneableRealmObject<T> { T cloneRealmObject(); } 

2 – Haga que su realmObjetcs implemente la interfaz anterior de la siguiente manera:

 public class Model extends RealmObject implements CloneableRealmObject<Model> { @PrimaryKey long id; public Model() { // Empty constructor required by Realm. } @Override public Model cloneRealmObject() { Model clone = new Model(); clone.id = this.id; return clone; } } 

3 – Clonar el objeto antes de pasar a sus llamadas de Retrofit.

 new Gson().toJson(model.cloneRealmObject()); 

En una publicación reciente

Le di una respuesta explicando por qué estamos recibiendo esta extraña salida serializada al usar realmObjects administrados. Te recomiendo que lo eches un vistazo.

Prima

También es posible que desee comprobar RealmFieldNamesHelper , una biblioteca hecha por Christian Melchior "para que las consultas de Realm sean más seguras".

  • Llamar al servicio web WSDL mediante el uso de retrofit android
  • Error de actualización La cadena de consulta de URL no debe tener el bloque de reemplazo
  • Cómo saber cuándo finaliza una llamada de Retrofit
  • RecyclerView no se actualiza después de NotifyDataSetChanged (), el contenido aparece después de encender y apagar la pantalla
  • Retrofit Se esperaba BEGIN_OBJECT pero era BEGIN_ARRAY
  • Renovar 2 verifica la URL de la llamada
  • ¿Cómo implementar la función de búsqueda con SearchView, Retrofit y RxJava (RxBindings)?
  • ¿Cómo usar Mockito para probar las API a lo largo de Robospice y Retrofit?
  • Manejo de errores personalizados con Retrofit cuando se ofusca usando proguard da java.lang.reflect.UndeclaredThrowableException
  • Retrofit: ¿Cómo especificar parámetros separados por comas en la solicitud?
  • Uso de Retrofit para acceder a una API con key / id
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.