1

I'm facing an awkward problem here, I'm trying to have hierarchy parcelable classes but I'm getting this bizarre error:

java.lang.InstantiationException: Can't instantiate abstract class br.com.dinda.models.credit_cards.base.CreditCard
    at java.lang.reflect.Constructor.newInstance()(Constructor.java:-2)
    at com.google.gson.internal.ConstructorConstructor$3.construct()(ConstructorConstructor.java:104)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read()(ReflectiveTypeAdapterFactory.java:186)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read()(ReflectiveTypeAdapterFactory.java:103)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read()(ReflectiveTypeAdapterFactory.java:196)
    at com.google.gson.Gson.fromJson()(Gson.java:810)
    at com.google.gson.Gson.fromJson()(Gson.java:775)
    at com.google.gson.Gson.fromJson()(Gson.java:724)
    at com.google.gson.Gson.fromJson()(Gson.java:696)
    at com.newrelic.agent.android.instrumentation.GsonInstrumentation.fromJson()(GsonInstrumentation.java:90)
    at br.com.dinda.models.CheckoutData.fromJson()(CheckoutData.java:38)
    at br.com.dinda.repositories.CheckoutRepository.getPersistedCheckout()(CheckoutRepository.java:46)
    at br.com.dinda.repositories.CheckoutRepository.getCheckout()(CheckoutRepository.java:28)
    at br.com.dinda.presenters.CheckoutStep2Presenter.onCreate()(CheckoutStep2Presenter.java:43)
    at br.com.dinda.views.fragments.CheckoutStep2Fragment.onCreateView()(CheckoutStep2Fragment.java:183)
    at android.support.v4.app.Fragment.performCreateView()(Fragment.java:1789)
    at android.support.v4.app.FragmentManagerImpl.moveToState()(FragmentManager.java:955)
    at android.support.v4.app.FragmentManagerImpl.moveToState()(FragmentManager.java:1138)
    at android.support.v4.app.FragmentManagerImpl.moveToState()(FragmentManager.java:1120)
    at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated()(FragmentManager.java:1929)
    at android.support.v4.app.FragmentActivity.onStart()(FragmentActivity.java:547)
    at android.support.v7.app.AppCompatActivity.onStart()(AppCompatActivity.java:-1)
    at br.com.dinda.views.activities.BaseActivity.onStart()(BaseActivity.java:49)
    at android.app.Instrumentation.callActivityOnStart()(Instrumentation.java:1238)
    at android.app.Activity.performStart()(Activity.java:6288)
    at android.app.ActivityThread.performLaunchActivity()(ActivityThread.java:2397)
    at android.app.ActivityThread.handleLaunchActivity()(ActivityThread.java:2494)
    at android.app.ActivityThread.access$900()(ActivityThread.java:157)
    at android.app.ActivityThread$H.handleMessage()(ActivityThread.java:1356)
    at android.os.Handler.dispatchMessage()(Handler.java:102)
    at android.os.Looper.loop()(Looper.java:148)
    at android.app.ActivityThread.main()(ActivityThread.java:5530)
    at java.lang.reflect.Method.invoke()(Method.java:-2)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run()(ZygoteInit.java:733)
    at com.android.internal.os.ZygoteInit.main()(ZygoteInit.java:623)

My code is:

@Parcel(converter = CreditCard.CreditCardConverter.class)
public abstract class CreditCard {

    public static final String CREDIT_CARD_MASK = "**** **** **** ####";
    public static ImmutableList<CreditCard> CREDIT_CARD_OPERATORS;

    static {
        CREDIT_CARD_OPERATORS = ImmutableList.of(
                new EloCreditCard(),
                new VisaCreditCard(),
                new MastercardCreditCard(),
                new HipercardCreditCard()
        );
    }

    @SerializedName("credit_card_id")
    Integer id;

    @SerializedName("credit_card_name")
    String name;

    @SerializedName("credit_card_number")
    String number;

    @SerializedName("credit_card_month")
    Integer month;

    @SerializedName("credit_card_year")
    Integer year;

    @SerializedName("credit_card_operator")
    String operator;

    String securityCode;

    boolean saveCreditCard;

    /**
     * Funcao para retornar o nome da operadora do cartao
     * eg. visa ou mastercard
     * @return nome da operadora
     */
    @NonNull
    public abstract String getCreditCardOperatorName();

    /**
     * Funcao que retorna o regex para verificar a validade da operadora do cartao (nums sao validos para a bandeira especificada)
     * eg. visa = Pattern.compile("^4[0-9]{15}$"),
     * @return @Pattern para a operadora especificada
     */
    public abstract Pattern creditCardRegex();

