17

I'm trying to create public class MyClass<T extends Parcelable> implements Parcelable. I'm having trouble implementing Parcelable. Is it possible to create a generic class that implements Parcelable? (Note that T is bounded so that it also must implement Parcelable).

I am running into trouble with the fact that the Parcelable interface requires a static variable: public static final Parcelable.Creator<MyParcelable> CREATOR. Thus I cannot do public static final Parcelable.Creator<MyClass<T>> CREATOR because MyParcelable<T> is nonstatic.

André

Andre Gregori
  • 1,010
  • 1
  • 10
  • 17
  • Related if not duplicate: [Generic Class with Constraint access issue](http://stackoverflow.com/questions/12601489/generic-class-with-constraint-access-issue) – Paul Bellora Sep 14 '13 at 02:54
  • Thanks. The issue is the same. There was no solution offered there that worked, though. And I can't post a comment on that thread because I don't have enough "reputation"! – Andre Gregori Sep 14 '13 at 19:09
  • 1
    I browsed through your other questions and upvoted them for being very well-written. As a side-effect, you should now have enough rep to comment. – Paul Bellora Sep 15 '13 at 15:34
  • Thanks a lot Paul. That really helps!! – Andre Gregori Sep 18 '13 at 04:40

4 Answers4

22

I had similar issues with implementing Parcelable on a class with a generic, the first issue was the same as what you were experiencing:

Thus I cannot do public static final Parcelable.Creator> CREATOR because MyParcelable is nonstatic.

The second was to read in a Parcelable object you need access to the ClassLoader which cannot be gotten from T due to type erasure.

The class below is an adaption of a class I am using in production which overcomes both issues. Note: I have not tested this class specifically, so let me know if you have any issues.

public class TestModel<T extends Parcelable> implements Parcelable {

private List<T> items;
private String someField;

public List<T> items() {
    return items;
}

public void setItems(List<T> newValue) {
    items = newValue;
}

public String someField() {
    return someField;
}

public void setSomeField(String newValue) {
    someField = newValue;
}

//region: Parcelable implementation

public TestModel(Parcel in) {
    someField = in.readString();

    int size = in.readInt();
    if (size == 0) {
        items = null;
    }

    else {

        Class<?> type = (Class<?>) in.readSerializable();

        items = new ArrayList<>(size);
        in.readList(items, type.getClassLoader());
    }
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(someField);

    if (items == null || items.size() == 0)
        dest.writeInt(0);

    else {
        dest.writeInt(items.size());

        final Class<?> objectsType = items.get(0).getClass();
        dest.writeSerializable(objectsType);

        dest.writeList(items);
    }
}

public static final Parcelable.Creator<TestModel> CREATOR = new Parcelable.Creator<TestModel>() {
    public TestModel createFromParcel(Parcel in) {
        return new TestModel(in);
    }

    public TestModel[] newArray(int size) {
        return new TestModel[size];
    }
};

//endregion
}
Community
  • 1
  • 1
Bulwinkel
  • 1,606
  • 20
  • 21
  • For others knowledge here is how I did it: `protected OtherModel(Parcel in) { mTestModel = in.readParcelable(TestModel.class.getClassLoader()); }` – MidasLefko Apr 07 '16 at 09:41
  • Just wondering why you are calling both writeList(items) and iterating through the list calling item.writeToParcel(...)? Shouldn't the second parcelling be redundant? – TheIT May 13 '16 at 02:30
  • @TheIT you are right, thanks for pointing it out! Answer updated – Bulwinkel May 13 '16 at 05:45
4

Write the generic data member class name to the parcel and then read it back in order to create its class loader. Example,

public class MyClass<T> implements Parcelable {
    T data;

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(data.getClass().getName());
        dest.writeParcelable((Parcelable) data, 0);
    }

    private MyClass(Parcel in) {
        final String className = in.readString();
        try {
            data = in.readParcelable(Class.forName(className).getClassLoader());
        } catch (ClassNotFoundException e) {
            Log.e("readParcelable", className, e);
        }
    }
farid_z
  • 1,627
  • 20
  • 10
2

Yes you can. You just need to store the class name or class loader during the construction of your subclass object and then you can pass it during the read/write operation of the parcelable.

Step by step instructions:

Step 1. Store the class name that extends from your Generic class like this:

public abstract class GenericClass<T> implements Parcelable {
    private String className;

Step 2. Any classes that extends from your generic class must specify the class name during its construction like this:

public class MyClass extends GenericClass<MyClass> {

    public MyClass () {
        super();
        setClassName(MyClass.class.getName()); // Generic class setter method
    }

Step 3. In your generic class, you can then read/write your class names to getClassLoader() like this:

public abstract class GenericClass<T> implements Parcelable {
    private String className;
    T myGenericObject;

    protected MyClass (Parcel in) {
        super(in);
        this.className = in.readString();
        ClassLoader classLoader;
        try {
            classLoader = Class.forName(this.className).getClassLoader();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        myGenericObject = in.readParcelable(classLoader);
        //... Other class members can go here
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeString(className);
        //... Other class members can go here
    }

}

chaser
  • 2,837
  • 3
  • 23
  • 33
0

Based on answers above, have created extension functions for this.

fun <T : Parcelable> Parcel.writeGenericParcelable(data: T, flags: Int) {
    writeString(data::class.java.name)
    writeParcelable(data, flags)
}

fun <T : Parcelable> Parcel.readGenericParcelable(): T {
  val className = readString()!!
  val classNameLoader = Class.forName(className).classLoader
  return readParcelable(classNameLoader)!!
}
adi9090
  • 459
  • 7
  • 10