Abstract class as parcelable - java

Basicly I have the following structure in 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

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;
}
}

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];
}
};
}

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);
}
}

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;
}
}

Related

How to pass a Parcelable class which contains a list of another parcelable class?

I'm trying to pass a Parcelable class from one Activity to another. I do it like this:
Intent intent = new Intent(ClosedChatActivity.this, AdminProfileActivity.class);intent.putExtra("adminProfile", adminProfile);
startActivity(intent);
And then get it in the other Activity like this:
adminProfile = (AdminProfile) getIntent().getExtras().getParcelable("adminProfile");
This is the AdminProfile class and the WebLink class it has inside:
public class AdminProfile implements Parcelable {
public static final Creator<AdminProfile> CREATOR = new Creator<AdminProfile>() {
#Override
public AdminProfile createFromParcel(Parcel in) {
return new AdminProfile(in);
}
#Override
public AdminProfile[] newArray(int size) {
return new AdminProfile[size];
}
};
public Long idUser;
public String name;
public String professio;
public String description;
public List<WebLink> webLinks;
public Long idOficina;
protected AdminProfile(Parcel in) {
idUser = in.readLong();
name = in.readString();
professio = in.readString();
description = in.readString();
webLinks = in.createTypedArrayList(WebLink.CREATOR);
idOficina = in.readLong();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(idUser);
parcel.writeString(name);
parcel.writeString(professio);
parcel.writeString(description);
parcel.writeLong(idOficina);
parcel.writeTypedList(webLinks);
}
}
public class WebLink implements Parcelable {
public static final Creator<WebLink> CREATOR = new Creator<WebLink>() {
#Override
public WebLink createFromParcel(Parcel in) {
return new WebLink(in);
}
#Override
public WebLink[] newArray(int size) {
return new WebLink[size];
}
};
public String name;
public String url;
protected WebLink(Parcel in) {
name = in.readString();
url = in.readString();
}
#Override
public boolean equals(#Nullable Object obj) {
WebLink webLink = (WebLink) obj;
assert webLink != null;
return this.name.equals(webLink.name) && this.url.equals(webLink.url);
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeString(url);
}
}
When I get the object, the two Strings in it aren't the same I sent. Why is that?
The weird thing is that I have another Parcelable class which contains an AdminProfile, and when sending that class it does send fine with the Weblinks included, but if I send only an AdminProfile somewhere in the way it fails.
When you read and write Parcelable classes, you need to make sure that the order of the elements is exactly the same.
You are writing idOficina followed by WebLinks, but you are reading them in the opposite order.

Problem to read an ArrayList or List of a parcelable class