    /**
     * Funcao que retorna o Regex parcial para identificar a operadora do cartao
     * * eg. visa = Pattern.compile("^4[0-9]*$")
     * @return @Pattern para a verificacao parcial da operadora do cartao
     */
    public abstract Pattern partialCreditCardRegex();

    /**
     * Funcao que retorna o logo da bandeira da operadaora do cartao quando o msm esta selecionado
     * @return Drawable do logo
     */
    @DrawableRes
    public abstract int operatorLogoRes();

    /**
     * Funcao que retorna o logo da bandeira da operadaora do cartao quando o msm <b>nao</b> esta selecionado
     * @return Drawable do logo desmarcado (cinza)
     */
    @DrawableRes
    public abstract int operatorLogoDisabledRes();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof CreditCard)) return false;

        CreditCard card = (CreditCard) o;

        return getCreditCardOperatorName().equals(card.getCreditCardOperatorName());

    }

    @Override
    public int hashCode() {
        return getCreditCardOperatorName().hashCode();
    }

    public static class CreditCardConverter implements ParcelConverter<CreditCard> {

        @Override
        public void toParcel(CreditCard input, android.os.Parcel parcel) {
            parcel.writeParcelable(Parcels.wrap(input), 0);
        }

        @Override
        public CreditCard fromParcel(android.os.Parcel parcel) {
            return Parcels.unwrap(parcel.readParcelable(CreditCard.class.getClassLoader()));
        }
    }
}

And a child class:

@Parcel
public class HipercardCreditCard extends CreditCard {

    @Override
    public String getCreditCardOperatorName() {
        return "hipercard";
    }

    @Override
    public Pattern creditCardRegex() {
        return Pattern.compile("^606282[0-9]{10}$");
    }

    @Override
    public Pattern partialCreditCardRegex() {
        return Pattern.compile("^(?:6|60|606|6062|60628|606282[0-9]{0,10})$");
    }

    @Override
    public int operatorLogoRes() {
        return R.drawable.ic_credit_card_hipercard_on;
    }

    @Override
    public int operatorLogoDisabledRes() {
        return R.drawable.ic_credit_card_hipercard_off;
    }
}

I took care of annotating them as @Parcel, but I can't seem to find out what's going on.

This issue has happened on version 1.1.5 of Parceler.

Thanks.

Leonardo
  • 3,141
  • 3
  • 31
  • 60
  • In general you can't instantiate abstract class as well as you can't instantiate interface. You have to extends the abstact class. – baudo2048 Sep 15 '16 at 16:22
  • That's the point, I'm not instantiating the abstract class, I can't understand why Parceler is calling the base class, not it's children. – Leonardo Sep 15 '16 at 16:43
  • The provided stack trace seems to point at Gson having trouble instantiating your abstract class. Answered here on Github as well: https://github.com/johncarl81/parceler/issues/237 – John Ericksen Sep 16 '16 at 00:54
  • I see, but that doesn't solve my problem anyway. I'm still trying to figure out what's going on. – Leonardo Sep 16 '16 at 13:05

2 Answers2

2

Actually I forgot to add in my serializer the CustomTypeAdapter,

if (json != null) {
            Gson gson = new Gson();
            return gson.fromJson(json, CheckoutData.class);

Should be:

if (json != null) {
            Gson gson = new GsonBuilder().registerTypeAdapter(CreditCard.class, new CreditCardTypeAdapter()).create();
            return gson.fromJson(json, CheckoutData.class);

Sorry to bother and hope it helps someone else !

Leonardo
  • 3,141
  • 3
  • 31
  • 60
1

I believe you're running into this problem:

Polymorphism

Note that Parceler does not unwrap inheritance hierarchies, so any polymorphic fields will be unwrapped as instances of the base class. This is because Parceler opts for performance rather than checking .getClass() for every piece of data.

 @Parcel public class Example {
     public Parent p;
     @ParcelConstructor Example(Parent p) { this.p = p; } }

 @Parcel public class Parent {} @Parcel public class Child extends
 Parent {} Example example = new Example(new Child());
 System.out.println("%b", example.p instanceof Child); // true example
 = Parcels.unwrap(Parcels.wrap(example)); System.out.println("%b", example.p instanceof Child); // false 

Refer to the Custom Serialization section for an example of working with polymorphic fields.

So you should be able to use custom serialization to get around this.

https://github.com/johncarl81/parceler#custom-serialization

nasch
  • 5,330
  • 6
  • 31
  • 52