0

How to write Parcelable for a tree like structure that has a Base class with two implementations, one a Node type and the other a Container Type which contains a list of Base types.

Sorry for the mass of code below. The basic data structure is small but I have in included my current Parcelable implementation which is not working.

I have tried adding a byte in the base writeToParcel which signals which type to create when unparceling but the does not seem to work.

The problem is I am getting different data back when I parcel a Base object that contains a deep tree of Container objects.

Any ideas on what I am doing wrong?

Also any ideas on how to implemented unit tests for Parcelables would be appreciated.

Base Class

public abstract class Base implements Parcelable {
    public int val;
    protected Base(final int val) {
        this.val = val;
    }


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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeByte((byte) (this instanceof NodeBase ? 0 : 1));
        dest.writeInt(this.val);
    }

    protected Base(Parcel in) {
        this.val = in.readInt();
    }

    public static final Creator<Base> CREATOR = new Creator<Base>() {
        @Override
        public Base createFromParcel(Parcel source) {
            return source.readByte() == 0 ? new NodeBase(source) : new ContainerBase(source);
        }

        @Override
        public Base[] newArray(int size) {
            return new Base[size];
        }
    };
}

Node Class

public class NodeBase extends Base {

    public final String name;

    public NodeBase(final int val, final String name) {
        super(val);
        this.name = name;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeString(this.name);
    }

    protected NodeBase(Parcel in) {
        super(in);
        this.name = in.readString();
    }

    public static final Creator<NodeBase> CREATOR = new Creator<NodeBase>() {
        @Override
        public NodeBase createFromParcel(Parcel source) {
            return new NodeBase(source);
        }

        @Override
        public NodeBase[] newArray(int size) {
            return new NodeBase[size];
        }
    };
}

Container Class

public class ContainerBase extends Base {
    public final  List<Base> baseList;

    public ContainerBase(final int val, final List<Base> baseList) {
        super(val);
        this.baseList = baseList;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeTypedList(this.baseList);
        dest.writeInt(this.val);
    }

    protected ContainerBase(Parcel in) {
        super(in);
        this.baseList = in.createTypedArrayList(Base.CREATOR);
        this.val = in.readInt();
    }

    public static final Creator<ContainerBase> CREATOR = new Creator<ContainerBase>() {
        @Override
        public ContainerBase createFromParcel(Parcel source) {
            return new ContainerBase(source);
        }

        @Override
        public ContainerBase[] newArray(int size) {
            return new ContainerBase[size];
        }
    };
}
fergdev
  • 963
  • 1
  • 13
  • 27

1 Answers1

0

You mentioned Generic Types, but I do not see any Generic Types being used in your code. All I see are Parent and Child inherited classes. But assuming that your Node and Container are generic and that they have a generic parameter T, where T can be any object that can be stored in your Node/Container, then the following solution should help:

Step 1. Store the classes that extends from your Generic classes as a string in your base class:

public abstract class Base implements Parcelable {
    private String containerClassName;
    private String nodeClassName;

Step 2. Make sure that your generic classes call super(), which you have already done.

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

public class NewNode extends NodeBase {

    public NodeBase() {
        super();
        setClassNames(NewNode.class.getName()); // Base class setter method
    }

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

protected NodeBase(Parcel in) {
    super(in);
    this.containerClassName = in.readString();
    this.nodeClassName = in.readString();

    ClassLoader containerLoader;
    ClassLoader nodeLoader;
    try {
        containerLoader = Class.forName(containerClassName).getClassLoader();
        nodeLoader = Class.forName(nodeClassName).getClassLoader();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    containerObject = in.readParcelable(containerLoader);
    nodeObject = in.readParcelable(nodeLoader);
    //... Other class members can go here
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    super.writeToParcel(dest, flags);
    dest.writeString(containerClassName);
    dest.writeString(nodeClassName);
    //... Other class members can go here
}
chaser
  • 3,107
  • 4
  • 29
  • 34