Search code examples
javaandroidfirebasefirebase-realtime-databaseauto-value

How to use auto-value with firebase 9.2 in Android


I want to use auto-value with firebase 9.2.0+. I have the following code:

@AutoValue
public abstract class Office {

    public static Builder builder() {
        return new AutoValue_Office.Builder();
    }

    public abstract double latitud();
    public abstract double longitud();

    @AutoValue.Builder
    public static abstract class Builder {

        public abstract Builder latitud(double latitud);
        public abstract Builder longitud(double longitud);

        public abstract Office build();

    }

}

But when I make to call this Office office = childDataSnapshot.getValue(Office.class); I am getting this error:

com.google.firebase.database.DatabaseException: No properties to serialize found on class com.example.app.model.Office

Somebody have an idea why I am getting this error and how to solve it? I read that firebase is no longer using jackson for json serialization. So I am not sure how to specify a kind of @JsonProperty("latitud") I have used @PropertyName unsuccessfully.

I also tried rename the abstract methods like public abstract double getLatitud(); and after that the error is the next one:

java.lang.InstantiationException: Can't instantiate abstract class com.example.app.model.Office

So I am not sure how to solve this.

SOLUTION

Thanks to hatboysam and Frank van Puffelen I finally could face this problem with the next solution.

  1. I created a FirebaseUtil enum with two methods for serialize and deserialize objects for Firebase based on hatboysam answer and Frank van Puffelen comment.
  2. I create a couple of User and Phone classes for testing.
  3. Dependencies:

    compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.0'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.8.0'
    
  4. Usage example:

    User user = FirebaseUtil.deserialize(dataSnapshot, User.class);
    Map<String, Object> map = FirebaseUtil.serialize(user);
    

Solution

  • I'm not sure this is possible with the default Firebase data mapper, but there is a possible workaround. First let's explain the errors you're seeing:

    com.google.firebase.database.DatabaseException: No properties to serialize found on class com.example.app.model.Office

    The Firebase mapper looks for either public fields or fields named with the getFoo/setFoo pattern. So on your class the mapper does not see any properties.

    java.lang.InstantiationException: Can't instantiate abstract class com.example.app.model.Office

    This is the one I think you'll have trouble getting around. In order for the deserialization to work your class needs to have a public, no-argument constructor that the mapper can call via reflection (newInstance()). As far as I know this is not how AutoValue works.

    But don't lose hope!. According to this github issue there is a way to make Jackson and AutoValue compatible using the @JsonCreator annotation. So you'll need to use both Jackson and Firebase to get the job done here.

    Serializing:

    // Convert to a Map<String,Object> using Jackson and then pass that to Firebase
    ObjectMapper mapper = new ObjectMapper();
    Map<String, Object> map = mapper.convertValue(office, Map.class);
    databaseReference.setValue(map);
    

    Deserializing:

    // Use Firebase to convert to a Map<String,Object>
    GenericTypeIndicator<Map<String,Object>> t = new GenericTypeIndicator<Map<String,Object>>() {};
    Map<String,Object> map = dataSnap.getValue(t);
    
    // Use Jackson to convert from a Map to an Office object
    ObjectMapper mapper = new ObjectMapper();
    Office pojo = mapper.convertValue(map, Office.class);