Abstract class as parcelable
Asked Answered
C

5

19

Basicly I have the following structure in my app:

uml of my app

It would be straightforward to implement such a structure without the abstract class ProjectItem, but in this case I don't know how to implement this.

The abstract class ProjectItem needs a CREATOR as it should be parcelable. (like in.readTypedList(mProjectItems, ProjectItem.CREATOR); within the constructor Project(Parcel in))

But in fact, the CREATOR can only be implemented in its derived classes for logical reasons.

So, how to implement this structure in order to keep the class Project parcelable??

Edit

This is what one of the constructors of Project looks like:

private Project(Parcel in) {
    in.readTypedList(mProjectItems, ProjectItem.CREATOR);
}

But as I already said, ProjectItem shouldn't have to implement a CREATOR

Cleres answered 22/3, 2014 at 11:0 Comment(0)
H
3

The selected answer (from evertvandenbruel's post) has a bug in it. The correct code must account for parceling when just one of the subclasses is being parceled, not just a list of the superclass objects.

All the other code should be the same, the key is that you MUST read in the type variable in ALL creators (see code below). Otherwise there will be issues with the ordering when trying to unparcel a subclass object

Ex:

package com.example.parcelable_example.model;

import android.os.Parcel;
import android.os.Parcelable;

public class Cat extends Animal{

    public Cat(String name){
        super(name, "Cat");
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(getType());
        super.writeToParcel(dest, flags);
    }

    public Cat(Parcel source) {
        super(source);      
    }

    public static final Parcelable.Creator<Cat> CREATOR = new Parcelable.Creator<Cat>() {
        public Cat createFromParcel(Parcel in) {
            /** DO NOT FORGET THIS!!! **/
            type = in.readString();
            return new Cat(in);
        }

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

}
Hunkydory answered 1/2, 2016 at 22:32 Comment(5)
Why wouldn't you put that line in the Parcel constructor of Animal?Schroth
For this scenario: I need to parcel a list of Cat, Dog, etc (subclasses of Animal) as an array list with type "Animal." In this case, Animal's CREATOR will be used with the data from the writeToParcel, and in.readString() is used to determine the type so that the correct subclass can be instantiated using its parcel constructor, which should call to Animal's parcel constructor using super(in). If you read in the type in the Animal constructor, you've then got a broken implementation -- the type has already been read in order to create the Dog object instead of the Cat object.Hunkydory
Why? You call new Cat(in) that calls super(source) than the constructor of Animal reads the type. Why should it call the Dogs constructor?: Edit I see: it will be read twice when using the Animal CREATOR and than the parcel is ruined. But what you do here is inpossible because of what do you set the type?Schroth
I solded this by adding a boolean parameter to the parcel constructor with named readObjectType which is false when using the absract CREATOR and true with concrete CREATORs.Schroth
Come again? You should not need anything else but the source provided. No need for a boolean parameter. You ALWAYS read the object type regardless of which CREATOR is used. The key being that only one CREATOR will be used in the parcelable implementation, but all of them still have to read the type (because all of them write the type due to the abstract superclass' writeToParcel() implementation).Hunkydory
R
10

My solution is similar to evertvandenbruel's. But I identify the concrete class using an int so that I can use a switch block. I also have that switch block in a static getConcreteClass(Parcel) method.

AbstractClass.java

public abstract class AbstractClass implements Parcelable {

public static final int CLASS_TYPE_ONE = 1;
public static final int CLASS_TYPE_TWO = 2;

public static final Creator<AbstractClass> CREATOR = new Creator<AbstractClass>() {
    @Override
    public AbstractClass createFromParcel(Parcel source) {

        return AbstractClass.getConcreteClass(source);
    }

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

protected String mAbstractClassString;

public AbstractClass(String abstractClassString) {
    mAbstractClassString = abstractClassString;
}

public AbstractClass(Parcel source) {
    mAbstractClassString = source.readString();
}

public static AbstractClass getConcreteClass(Parcel source) {

    switch (source.readInt()) {
        case CLASS_TYPE_ONE:
            return new ConcreteClassOne(source);
        case CLASS_TYPE_TWO:
            return new ConcreteClassTwo(source);
        default:
            return null;
    }
}

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

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

@Override
public String toString() {
    return "Parent String: " + mAbstractClassString + '\n';
}
}

ConcreteClassOne.java

public class ConcreteClassOne extends AbstractClass {

private String mString;

public ConcreteClassOne(String abstractClassMemberString, String string) {
    super(abstractClassMemberString);

    mString = string;
}

public ConcreteClassOne(Parcel source) {
    super(source);
    mString = source.readString();
}

@Override
public void writeToParcel(Parcel dest, int flags) {

    dest.writeInt(CLASS_TYPE_ONE);
    super.writeToParcel(dest, flags);
    dest.writeString(mString);
}

@Override
public String toString() {
    return super.toString().concat("Child String: " + mString);
}
}

ConcreteClassTwo.java

public class ConcreteClassTwo extends AbstractClass {

private String mString;
private int mInt;

public ConcreteClassTwo(String abstractClassString, String string, int anInt) {
    super(abstractClassString);
    mString = string;
    mInt = anInt;
}

public ConcreteClassTwo(Parcel source) {
    super(source);
    mString = source.readString();
    mInt = source.readInt();
}

@Override
public void writeToParcel(Parcel dest, int flags) {

    dest.writeInt(CLASS_TYPE_TWO);
    super.writeToParcel(dest, flags);
    dest.writeString(mString);
    dest.writeInt(mInt);
}

@Override
public String toString() {

    String string = super.toString();
    for (int i = 0; i < mInt; i++) {
        string = string.concat("Child String: " + mString + '\n');
    }
    return string;
}
}
Revert answered 6/10, 2014 at 14:8 Comment(1)
Just a note: as of Java 7, String types are now supported in switch statements. For pre-Java 7 users, the optimization of using an int for readability isn't a bad one at all.Hunkydory
H
3

The selected answer (from evertvandenbruel's post) has a bug in it. The correct code must account for parceling when just one of the subclasses is being parceled, not just a list of the superclass objects.

All the other code should be the same, the key is that you MUST read in the type variable in ALL creators (see code below). Otherwise there will be issues with the ordering when trying to unparcel a subclass object

Ex:

package com.example.parcelable_example.model;

import android.os.Parcel;
import android.os.Parcelable;

public class Cat extends Animal{

    public Cat(String name){
        super(name, "Cat");
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(getType());
        super.writeToParcel(dest, flags);
    }

    public Cat(Parcel source) {
        super(source);      
    }

    public static final Parcelable.Creator<Cat> CREATOR = new Parcelable.Creator<Cat>() {
        public Cat createFromParcel(Parcel in) {
            /** DO NOT FORGET THIS!!! **/
            type = in.readString();
            return new Cat(in);
        }

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

}
Hunkydory answered 1/2, 2016 at 22:32 Comment(5)
Why wouldn't you put that line in the Parcel constructor of Animal?Schroth
For this scenario: I need to parcel a list of Cat, Dog, etc (subclasses of Animal) as an array list with type "Animal." In this case, Animal's CREATOR will be used with the data from the writeToParcel, and in.readString() is used to determine the type so that the correct subclass can be instantiated using its parcel constructor, which should call to Animal's parcel constructor using super(in). If you read in the type in the Animal constructor, you've then got a broken implementation -- the type has already been read in order to create the Dog object instead of the Cat object.Hunkydory
Why? You call new Cat(in) that calls super(source) than the constructor of Animal reads the type. Why should it call the Dogs constructor?: Edit I see: it will be read twice when using the Animal CREATOR and than the parcel is ruined. But what you do here is inpossible because of what do you set the type?Schroth
I solded this by adding a boolean parameter to the parcel constructor with named readObjectType which is false when using the absract CREATOR and true with concrete CREATORs.Schroth
Come again? You should not need anything else but the source provided. No need for a boolean parameter. You ALWAYS read the object type regardless of which CREATOR is used. The key being that only one CREATOR will be used in the parcelable implementation, but all of them still have to read the type (because all of them write the type due to the abstract superclass' writeToParcel() implementation).Hunkydory
A
2

This question arises from a false assumption.


Here is a quote from the original post.

The abstract class ProjectItem needs a CREATOR as it should be parcelable.

In fact, It is not necessary for the super class to define CREATOR since it is abstract.


Here is a minimal example which demonstrates the method.

/*   Super class   */

abstract class SuperClass
        implements Parcelable {

    protected SuperClass(Parcel in) {
        mSuperId = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(mSuperId);
    }

}



/*   Sub class   */

public class SubClass
        extends SuperClass {

    protected SubClass(Parcel in) {
        super(in);
        mSubId = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeLong(mSubId);
    }

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

    public static final Creator<SubClass> CREATOR = new Creator<SubClass>() {

        @Override
        public SubClass createFromParcel(Parcel in) {
            return new SubClass(in);
        }

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

    };

}



/*   Usage   */

class AnotherClass {

    void aMethod() {
        Bundle args = new Bundle();
        args.putParcelable("EXTRA_SUPER_CLASS", subClassObject);
    }

}
Almanac answered 20/7, 2018 at 11:17 Comment(1)
In some cases you need parent class to implement CREATOR e.g. passing an abstract class in AIDLCannady
G
0
public abstract class A implements Parcelable {
    private int a;

    protected A(int a) {
        this.a = a;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(a);
    }

    protected A(Parcel in) {
        a = in.readInt();
    }
}

public class B extends A {
    private int b;

    public B(int a, int b) {
        super(a);
        this.b = b;
    }

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

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

    public int describeContents() {
        return 0;
    }
}
Gaulin answered 22/3, 2014 at 11:13 Comment(1)
Unfortunatly this doesn't help very much, as my current structure requires to implement a CREATOR in the parent-classCleres
E
0

Since the introduction of @Parcelize. You can annotate the derived classes with @Parcelize and have them implement Parcelable. Then have your abstract class implement Parcelable. The derived classes will implement the Parcelable interface methods through the @Parcelize anottation.

abstract class News(open val title: String, open val content: String): Parcelable

@Parcelize
class MorningNews(override val title: String, override val content) :
    News(title, content), Parcelable

@Parcelize
class EveningNews(override val title: String, override val content: String,
) : NewsLetter(title, content), Parcelable
Enrobe answered 25/7 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.