10

JSON structure:

{
    "breviario": {
        "metaLiturgia": {
                "fecha"  : "Martes  5 de febrero del 2019",
                "tiempo" : "PROPIO DE LOS SANTOS",
                "semana"   : "",
                "mensaje": "",
                "salterio": "",
                "color":0,
                "meta": ""
        },
        "santo": {
                "nombre": "Santa Águeda, virgen y mártir",
                "vida": "Padeció el martirio en Catania (Sicilia), probablemente en la persecución de Decio. Desde la antigüedad su culto se extendió por toda la Iglesia y su nombre fue introducido en el Canon romano."
        },

        "oficio": {
            "himno": {
                "texto": "Testigos de amor~de Cristo Señor,~mártires santos.§Rosales en flor~de Cristo el olor,~mártires santos.§Palabras en luz~de Cristo Jesús,~mártires santos.§Corona inmortal~del Cristo total,~mártires santos. Amén."
            },
            "salmodia": {
  ... 


Oficio:

Structure of Ofcio

public class Oficio {
    private Invitatorio invitatorio;
    private Himno himno;
    private Salmodia salmodia;
    private String oracion;
    private String responsorio;
    private OficioLecturas oficioLecturas;
    public Oficio () {}

    public Himno getHimno() {
        return himno;
    }

    public void setHimno(Himno himno) {
        this.himno = himno;
    }
// ...
}


Himno:

Structure of Himno

public class Himno {
    private String texto;
    public Himno () {}

    public Spanned getTexto() {
        Spanned str = Utils.fromHtml(Utils.getFormato(texto));
        return str;
    }

    public void setTexto(String texto) {
        this.texto = texto;
    }
    //...
}


What I have tried:

    DocumentReference docRef = db.collection("liturgia").document("breviario")
            .collection("oficio").document("20190204");
    docRef.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
        @Override
        public void onSuccess(DocumentSnapshot documentSnapshot) {
            Oficio oficio = documentSnapshot.toObject(Oficio.class);
            Himno himno=oficio.getHimno();
            Log.d(TAG,oficio.getOracion().toString()); //Correct
        }
    });


The problem:

I can't read the property himno as the custom class 'Himno'. When I try, I get a 'RuntimeException' even if I comment the line: Himno himno=oficio.getHimno();. I can however get the property oracion into the corresponding variable.


Stack trace:

E/AndroidRuntime: FATAL EXCEPTION: main Process: org.my.app, PID: 10532 java.lang.RuntimeException: Could not deserialize object. Can't convert object of type com.google.firebase.firestore.DocumentReference to type org.my.app.model.Himno (found in field 'himno') at com.google.firebase.firestore.util.CustomClassMapper.deserializeError(com.google.firebase:firebase-firestore@@17.1.2:524) at com.google.firebase.firestore.util.CustomClassMapper.convertBean(com.google.firebase:firebase-firestore@@17.1.2:505) at com.google.firebase.firestore.util.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-firestore@@17.1.2:242) at com.google.firebase.firestore.util.CustomClassMapper.deserializeToType(com.google.firebase:firebase-firestore@@17.1.2:180) at com.google.firebase.firestore.util.CustomClassMapper.access$200(com.google.firebase:firebase-firestore@@17.1.2:53) at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-firestore@@17.1.2:700) at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-firestore@@17.1.2:674) at com.google.firebase.firestore.util.CustomClassMapper.convertBean(com.google.firebase:firebase-firestore@@17.1.2:503) at com.google.firebase.firestore.util.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-firestore@@17.1.2:242) at com.google.firebase.firestore.util.CustomClassMapper.convertToCustomClass(com.google.firebase:firebase-firestore@@17.1.2:97) at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@17.1.2:203) at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@17.1.2:183)

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
A. Cedano
  • 557
  • 7
  • 39

2 Answers2

7

You are getting the following error:

Can't convert object of type com.google.firebase.firestore.DocumentReference to type org.my.app.model.Himno (found in field 'himno')

Because you are trying to convert a DocumentReference object to a Himno object. There is no way in Java to achieve this since there is no inheritance relationship between them.

The document that you are trying to get from the following location:

db.collection("liturgia").document("breviario").collection("oficio").document("20190204");

Is of type Oficio, so the following line of code:

Oficio oficio = documentSnapshot.toObject(Oficio.class);

Will work perfectly fine. The problem is when you are trying to get the Himno object which is nested under your Oficio class like this:

Himno himno=oficio.getHimno();

And this will not work in the way you do since your himno property inside the document holds a value which is of type DocumentReference and not of type Himno.

enter image description here

See, the himno property has a reference. If you want to get that document reference, simply use:

DocumentReference documentReference = documentSnapshot.getDocumentReference("himno");

If you want to use:

Himno himno=oficio.getHimno();

Then change the himno property to be of type Himno and not DocumentReference.

Another issue in your code, as also @Anees pointed out in his aswer is that the getTexto() method should return a String and not a Spanned object because this the way in which is stored in the database.

enter image description here

See, the texto property holds a String and not a Spanned. But this is not the reason why your error occurs.

Please also note, that the following sentence:

Custom classes in Firestore must contain

Is incorrect! There isn't a must for the public no-argument constructor nor for the public getters.

So you class might simply look like this:

public class Oficio {
    public Invitatorio invitatorio;
    public Himno himno;
    public Salmodia salmodia;
    public String oracion;
    public String responsorio;
    public OficioLecturas oficioLecturas;
}

Without any constructors, setters or getters at all. More informations here.

Edit:

According to your comment:

I changet the type of getTexto() from Spanned to String, but is not working.

Changing only the return type of your getTexto() method from Spanned to String will not help you solve the main error.

I don't understand how i can change the himno property to be of type Himno and not DocumentReference.

You haven't shared the way you are adding the data to the database but it's actually very simple. First, remove that himno property from those documents. Then, when you are adding a new document, instead of adding a DocumentReference, add an object of type Himno, as I also see that you have declared in your Oficio class. This also means, that when you'll use the following line of code:

Himno himno=oficio.getHimno();

An object of type Himno will be returned and not a DocumentReference. This is because it is stored accordingly to its data type.

In RealTimeDatabase is possible to take only one object (Oficio par example) and from this object i can get references to another nested object (Himno par example).

This can be also done in Cloud Firestore but you need to get the data according to the data type of your property it exists in the database. If your property is of type DocumentReference, you cannot get it as a type Himno, unless you add it like so.

See the section named "Custom objects" in the docs

Yes, I see. That's why I told you to get the data according to the data type it is stored. So if you have the data stored as a DocumentReference, get it according to it. If you want to get it as a Himno, store it as a Himno in the first place and then get it accordingly.

Edit2:

Are you telling me that in can create a field of type Himno in Firestore?

Yes, you can do it. How did you add that reference in the database? In the same way you added that reference add the Himno object.

I don't understand how i can achieve this.

When you are adding data to the database by creating an object of type Oficio, set the Himno object in correct a way. Try this:

Oficio oficio = new Oficio();
Himno himno = new Himno();
himno.setTexto("Your Text");
oficio.setHimno(himno);
docRef.set(oficio);

I'm misunderstanding things about it?

Yes. To able to manage a Cloud Firestore database, you should have at least basic understanding on:

  • How to create objects in Java
  • How set object properties
  • How to add data to Cloud Firestore
  • What are collections / documents
  • Allowed data type in Firestore
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thans for your response. As @Anees sugest, i changet the type of `getTexto()` from `Spanned` to `String`, but is not working. I don't understand how i can *change the himno property to be of type Himno and not DocumentReference*. In RealTimeDatabase is possible to take only one object (`Oficio` par example) and from this object i can get references to another nested object (`Himno` par example). In this way i map only one object. – A. Cedano Feb 09 '19 at 12:25
  • Alex, May be I was wrong. But Why do you think it is incorrect? See the section named "Custom objects" in the [docs](https://firebase.google.com/docs/firestore/query-data/get-data) – Bertram Gilfoyle Feb 09 '19 at 13:00
  • @Anees It's incorrect because changing the return type of that method, will not solve the main problem at all. The main problem is produces by the way the OP is trying to cast that property, from `DocumentReference` to `Himno`. Trying to do that way, produces that error, due to incompatibility of object types. – Alex Mamo Feb 09 '19 at 20:00
  • Thanks Alex. I'm confused. Are you telling me that in can create a field of type `Himno` in Firestore? I don't understand how i can achieve this. Into the question (image 2) i show how i'm adding the data for `himno` (as a Collection with documents as 1,2,3...). I'm misunderstanding things about it? – A. Cedano Feb 10 '19 at 01:16
  • @AlexMamo of course my answer didn't solve the issue. But does that means "Custom classes in Firestore must contain" part is incorrect? Didn't you see the following in the doc ? "Important: Each custom class must have a public constructor that takes no arguments. In addition, the class must include a public getter for each property". Anyway thanks and +1 for "There is no way in Java to achieve this since there is no inheritance relationship between them.". It seems you are right. But Firestore looks less useful without it to me. – Bertram Gilfoyle Feb 10 '19 at 09:15
  • 2
    @Anees That sentence from the docs is correct **only** if you are using private fields and this should be mentioned there. If you have public fields there is no need for a constructor, getters or setters. Please see the example in the last part of my answer. A class like that will work perfectly fine. You should also try that. Thanks for the chat :) – Alex Mamo Feb 10 '19 at 09:22
  • 2
    "This should be mentioned there" Yes you are right. – Bertram Gilfoyle Feb 10 '19 at 09:24
  • Thanks Alex. Now i can understand more things, but not all. My idee is use one reference of himno as foreign key. I'm adding data to Firestore database manually (for test) and i don't see how i can in this case add data of type `Himno` ¿? Reading another questions here and into Google Forum i read that for now is not possible to do that: read the data of one subcollection when it's a reference (see [this comment and links](https://stackoverflow.com/questions/54534278/runtimeexception-could-not-deserialize-object-while-reading-nested-object-fro#comment96033110_54598525) in @Anees response). – A. Cedano Feb 11 '19 at 11:28
  • @A.Cedano You're welcome! Regarding your new questions, note that you cannot simply add in the console a new object of type `Himno` unless you map each and every property of it. You should definitely add it programmatically. So you should make your own attempt given the information in the answer/additions/comments and ask another question if something else comes up. Regarding the option to read the data of one subcollection when it's a reference it's beyond the scope of this question. So I recommend you again post a fresh question for that, so me and other Firebase developers can help you. – Alex Mamo Feb 11 '19 at 11:54
  • @AlexMamo i'm not solved this issue. I think is not possible at this moment to use one reference to retrieve the data from another document. Thanks for your help anywhere. – A. Cedano Feb 15 '19 at 13:51
  • You haven'l solved it but you have all necessary informations. I think it's a matter of time. Furthermore, if you want to get data across different collections, is not posible. Queries in Firestore are shallow, they only get items from the collection that the query is run against. – Alex Mamo Feb 15 '19 at 14:30
  • 1
    Alex i don`t know if i wrong, but if the problem is not solved, i think i can't check the question as accepted. – A. Cedano Feb 16 '19 at 10:47
4

Custom classes in Firestore must contain:

  • A public default constructor (A constructor which takes no argument)
  • Public getters (of the same type) for each property


Here is what you are missing

texto from the class Himno does not have a public getter (of the same type). Your getter returns a Spanned.


Add this to the class Himno:

public String getTexto() {
    return this.textTo;
}
Bertram Gilfoyle
  • 9,899
  • 6
  • 42
  • 67
  • Thanks. If you see the class, i have a public *getter* : **`public Spanned getTexto() { ... }`** – A. Cedano Feb 08 '19 at 19:06
  • @A.Cedano But, it needs to be the same type as the property (String in this case). Yours is Spanned. You just have to add one with String type without removing the existing. – Bertram Gilfoyle Feb 08 '19 at 19:36
  • Anees [checking the source code here](https://github.com/firebase/firebase-android-sdk/blob/master/firebase-firestore/src/main/java/com/google/firebase/firestore/util/CustomClassMapper.java#L501) i can see that if `o` is not *instance of Map* throws the same exception i'm having. Tried to put the reference into a Map but i can't map this reference to the class model `Himno`. I can see the object `com.google.firebase.firestore.DocumentReference@8103823b`but not possible have data that is into. – A. Cedano Feb 09 '19 at 14:39
  • Have you tried the `@PropertyName()` annotation with value `"himno.texto"` to texto? – Bertram Gilfoyle Feb 09 '19 at 17:56
  • Reading in [Google Groups](https://groups.google.com/forum/#!topic/google-cloud-firestore-discuss/TyOK5rao8hc) i can think that now is not possible to query into sub-collections. In more comments of [this response](https://stackoverflow.com/q/46568850/5587982) we can conclude the same thing: we can't use sub-collections as Foreign key. – A. Cedano Feb 10 '19 at 19:26