9

I've looked at a few other answers for similar problems but don't understand why mine isn't working.

I'm trying to get my app to read commands from Firebase and move a drone. The command in firebase comes from a separate software. The app is built on top of the Parrot Drone SDK Sample code.

It seems to be able to get the text from the command object and append it to a textview, but when a new child is added it just crashes. I'm getting this error when a new child is added.

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)

A sample of my data structure in firebase is below.

{
"drones" : {
    "commands" : {
    "-L-n9HwaQktOdI2VEVlH" : {
        "executed" : false,
        "text" : "TAKE_OFF",
        "timestamp" : 1.512686825309134E9
    },
    "-L-nAuK5Ifde7Cdnan8K" : {
        "executed" : false,
        "text" : "LAND",
        "timestamp" : 1.512687248764272E9
    }
    }
}
}

The function in my activity to get data from firebase looks like this.

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);
    }

My CommandObject class looks like this.

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;
    }
}

I've also tried using a value event listener instead, but the same problem occured.

2
  • 1
    On first sight the code and the JSON you shared match up. Are you sure you have no children under /drones/commands that are of a different format? Commented Dec 7, 2017 at 23:57
  • Yes, the json was an export from the root of the firebase. Commented Dec 8, 2017 at 9:07

7 Answers 7

9

You are getting this error:

Can't convert object of type java.lang.String to type com.parrot.sdksample.activity.CommandObject

Because you are trying to read the data of type String which is of type CommandObject and that why you are getting this error.

A more simple way to get those values would be to use the String class like this:

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);

And this the approach using an object of CommandObject class:

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);

In both cases your output will be:

false / TAKE_OFF / 1.512686825309134E9
false / LAND / 1.512687248764272E9
Sign up to request clarification or add additional context in comments.

Comments

4

The solution provided by Alex worked but didn't quite answer why I couldn't get store my data in an object, and for my app while the child added listener, the app is able to read data that was already on Firebase but when new child is added, the value is empty.

    onChildAdded:DataSnapshot { key = -L0JjGo-3QMYDsuTMQcN, value =  }

I've done a little more digging to find out what might be causing this and found that it is probably caused because the values were not all written at the same time. And looking at firebase it seems that the key is added first, then the values are added. This is why the error says can't convert, it is because it doesn't exist. I'm using the Python Firebase Admin SDK to add the data to Firebase, so I'm not sure if this is the reason for that.

So to fix my problem I moved my code to the onChildChanged function and added a check so that the code only runs when all of the data I need exists. This way I can get the values stored in an object straight away.

    @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();
            }
        }
    }

1 Comment

I haven't seen in your question that you want to get the data from the object. I updated my answer accordingly. What you are saying in your answer that "is caused because the values were not all written at the same time." it is incorrect. You are getting that error because you were trying to read the data of type String which was of type CommandObject and that why you were getting that error. This is the single reason and that's why i provided you that answer and used the String class.
2

Alex posted the right solution for me, but since I was using firebase-ui and kotlin, the code used is different. More info here

    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)
        }
    }

Comments

1

if u are using map object in your database should use get method directly with your object

doc: DocumentSnapshot val data = doc.get("location", SaleLocations::class.java)

Comments

0

I faced a similar issue where I could not retrieve the data from the database; having a DatabaseException error with a message

can't convert from this type to that type.

The mistake I was making was I was writing incorrectly to the database. I wrote something like ...

mdatabaseReference.setValue(myObject)

instead of

mdatabaseReference.child(myObject.getId()).setValue(myObject)

So essentially the answer I am suggesting is: you should ensure your data is written correctly to the database at the right node in the JSON tree if not you would likely get this kind of error when you are reading from the database.

1 Comment

@Desmond chin should have shared as well the code where he was writing to the database so we could check to be sure he didn't make this kind of error when he was writing to the database.
0

you can use constructor oop normally like this

it's the same and no Error anymore

CommandObject command = new CommandObject(ds.child("").getValue()....

fill it and make sure your reference in firebase data should not be empty

Comments

0

In my case the problem was, that I using Kotlin class, and there were no default constructor such as

 constructor() : this(0, "")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.