Is it possible to parcel a generic class?

9.7k views Asked by At

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é

4

There are 4 answers

3
Bulwinkel On

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
}
0
adi9090 On

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)!!
}
1
farid_z On

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);
        }
    }
1
chaser On

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
    }

}