guardar - ¿Por qué me aparece el mensaje "Fallé al rebotar para escribir" cuando cambio JSON de Firebase a objetos Java?
guardar datos en firebase android (3)
[Divulgación: soy un ingeniero en Firebase. Esta pregunta pretende ser una pregunta de referencia para responder muchas preguntas de una vez.]
Tengo la siguiente estructura JSON en mi base de datos de Firebase:
{
"users": {
"-Jx5vuRqItEF-7kAgVWy": {
"handle": "puf",
"name": "Frank van Puffelen",
"soId": 209103
},
"-Jx5w3IOHD2kRFFgkMbh": {
"handle": "kato",
"name": "Kato Wulf",
"soId": 394010
},
"-Jx5x1VWs08Zc5S-0U4p": {
"handle": "mimming",
"name": "Jenny Tong",
"soId": 839465
}
}
}
Estoy leyendo esto con el siguiente código:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
}
Firebase ref = new Firebase("https://stackoverflow.firebaseio.com/32108969/users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
System.out.println(user.toString());
}
}
@Override
public void onCancelled(FirebaseError firebaseError) { }
});
Pero me sale este error:
Excepción en el hilo "FirebaseEventTarget" com.firebase.client.FirebaseException: no se pudo revertir al tipo
¿Cómo puedo leer a mis usuarios en objetos Java?
Firebase utiliza Jackson para permitir la serialización de objetos Java en JSON y la deserialización de JSON en objetos Java. Puede encontrar más acerca de Jackson en el sitio web de Jackson y en esta página sobre las anotaciones de Jackson .
En el resto de esta respuesta, mostraremos algunas formas comunes de usar Jackson con Firebase.
Cargando usuarios completos
La forma más sencilla de cargar a los usuarios de Firebase en Android es si creamos una clase Java que imita por completo las propiedades en el JSON:
private static class User {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
@Override
public String toString() { return "User{handle=''"+handle+“'', name=''"+name+"'', stackId="+stackId+"/’}”; }
}
Podemos usar esta clase en un oyente:
Firebase ref = new Firebase("https://.firebaseio.com/32108969/users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
System.out.println(user.toString());
}
}
@Override
public void onCancelled(FirebaseError firebaseError) { }
});
Puede observar que la clase User sigue el patrón de propiedad JavaBean . Cada propiedad JSON se mapea por un campo en la clase User y tenemos un método public getter para cada campo. Al asegurarnos de que todas las propiedades estén mapeadas con el mismo nombre, nos aseguramos de que Jackson pueda mapearlas automáticamente.
También puede controlar manualmente la asignación colocando anotaciones Jackson en su clase Java y sus campos y métodos. @JsonIgnore
las dos anotaciones más comunes ( @JsonIgnore
y @JsonIgnoreProperties
) a continuación.
Carga parcial de usuarios
Digamos que solo le importa el nombre del usuario y maneja su código Java. Vamos a eliminar el stackId
y ver qué pasa:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
@Override
public String toString() {
return "User{handle=''" + handle + “/', name=''" + name + "/’}”;
}
}
Si ahora adjuntamos el mismo oyente que antes y ejecutamos el programa, lanzará una excepción:
Exception in thread "FirebaseEventTarget" com.firebase.client.FirebaseException: Failed to bounce to type
at com.firebase.client.DataSnapshot.getValue(DataSnapshot.java:187)
at com.firebase.LoadPartialUsers$1.onDataChange(LoadPartialUsers.java:16)
El "tipo de error de eliminación de rebote" indica que Jackson no pudo deserializar el JSON en un objeto Usuario. En la excepción anidada, nos dice por qué:
Caused by: com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "stackId" (class com.firebase.LoadPartialUsers$User), not marked as ignorable (2 known properties: , "handle", "name"])
at [Source: java.io.StringReader@43079089; line: 1, column: 15] (through reference chain: com.firebase.User["stackId"])
at com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
Jackson encontró una propiedad stackId
en el JSON y no sabe qué hacer con ella, por lo que arroja una excepción. Afortunadamente, hay una anotación que podemos usar para indicarle que ignore las propiedades específicas de JSON al mapearlo a nuestra clase de User
:
@JsonIgnoreProperties({ “stackId" })
private static class User {
...
}
Si no ejecutamos el código nuevamente con nuestro oyente, Jackson sabrá que puede ignorar stackId
en el JSON y podrá deserializar el JSON en un objeto User nuevamente.
Dado que agregar propiedades al JSON es una práctica tan común en las aplicaciones de Firebase, quizás le resulte más conveniente simplemente decirle a Jackson que ignore todas las propiedades que no tienen un mapeo en la clase Java:
@JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
...
}
Ahora si agregamos propiedades a JSON más tarde, el código de Java aún podrá cargar los User
''s. Solo tenga en cuenta que los objetos de Usuario no contendrán toda la información que estaba presente en el JSON, así que tenga cuidado al volver a escribirlos en Firebase.
Parcialmente ahorrando usuarios
Una de las razones por las que es bueno tener una clase Java personalizada es que podemos agregarle métodos de conveniencia. Digamos que agregamos un método de conveniencia que obtiene el nombre para mostrar para un usuario:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
@JsonIgnore
public String getDisplayName() {
return getName() + “ (" + getHandle() + ")";
}
@Override
public String toString() {
return "User{handle=''" + handle + "/', name=''" + name + "/', displayName=''" + getDisplayName() + "''}";
}
}
Ahora leamos a los usuarios de Firebase y escríbalos de nuevo en una nueva ubicación:
Firebase srcRef = new Firebase("https://.firebaseio.com/32108969/users");
final Firebase copyRef = new Firebase("https://.firebaseio.com/32108969/copiedusers");
srcRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
copyRef.child(userSnapshot.getKey()).setValue(user);
}
}
@Override
public void onCancelled(FirebaseError firebaseError) { }
});
El JSON en el nodo copiedusers
ve así:
"copiedusers": {
"-Jx5vuRqItEF-7kAgVWy": {
"displayName": "Frank van Puffelen (puf)",
"handle": "puf",
"name": "Frank van Puffelen"
},
"-Jx5w3IOHD2kRFFgkMbh": {
"displayName": "Kato Wulf (kato)",
"handle": "kato",
"name": "Kato Wulf"
},
"-Jx5x1VWs08Zc5S-0U4p": {
"displayName": "Jenny Tong (mimming)",
"handle": "mimming",
"name": "Jenny Tong"
}
}
No es lo mismo que el JSON de origen, porque Jackson reconoce el nuevo método getDisplayName()
como un get de JavaBean y, por lo tanto, agregó una propiedad displayName
al JSON que genera. JsonIgnore
este problema agregando una anotación JsonIgnore
a getDisplayName()
.
@JsonIgnore
public String getDisplayName() {
return getName() + "(" + getHandle() + ")";
}
Al serializar un objeto User, Jackson ahora ignorará el método getDisplayName()
y el JSON que escribamos será el mismo que el que obtuvimos.
Las versiones 9.x (y superiores) del SDK de Firebase para Android / Java dejaron de incluir a Jackson para serializar / deserializar Java <-> JSON. El SDK más nuevo en su lugar proporciona un conjunto mínimo de anotaciones personalizadas para permitir el control de las necesidades de personalización más comunes, a la vez que tiene un impacto mínimo en el tamaño de JAR / APK resultante.
Mi respuesta original sigue siendo válida si eres:
- Uso de los SDK de Firebase 2.x
- Uso de Firebase 9.0 o superior SDK, pero use Jackson para serializar / deserializar Java <-> JSON .
El resto de esta respuesta cubre cómo manejar escenarios de serialización / deserialización en Firebase SDK 9.0 o superior.
Estructura de datos
Comenzaremos con esta estructura JSON en nuestra base de datos de Firebase:
{
"-Jx86I5e8JBMZ9tH6W3Q" : {
"handle" : "puf",
"name" : "Frank van Puffelen",
"stackId" : 209103,
"Id" : 209103
},
"-Jx86Ke_fk44EMl8hRnP" : {
"handle" : "mimming",
"name" : "Jenny Tong",
"stackId" : 839465
},
"-Jx86N4qeUNzThqlSMer" : {
"handle" : "kato",
"name" : "Kato Wulf",
"stackId" : 394010
}
}
Cargando usuarios completos
En su forma más básica, podemos cargar cada usuario de este JSON en la siguiente clase de Java:
private static class CompleteUser {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
@Override
public String toString() { return "User{handle=''"+handle+"'', name=''"+name+"'', stackId="+stackId+ "''}"; }
}
Si declaramos que los campos son públicos, ni siquiera necesitamos los getters:
private static class CompleteUser {
public String handle;
public String name;
public long stackId;
}
Carga parcial de usuarios
También podemos cargar parcialmente a un usuario, por ejemplo con:
private static class PartialUser {
String handle;
String name;
public String getHandle() {
return handle;
}
public String getName() { return name; }
@Override
public String toString() {
return "User{handle=''" + handle + "'', NAME=''" + name + "''''}";
}
}
Cuando usamos esta clase para cargar a los usuarios desde el mismo JSON, el código se ejecuta (a diferencia de la variante de Jackson mencionada en mi otra respuesta). Pero verá una advertencia en su salida de registro:
ADVERTENCIA: Ningún setter / field para stackId encontrado en la clase Anotaciones $ PartialUser
Así que deshazte de eso, podemos anotar la clase con @IgnoreExtraProperties
:
@IgnoreExtraProperties
private static class PartialUser {
String handle;
String name;
public String getHandle() {
return handle;
}
public String getName() { return name; }
@Override
public String toString() {
return "User{handle=''" + handle + "'', NAME=''" + name + "''''}";
}
}
Parcialmente ahorrando usuarios
Como antes, es posible que desee agregar una propiedad calculada para el usuario. Desearía ignorar tal propiedad al guardar los datos nuevamente en la base de datos. Para hacer esto, puede anotar la propiedad / getter / setter / field con @Exclude
:
private static class OvercompleteUser {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
@Exclude
public String getTag() { return getName() + " ("+getHandle()+")"; }
@Override
public String toString() { return "User{handle=''"+handle+"'', name=''"+name+"'', stackId="+stackId+ "''}"; }
}
Ahora, al escribir un usuario en la base de datos, se getTag()
el valor de getTag()
.
Usar un nombre de propiedad diferente en el JSON que en el código de Java
También puede especificar qué nombre debe obtener un campo / getter / setter del código Java en el JSON de la base de datos. Para hacer esto: anote el campo / getter / setter con @PropertyName()
.
private static class UserWithRenamedProperty {
String handle;
String name;
@PropertyName("stackId")
long Id;
public String getHandle() { return handle; }
public String getName() { return name; }
@PropertyName("stackId")
public long getId() { return Id; }
@Override
public String toString() { return "User{handle=''"+handle+"'', name=''"+name+"'', stackId="+Id+ "''}"; }
}
En general, es mejor usar la asignación predeterminada entre Java <-> JSON que usa Firebase SDK. Pero @PropertyName
puede ser necesario cuando tienes una estructura JSON preexistente que no puedes asignar a las clases de Java.
Porque la carpeta de ruta de consulta incorrecta en root.this es un ejemplo
My code:
private void GetUpdates(DataSnapshot snapshot){
romchat.clear();
for (DataSnapshot ds: snapshot.getChildren()){
Rowitemroom row = new Rowitemroom();
row.setCaption(ds.getValue(Rowitemroom.class).getCaption());
row.setFileUrl(ds.getValue(Rowitemroom.class).getFileUrl());
romchat.add(row);
/* Rowitemroom row = snapshot.getValue(Rowitemroom.class);
String caption = row.getCaption();
String url = row.getFileUrl();*/
}
if (romchat.size()>0){
adapter = new CustomRoom(context,romchat);
recyclerView.setAdapter(adapter);
}else {
Toast.makeText(context, "No data", Toast.LENGTH_SHORT).show();
}
}
db_url ="your apps`enter code here`.appspot.com/admins"