I have this parcelable class:
public class EventInviteWorkingHours implements Parcelable {
int dayOfWeek;
String begin;
String finish;
public EventInviteWorkingHours() {
}
protected EventInviteWorkingHours(Parcel in) {
dayOfWeek = in.readInt();
begin = in.readString();
finish = in.readString();
}
public static final Creator<EventInviteWorkingHours> CREATOR = new Creator<EventInviteWorkingHours>() {
#Override
public EventInviteWorkingHours createFromParcel(Parcel in) {
return new EventInviteWorkingHours(in);
}
#Override
public EventInviteWorkingHours[] newArray(int size) {
return new EventInviteWorkingHours[size];
}
};
public int getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
public String getBegin() {
return begin;
}
public void setBegin(String begin) {
this.begin = begin;
}
public String getFinish() {
return finish;
}
public void setFinish(String finish) {
this.finish = finish;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(dayOfWeek);
dest.writeString(begin);
dest.writeString(finish);
}
}
and in another class that implements parcelable and has this variable:
List<EventInviteWorkingHours> workingHours;
i write and read this variable like that:
dest.writeTypedList(workingHours);
in.readTypedList(workingHours, EventInviteWorkingHours.CREATOR);
But, when I get the item by intent that contains this variable, this error appears:
java.lang.RuntimeException: Parcel android.os.Parcel#d7fbbfb: Clearing the unknown type code 7274612 at offset 788
Somebody knows how to fix it? I already have tried with ArrayList and write and read for differents ways, but never works
I haven`t find any solution with parcelable, but i used serializable and it works very well:
The first parcelable class is now serializable class:
public class EventInviteWorkingHours implements Serializable {
int dayOfWeek;
String begin;
String finish;
public EventInviteWorkingHours() {
}
public int getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
public String getBegin() {
return begin;
}
public void setBegin(String begin) {
this.begin = begin;
}
public String getFinish() {
return finish;
}
public void setFinish(String finish) {
this.finish = finish;
}
}
And in another class that implements parcelable and has this variable:
List<EventInviteWorkingHours> workingHours;
Will read and write that variable like that:
workingHours = (List<EventInviteWorkingHours>) in.readSerializable();
dest.writeSerializable((Serializable) workingHours);

Android abstract parcelable class

Problem Description
I have Category class which implements Parcelable, also I have few more classes which are extended from the Category class. My base class has 2 protected members title and id which are set mainly from the inherited classes. So in order not to implement Parcelable related stuff everywhere in the inherited classes I decide to do it in a base class and let it handle all actions.
Question
The problem is that I can't have constructor of Category class as it is abstract class. So what is the solution? As I have abstract methods in the class I can't remove abstract modifier.
Source code
public abstract class Category implements Parcelable {
private static Map<Integer, Category> categoryMap = new TreeMap<Integer, Category>();
protected Sting title;
protected Integer id;
static {
categoryMap.put(0, new Taxi());
categoryMap.put(1, new Hotel());
}
private Category(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInteger(id);
dest.writeString(title);
}
public static final Parcelable.Creator<Category > CREATOR = new Parcelable.Creator<Category >() {
public Category createFromParcel(Parcel in) {
return new Category (in); <=== !!! THIS IS NOT ALLOWED AS CLASS IS ABSTRACT !!!
}
public Category [] newArray(int size) {
return new Category[size];
}
};
abstract void generateCodes();
abstract String getImageIcon();
};
public final class Taxi extends Category {
public Taxi() {
title = "taxi";
id = 1547845;
}
};
public final class Hotel extends Category {
public Hotel() {
title = "hotel";
id = 1397866;
}
};
It is possible to have constructors in abstract classes, and you can do the parceling there - just make sure you call the respective super(...) method in the child classes.
Check this out.
EDIT:
I think Category does not need to implement CREATOR as you cannot instantiate it...? Similar suggestions are here and here
public abstract class Category implements Parcelable {
private static Map<Integer, Category> categoryMap = new TreeMap<Integer, Category>();
protected String title;
protected Integer id;
static {
categoryMap.put(0, new Taxi());
categoryMap.put(1, new Hotel());
}
protected Category(){}
protected Category(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInteger(id);
dest.writeString(title);
}
abstract void generateCodes();
abstract String getImageIcon();
};
public final class Taxi extends Category {
public Taxi() {
title = "taxi";
id = 1547845;
}
protected Taxi(Parcel in) {
super(in);
}
public static final Parcelable.Creator<Taxi> CREATOR = new Parcelable.Creator<Taxi>() {
public Category createFromParcel(Parcel in) {
return new Taxi (in);
}
public Category [] newArray(int size) {
return new Taxi[size];
}
};
};
public final class Hotel extends Category {
public Hotel() {
title = "hotel";
id = 1397866;
}
protected Hotel(Parcel in) {
super(in);
}
public static final Parcelable.Creator<Hotel> CREATOR = new Parcelable.Creator<Hotel>() {
public Category createFromParcel(Parcel in) {
return new Hotel (in);
}
public Category [] newArray(int size) {
return new Hotel[size];
}
};
};

Obfuscate Parcelable classes with proguard

I'm trying to obfuscate a parcelable class with Proguard:
Before adding the Parcelable part the class is:
public class Foo{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
The obfuscated result is:
public class a
{
private String a;
public String a()
{
return this.a;
}
public void a(String paramString)
{
this.a = paramString;
}
}
After adding implementing parcelable the example class is
public class Foo implements Parcelable {
private String value;
private Foo(Parcel in) {
value = in.readString();
}
public Foo() {
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(value);
}
public static final Parcelable.Creator<Foo> CREATOR
= new Parcelable.Creator<Foo>() {
public Foo createFromParcel(Parcel in) {
return new Foo(in);
}
public Foo[] newArray(int size) {
return new Foo[size];
}
};
}
The obfuscated result is
public class Foo implements Parcelable {
public static final Parcelable.Creator CREATOR = new a();
private String a;
public Foo() {
}
private Foo(Parcel paramParcel) {
this.a = paramParcel.readString();
}
public String a() {
return this.a;
}
public void a(String paramString) {
this.a = paramString;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel paramParcel, int paramInt) {
paramParcel.writeString(this.a);
}
}
class a implements Parcelable.Creator {
public Foo a(Parcel paramParcel) {
return new Foo(paramParcel, null);
}
public Foo[] a(int paramInt) {
return new Foo[paramInt];
}
}
How can I configure proguard for obfuscate the whole class (including name, params and methods) except the parcelable part?
Thanks
Try putting this in your proguard.cfg file:
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
That should preserve Parcelable part and obfuscate everything else.

Parcelable and inheritance in Android

I got an implementation of Parcelable working for a single class that involves no inheritance. I have problems figuring out the best way to implement the interface when it come to inheritance. Let's say I got this :
public abstract class A {
private int a;
protected A(int a) { this.a = a; }
}
public class B extends A {
private int b;
public B(int a, int b) { super(a); this.b = b; }
}
Question is, which is the recommended way to implement the Parcelable interface for B (in A? in both of them? How?)
Here is my best solution, I would be happy to hear from somebody that had a thought about it.
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;
}
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(b);
}
private B(Parcel in) {
super(in);
b = in.readInt();
}
}
This is my variant. I think it's nice because it shows the symmetry between the virtual read- and write- methods very clearly.
Side note: I think Google did a really poor job at designing the Parcelable interface.
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);
}
public void readFromParcel(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;
}
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(b);
}
public void readFromParcel(Parcel in) {
super(in);
b = in.readInt();
}
}
Here is the implementation for class A in a real world setting since class B will likely have more than one object with different types other than int
It uses reflection to get the types. Then uses a sorting function to sort the fields so that reading and writing happen in the same order.
https://github.com/awadalaa/Android-Global-Parcelable

Categories