1

I am using AutoValue to for things like getter/setter and Parcelable.

I am storing the JSON value of objects in SharedPreferences and then retrieving them when needed. However, this throws an exception when I try to convert the JSON array stored as String in SharedPreferences to ArrayList<T> since I am using AutoValue which does not allow to use the Constructor directly.

Here is the exception that I am getting -

java.lang.RuntimeException: Failed to invoke public org.yadavvi.tutorials.governor.data.Governor() with no args
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:111)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:206)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at com.google.gson.Gson.fromJson(Gson.java:879)
at com.google.gson.Gson.fromJson(Gson.java:844)
at com.google.gson.Gson.fromJson(Gson.java:793)
at org.yadavvi.tutorials.governor.data.source.local.GovernorLocalDataSource.getGovernors(GovernorLocalDataSource.java:60)
at org.yadavvi.tutorials.governor.data.source.local.GovernorLocalDataSourceTest.getGovernors_callsOnSuccessOfGetGovernorsCallback(GovernorLocalDataSourceTest.java:69)

....

Caused by: java.lang.InstantiationException: Can't instantiate abstract class org.yadavvi.tutorials.governor.data.Governor
at java.lang.reflect.Constructor.newInstance(Native Method)
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:108)
... 41 more

Here is how the Model/AutoValue class looks like -

package org.yadavvi.tutorials.governor.data;


import com.google.auto.value.AutoValue;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.SerializedName;

/**
 * Immutable Governor created using AutoValue.
 */
@AutoValue
public abstract class Governor {

    public static Governor create(String name) {
        return new AutoValue_Governor(name);
    }

    @SerializedName("name")
    abstract String name();
}

and here is how the class where I try to convert the JSON array string to ArrayList<T> looks like -

package org.yadavvi.tutorials.governor.data.source.local;


import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.util.Log;

import org.yadavvi.tutorials.governor.data.Governor;
import org.yadavvi.tutorials.governor.data.source.GovernorDataSource;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class GovernorLocalDataSource implements GovernorDataSource {

    ....

    @Override
    public void getGovernors(@NonNull GetGovernorsCallback callback) {
        Gson gson = new GsonBuilder().create();
        List<Governor> governors;

        String governorsString = mSharedPreference.getString(GOVERNORS, "");
        Log.i(TAG, "governorsString: " + governorsString);
        if (governorsString.equalsIgnoreCase("")) {
            governors = populateGovernors();
            governorsString = gson.toJson(governors);
            mSharedPreference.edit().putString(GOVERNORS, governorsString).commit();
        } else {
            governors = gson.fromJson(governorsString, new TypeToken<ArrayList<Governor>>(){}.getType());
        }

        if (governors != null && governors.size() > 0) {
            callback.onSuccess(governors);
        } else {
            callback.onFailure();
        }
    }

    ....

}

The exception is thrown at this line -

governors = gson.fromJson(governorsString, new TypeToken<ArrayList<Governor>>(){}.getType());

EDIT: I have some AutoValue-Extensions in my Android project.

The app build.gradle looks like this -

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        applicationId "org.yadavvi.tutorials.governor"
        minSdkVersion 21
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.2.5'
    testCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    androidTestCompile 'org.mockito:mockito-core:2.2.5'

    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.google.auto.value:auto-value:1.2'
    compile 'com.ryanharter.auto.value:auto-value-gson:0.4.3'
}
yadav_vi
  • 1,289
  • 4
  • 16
  • 46

2 Answers2

1

I have added a custom TypeAdapterFactory to the GsonBuilder and it seems to work fine.

The TypeAdapterFactory looks something like this -

package org.yadavvi.tutorials.governor.data;

import com.google.gson.typeadapterfactory;
import com.ryanharter.auto.value.gson.gsontypeadapterfactory;

@GsonTypeAdapterFactory
public abstract class GovernorAdapterFactory implements TypeAdapterFactory {

    public static GovernorAdapterFactory create() {
        return new AutoValueGson_GovernorAdapterFactory();
    }

}

The GovernorLocalDataSource is changed like so -

public class GovernorLocalDataSource implements GovernorDataSource {

    @Override
    public void getGovernors(@NonNull GetGovernorsCallback callback) {
       Gson gson = new GsonBuilder()
               .registerTypeAdapterFactory(GovernorAdapterFactory.create())
               .create();
       List<Governor> governors;

       String governorsString = mSharedPreference.getString(GOVERNORS, EMPTY_STRING);
       if (governorsString.isEmpty()) {
           governors = populateGovernors();
           governorsString = gson.toJson(governors);
           mSharedPreference.edit().putString(GOVERNORS, governorsString).commit();
       } else {
           Type type = new TypeToken<ArrayList<Governor>>() {}.getType();
           governors = gson.fromJson(governorsString, type);
       }

       if (governors != null && governors.size() > 0) {
           callback.onSuccess(governors);
       } else {
           callback.onFailure();
       }

    }

}

PS: It would be great if someone could explain WHY this works.

yadav_vi
  • 1,289
  • 4
  • 16
  • 46
  • 1
    This is the way to go per the doc of the Gson Auto Value extension : you need to specify an AdapterFactory to Gson. Otherwise, it tries to create the Governor object by reflection which it can't since it's abstract. The adapter factory created by the extension tells him to use the AutoValue implementation of Governor instead of the base class. – Julien Arzul Oct 19 '16 at 17:38
  • @JulienArzul Thanks! I read an [article](http://federico.defaveri.org/2013/07/07/gson-custom-serialization-falling-in-default-serialization/) which tries to explain `TypeAdapter` and `TypeAdapterFactory` and I think I get it now. I also saw the generated code from `AutoValue` and it does a lot of stuff, like skipping the `next value` when the `jsonReader.nextName()` is not `name`. – yadav_vi Oct 19 '16 at 18:24
0

This below link might help, basically instead of abstract class you need to use concrete class.

Gson deserialize json. java.lang.RuntimeException: Failed to invoke public com.derp.procedure.model.SkeletonElement() with no args] with root cause

Community
  • 1
  • 1
Learner_Programmer
  • 1,259
  • 1
  • 13
  • 38
  • 1
    I have already looked at that, however, I am using `AutoValue`, so I have no choice but to use `abstract class`. – yadav_vi Oct 19 '16 at 11:53