Android: Realm+Retrofit 2+Gson
(2)
¿Por qué escribir todos estos serializadores personalizados cuando puede hacer que Gson y Realm trabajen juntos con solo UNA LÍNEA DE CÓDIGO ?
TL; DR.
Simplemente puede resolver esto pasando
RealmObjects
no
RealmObjects
a sus llamadas de
Retrofit
.
Si no desea pasar por toda esta respuesta, salte a la sección "Soluciones recomendadas" publicada a continuación.
Charla larga (respuesta detallada)
Esto no tiene nada que ver con Retrofit . Si ha configurado a Gson para que sea el convertidor de datos a su instancia de Retrofit actual, entonces puede estar seguro de que es Gson quien falla.
Supongamos 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}
Estaremos sorprendidos
¡Estamos viendo
nulls
todas partes!
¿Por qué?
Gson
no serializa un
RealmObject
solo si es
administrado
.
Lo que significa que actualmente hay una instancia de
Realm
abierta que se asegura de que este
RealmObject
refleje lo que se encuentra actualmente en la capa de persistencia (la base de datos de
Realm
).
La razón por la que esto está sucediendo se debe a la naturaleza conflictiva de cómo funcionan
Gson
y
Realm
.
Citando a
Zhuinden
sobre por qué
Gson
ve
null
todas partes:
... eso es porque GSON intenta leer los campos del objeto Realm a través de la reflexión, pero para obtener los valores, debe usar métodos de acceso, que se aplican automáticamente a todos los accesos de campo en el código a través del transformador Realm, pero la reflexión todavía ve nulos por todas partes ...
Christian Melchior
propone una
workaround
a este conflicto escribiendo un
JsonSerializers
personalizado para cada
Model
creado.
Esta es la solución que ha utilizado, pero
NO la recomendaría
.
Como te has dado cuenta, requiere
escribir mucho código
que es
propenso a errores
y lo peor de todo,
mata de qué trata
Gson
(lo que hace que nuestra vida sea menos dolorosa).
Soluciones recomendadas
Si de alguna manera podemos asegurarnos de que el objeto
realmObject
que pasamos a Gson no sea
managed
, evitaremos este conflicto.
Solución 1
Obtenga una copia en la memoria del RealmObject administrado y páselo a Gson
new Gson().toJson(realm.copyFromRealm(managedModel));
Solución 2
(Envolviendo la primera solución). Si la primera solución es demasiado detallada para usted, haga que sus modelos se vean así:
public class Model extends RealmObject {
@PrimaryKey
long id;
boolean happy;
// Some methods ...
public Model toUnmanaged() {
return isManaged() ? getRealm().copyFromRealm(this) : this;
}
}
Y luego, puedes hacer algo como esto:
// always convert toUnmanaged when serializing
new Gson().toJson(model.toUnmanaged());
Solución 3
Este NO es muy práctico pero vale la pena mencionarlo. Puede ir con clonación profunda de sus modelos.
Ya publicado here (desplácese hacia abajo y busque la publicación de @ AnixPasBesoin).
1 - Crear una interfaz genérica CloneableRealmObject:
interface CloneableRealmObject<T> {
T cloneRealmObject();
}
2 - Haga que su realObjetcs 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 - Clone el objeto antes de pasarlo a sus llamadas de Retrofit.
new Gson().toJson(model.cloneRealmObject());
En una publicación reciente
realmObjects
una
answer
explicando por qué estamos obteniendo esta salida serializada extraña cuando usamos
realmObjects
managed
.
Te recomiendo que lo eches un vistazo.
Prima
También puede consultar RealmFieldNamesHelper , una biblioteca creada por Christian Melchior "para hacer que las consultas de Realm sean más seguras".
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 configurar una
ExclusionStrategy
de
ExclusionStrategy
para
Gson
puede resolver este problema, y lo intenté pero no funcionó.
Mi código se ve así:
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 teniendo la pérdida de memoria. ¿Hay alguna sugerencia para este código?
Mi estructura json se ve así:
{"anotherObject":[{"propA": "someValue"}]}
Yo también enfrenté el problema similar. Esto se debe a que su formato de solicitud es incorrecto. En mi caso, estoy tratando de enviar un objeto Realm obteniéndolo de SQLite DB local en lugar de un objeto Java. Retrofit convierte solo objetos Java a JSON pero no objetos Realm. Asegúrese de enviar un JSON correcto como solicitud cuando use Retrofit.
Luego reemplacé esto:
List<MyRealmModel> objectsToSync = mRealm.where(MyRealmModel.class).findAll();
A:
List<MyRealmModel> objectsToSend = mRealm.copyFromRealm(objectsToSync);