J'ai examiné quelques autres solutions à des problèmes similaires, mais je ne comprends pas pourquoi la mienne ne fonctionne pas.
J'essaie de faire en sorte que mon application lise les commandes de Firebase et déplace un drone. La commande dans firebase provient d'un logiciel séparé. L'application est construite sur le code exemple du Parrot Drone SDK.
Il semble être capable d'extraire le texte de l'objet de commande et de l'ajouter à une vue texte, mais lorsqu'un nouvel enfant est ajouté, il se bloque. Je reçois cette erreur lorsqu'un nouvel enfant est ajouté.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.parrot.sdksample, PID: 10592
com.google.firebase.database.DatabaseException: Can't convert object of type Java.lang.String to type com.parrot.sdksample.activity.CommandObject
at com.google.Android.gms.internal.zg.zzb(Unknown Source)
at com.google.Android.gms.internal.zg.zza(Unknown Source)
at com.google.firebase.database.DataSnapshot.getValue(Unknown Source)
at com.parrot.sdksample.activity.MiniDroneActivity$13.onChildAdded(MiniDroneActivity.Java:383)
at com.google.Android.gms.internal.px.zza(Unknown Source)
at com.google.Android.gms.internal.vj.zzHX(Unknown Source)
at com.google.Android.gms.internal.vp.run(Unknown Source)
at Android.os.Handler.handleCallback(Handler.Java:739)
at Android.os.Handler.dispatchMessage(Handler.Java:95)
at Android.os.Looper.loop(Looper.Java:148)
at Android.app.ActivityThread.main(ActivityThread.Java:5417)
at Java.lang.reflect.Method.invoke(Native Method)
at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:726)
at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:616)
Voici un exemple de ma structure de données dans Firebase.
{
"drones" : {
"commands" : {
"-L-n9HwaQktOdI2VEVlH" : {
"executed" : false,
"text" : "TAKE_OFF",
"timestamp" : 1.512686825309134E9
},
"-L-nAuK5Ifde7Cdnan8K" : {
"executed" : false,
"text" : "LAND",
"timestamp" : 1.512687248764272E9
}
}
}
}
La fonction dans mon activité pour obtenir des données à partir de firebase ressemble à ceci.
private void initFirebase(){
mCommandTextView = (TextView) findViewById(R.id.commandTextView);
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference commandsRef = database.getReference("drones/commands");
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
CommandObject command = dataSnapshot.getValue(CommandObject.class);
mCommandTextView.append(command.text + "\n");
// I've tried commenting out the if statements below this, but it doesn't seem to make a difference.
if ("TAKE_OFF".equals(command.text)) {
mMiniDrone.takeOff();
} else if ("LAND".equals(command.text)) {
mMiniDrone.land();
}
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName){
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName){
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
commandsRef.addChildEventListener(childEventListener);
}
Ma classe CommandObject ressemble à ceci.
public class CommandObject {
public String text;
public float timestamp;
public boolean executed;
public CommandObject() {
}
public CommandObject(boolean executed, String text, float timestamp){
this.executed = executed;
this.text = text;
this.timestamp = timestamp;
}
}
J'ai également essayé d'utiliser un écouteur d'événements de valeur à la place, mais le même problème s'est produit.
Vous obtenez cette erreur:
Can't convert object of type Java.lang.String to type com.parrot.sdksample.activity.CommandObject
Parce que vous essayez de lire les données de type String
qui sont de type CommandObject
et que vous obtenez cette erreur.
Un moyen plus simple d'obtenir ces valeurs serait d'utiliser la classe String
comme ceci:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference commandsRef = rootRef.child("drones").child("commands");
ValueEventListener eventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
boolean executed = ds.child("executed").getValue(Boolean.class);
String text = ds.child("text").getValue(String.class);
double timestamp = ds.child("timestamp").getValue(Double.class);
Log.d("TAG", executed + " / " + text + " / " + timestamp);
}
}
@Override
public void onCancelled(DatabaseError databaseError) {}
};
commandsRef.addListenerForSingleValueEvent(eventListener);
Et cette approche utilisant un objet de la classe CommandObject
:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference commandsRef = rootRef.child("drones").child("commands");
ValueEventListener eventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
CommandObject commandObject = ds.getValue(CommandObject.class);
Log.d("TAG", commandObject.getExecuted() + " / " +
commandObject.getText() + " / " +
commandObject.getTimestamp());
}
}
@Override
public void onCancelled(DatabaseError databaseError) {}
};
commandsRef.addListenerForSingleValueEvent(eventListener);
Dans les deux cas, votre sortie sera:
false / TAKE_OFF / 1.512686825309134E9
false / LAND / 1.512687248764272E9
La solution fournie par Alex a fonctionné, mais ne permettait pas de comprendre pourquoi je ne pouvais pas stocker mes données dans un objet. Pour mon application, alors que l'enfant ajoutait un programme d'écoute, l'application était capable de lire des données qui se trouvaient déjà sur Firebase, mais lorsqu'elles étaient nouvelles. l'enfant est ajouté, la valeur est vide.
onChildAdded:DataSnapshot { key = -L0JjGo-3QMYDsuTMQcN, value = }
J'ai fait un peu plus de recherches pour savoir ce qui pourrait en être la cause et j'ai découvert que c'est probablement parce que les valeurs n'ont pas toutes été écrites en même temps. Et en regardant firebase, il semble que la clé soit ajoutée en premier, puis les valeurs sont ajoutées. C’est la raison pour laquelle l’erreur indique que je ne peux pas convertir, c’est parce qu’elle n’existe pas. J’utilise le SDK d’administrateur Python Firebase pour ajouter les données à Firebase, je ne suis donc pas sûr que ce soit la raison pour ça.
Donc, pour résoudre mon problème, j'ai déplacé mon code vers la fonction onChildChanged et ajouté une vérification de sorte que le code ne s'exécute que lorsque toutes les données dont j'ai besoin existent. De cette façon, je peux obtenir immédiatement les valeurs stockées dans un objet.
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName){
if (dataSnapshot.child("text").exists() &&
dataSnapshot.child("executed").exists() &&
dataSnapshot.child("timestamp").exists()){
Log.d(TAG, "onChildChanged:" + dataSnapshot.toString());
CommandObject command = dataSnapshot.getValue(CommandObject.class);
if ("TAKE_OFF".equals(command.text)) {
mMiniDrone.takeOff();
} else if ("LAND".equals(command.text)) {
mMiniDrone.land();
}
}
}
Alex a posté la bonne solution pour moi, mais depuis que j'utilise firebase-ui et kotlin, le code utilisé est différent. Plus d'infos ici
val options = FirebaseRecyclerOptions.Builder<ChatMessage>()
.setQuery(query, ChatMessage::class.Java)
.build()
// equivalent options object with manual parsing, use it to debug which field gives error
val options = FirebaseRecyclerOptions.Builder<ChatMessage>()
.setQuery(query, object : SnapshotParser<ChatMessage> {
override fun parseSnapshot(snapshot: DataSnapshot): ChatMessage {
val senderId = snapshot.child("senderId").getValue(String::class.Java)
val receiverId = snapshot.child("receiverId").getValue(String::class.Java)
val senderName = snapshot.child("senderName").getValue(String::class.Java)
val text = snapshot.child("text").getValue(String::class.Java)
val timestamp = snapshot.child("timestamp").getValue(Long::class.Java)
return ChatMessage(senderId, receiverId, senderName, text, timestamp)
}
})
.build()
adapterMessages = object : FirebaseRecyclerAdapter<ChatMessage, ChatHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatHolder {
val rootView = LayoutInflater.from(parent.context).inflate(R.layout.chat_message_item, parent, false)
return ChatHolder(rootView)
}
override fun onBindViewHolder(holder: ChatHolder, position: Int, model: ChatMessage) {
holder.populateItem(model)
}
}