I'm trying to pass a list of images to another activity via a Parcelable object in Android. I can convert the images down to a byte[] array fine.
However, I've got a list of these images so there's going to be more than one byte[] array.
Here is my code so far.
public class InvalidItem implements Parcelable {
public String id;
public ArrayList<byte[]> imageList = new ArrayList<>();
public InvalidItem(String id) {
this.id = id;
}
public InvalidItem(Parcel in) {
String[] data = new String[22];
in.readStringArray(data);
this.id= data[0];
this.imageList = data[1];
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(new String[]{
this.id,
String.valueOf(this.imageList)
});
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public InvalidItem createFromParcel(Parcel in) {
return new InvalidItem(in);
}
public InvalidItem[] newArray(int size) {
return new InvalidItem[size];
}
};
}
As you can see I write the string array which would work for one byte[] array. Is there a way I could have a list of byte[] arrays that I decode into one string and then encode when I want to show the images?
I've tried looking into solutions and people have suggested passing it in the bundle.
However, this application follows a step by step process and requires an object to store the data in.
Related
I am trying to make a list of objects that are all of an abstract class, but each are there own class. This list needs to persistent so I figured I implement parcelable since I have done so in the past. Only not with different classes all of an abstract class.
I tried just making the abstract class parcelable but that can't have a creator that I am used to because (of course) you can't create an instance of it (because it is abstract). Reading around I noticed that people said you dont need a constructor in the abstract class, just in the subclasses.
AbstractFocusPower class
public abstract class AbstractFocusPower implements Parcelable {
private transient AppExtension app;
private ImplementSchool school;
private String name;
private int duration;
private int cost;
private int altCost;
private int requiredLevel;
private boolean isSelected;
private boolean isResonant;
private int nofSpirtBonusUsed;
/**
* Constructor for Focus Power with no alternative cost
*/
public AbstractFocusPower(AppExtension app, ImplementSchool school, String name, int requiredLevel, int duration, int cost, boolean isSelected) {
this.app = app;
this.school = school;
this.name = name;
this.requiredLevel = requiredLevel;
this.duration = duration;
this.cost = cost;
this.altCost = -1;
this.isSelected = isSelected;
this.isResonant = false;
}
// I cut out the other constructors
public abstract AbstractFocusPower makeCopy();
public abstract String getDescription();
// I cut out the getters and setters
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.school == null ? -1 : this.school.ordinal());
dest.writeString(this.name);
dest.writeInt(this.duration);
dest.writeInt(this.cost);
dest.writeInt(this.altCost);
dest.writeInt(this.requiredLevel);
dest.writeByte(this.isSelected ? (byte) 1 : (byte) 0);
dest.writeByte(this.isResonant ? (byte) 1 : (byte) 0);
dest.writeInt(this.nofSpirtBonusUsed);
}
protected AbstractFocusPower(Parcel in) {
int tmpSchool = in.readInt();
this.school = tmpSchool == -1 ? null : ImplementSchool.values()[tmpSchool];
this.name = in.readString();
this.duration = in.readInt();
this.cost = in.readInt();
this.altCost = in.readInt();
this.requiredLevel = in.readInt();
this.isSelected = in.readByte() != 0;
this.isResonant = in.readByte() != 0;
this.nofSpirtBonusUsed = in.readInt();
}
Sample subclass
public class AegisFocusPower extends AbstractFocusPower {
public AegisFocusPower(AppExtension app) {
super(app, ImplementSchool.ABJURATION, app.getString(R.string.focus_power_name_aegis), 0, 1, 1, false);
}
#Override
public String getDescription() {
return getApp().getString(R.string.focus_power_desc_aegis, (1+((int) Math.floor(getApp().getCurrentCharacter().getOccultistLevel()/6.0))));
}
#Override
public AegisFocusPower makeCopy() {
return new AegisFocusPower(getApp());
}
public AegisFocusPower(Parcel in) {
super(in);
}
public static final Parcelable.Creator<AegisFocusPower> CREATOR = new Parcelable.Creator<AegisFocusPower>() {
public AegisFocusPower createFromParcel(Parcel in) {
return new AegisFocusPower (in);
}
public AegisFocusPower [] newArray(int size) {
return new AegisFocusPower[size];
}
};
}
Code where I use it
Gson gsonFocusPowers = new Gson();
String jsonFocusPowers = sharedPreferences.getString(FOCUS_POWERS_GSON, null);
Type typeFocusPower = new TypeToken<ArrayList<AbstractFocusPower>>() {
}.getType();
ArrayList<AbstractFocusPower> focusPowers;
focusPowers = gsonFocusPowers.fromJson(jsonFocusPowers, typeFocusPower);
if (focusPowers != null) {
this.focusPowers.addAll(checkForNewFocusPowers(focusPowers));
} else {
this.focusPowers = getNewFocusPowerList();
}
Unfortunately this gives me an error which I don't know how to fix.
java.lang.RuntimeException: Unable to create application nl.rekijan.occultistmentalfocushelper.AppExtension: java.lang.RuntimeException: Unable to invoke no-args constructor for class nl.rekijan.occultistmentalfocushelper.mvc.focuspowers.AbstractFocusPower. Registering an InstanceCreator with Gson for this type may fix this problem.
Edit: Not sure why that post is a duplicate. For starters it doesn't have an accepted answer. The answer requires a 3rd party library. The question isn't about multiple subclasses under a single abstract.
have you tried registering a type adapter, something like Using Gson and Abstract Classes ? I always add adapters both for specific formatting (for dates, big decimals, anything where you usually require a very specific format) but also for sub-classing.
In this case however, no adapter is needed, this is.. straight on?
public abstract class AbstractFocusPower implements Parcelable {
// just some property needed to be pushed through a constructor
protected final String myString;
protected AbstractFocusPower(String myString) {
this.myString = myString;
}
}
and then the impl (yeah added toString(), hashCode() and equals() the way I like them to be in domain objects..):
public class AegisFocusPower extends AbstractFocusPower {
boolean imParcelled;
public AegisFocusPower(String myString) {
super(myString);
}
#Override //yup the interface impl
public void parcelMe() {
imParcelled = true;
}
#Override
public String toString() {
return new StringBuilder("{ imParcelled : ").append(imParcelled).append(", myString : ").append(myString).append(" }").toString();
}
#Override
public int hashCode() {
return toString().hashCode();
}
#Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other == null || !(other instanceof AegisFocusPower)) {
return false;
} else {
return other.hashCode() == hashCode();
}
}
}
and then I can run the following junit :
#Test
public void AegisFocusPowerToJsonAndBack(){
// single instance
AegisFocusPower ea = new AegisFocusPower("apa");
String json = GSON.toJson(ea);
assertEquals("{\"imParcelled\":\"false\",\"myString\":\"apa\"}", json);
AegisFocusPower backAtYa = (AegisFocusPower) GSON.fromJson(json, AegisFocusPower.class);
assertEquals(backAtYa, ea);
// A list
AegisFocusPower ea2 = new AegisFocusPower("bepa");
AegisFocusPower ea3 = new AegisFocusPower("cepa");
List<AegisFocusPower> powerList = new ArrayList<>();
powerList.add(ea2);
powerList.add(ea3);
String jsonList = GSON.toJson(powerList);
assertEquals("[{\"imParcelled\":\"false\",\"myString\":\"bepa\"},{\"imParcelled\":\"false\",\"myString\":\"cepa\"}]", jsonList);
List<AegisFocusPower> backAtYaz = Arrays.asList(GSON.fromJson(jsonList,AegisFocusPower[].class));
assertEquals(backAtYaz.get(0), ea2);
assertEquals(backAtYaz.get(1), ea3);
}
whereas GSON is initialized simply like
private static final Gson GSON = (new GsonBuilder()).registerTypeAdapter(Boolean.class, new JsonBooleanDeAndSerializer()).create();
and the type adapter registered for booleans which I use is irrelevant for your problem.
This is.. simple enough and would work for you too?
Check your imports. You may have mistakenly imported wrong class in your pojo. i.e. I have imported android.net.TransportInfo instead of my own TransportInfo class
Okay, I have read through nearly every single parcelable question on stack overflow and have tried a number of solutions in order to solve this problem. So overall I have an android project and I want to send 2 ArrayLists of Arrays from one class to another through Intent.
Note: this is not a duplicate of the nested parcelable thread here which does not deal with arrays. It is not a duplicate of this because I have made all nested classes parcelable. It is not a duplicate of this or this because I am not using an array list and also I recognize the error is a failure of initializing. It is not a duplicate of this because I do not need a parameter-less constructor in this situation (I have tested adding one and it doesn't change the error). It is also not a duplicate of this because it is not a parcel nested in a parcel.
I must be doing something obvious because the nullpointer error should only occur if my array has not been initialized. However, it is not possible for me to initialize my array length until I know how long I want it to be...
(as a side note perhaps someone could give me greater insight into the readTypedList class and what exactly XXX.Creator does because this undoubtedly could be part of the problem. Also for any future people with the same problem here is a link to the Parcel javadoc which was informative but did not solve my problem.)
So now I have a Display object. The Display object is solely used to store a String[] and allow it to be parceled. The line that breaks with the has been commented below, and should be obvious for anyone with parcel experience because it has clearly not been initialized:
class Display implements Parcelable {
String[] labels;
public Display(String[] labels) {
this.labels = labels;
}
protected Display(Parcel in) {
in.readStringArray(this.labels); // **** THIS LINE BREAKS
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(this.labels);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<Display> CREATOR = new Creator<Display>() {
#Override
public Display createFromParcel(Parcel in) {
return new Display(in);
}
#Override
public Display[] newArray(int size) {
return new Display[size];
}
};
}
So now I ask myself what I can do to solve this problem and investigated many other threads with promising names but they did not solve my issue.
Here was an answer I would have though would have helped, similarly here they suggested using createTypedList() instead of readTypedList().
However, I tried the first answer and also this and they didn't work because I don't know in advance how long I want my String[] to be, which just ends up leading instead to a bad array length error. And the second answer doesn't help because clearly I don't want to create a new typed list but instead use the typed list I already have, so by creating a new list I end up with a blank list and lose my data.
The root of all of these issues is the fact that my parcel is nested within another parcel and called via:
in.readTypedList(allChartLabels, Display.CREATOR);
Because it is nested, the CREATOR is being called in a way that very much limits my options and has led me to be unable to solve the issue.
Throughout various tests I have encountered a number of errors but no solutions... (the current error my code is throwing is Attempt to get length of null array error).
I know someone has a solution to this issue and for more details here is the rest of my code:
public class SummaryEntry implements Parcelable {
private ArrayList<Calculation> allChartValues; // NOTE: this is another nested parcel class which is basically a duplicate of Display but with float[]
private ArrayList<Display> allChartLabels;
public SummaryEntry() {
// initialize
allChartValues = new ArrayList<>();
allChartLabels = new ArrayList<>();
}
protected SummaryEntry(Parcel in) {
this();
// order in which we do this matters:
in.readTypedList(allChartLabels, Display.CREATOR);
in.readTypedList(allChartValues, Calculation.CREATOR);
}
#Override
public void writeToParcel(Parcel out, int i) {
// order in which we do this matters, must be same as reading typed lists
out.writeTypedList(allChartLabels);
out.writeTypedList(allChartValues);
}
/// ... getters and setters excluded because of irrelevance ///
/// PARCELABLE METHODS
#Override
public int describeContents() {
return 0;
}
public static final Creator<SummaryEntry> CREATOR = new Creator<SummaryEntry>() {
#Override
public SummaryEntry createFromParcel(Parcel in) {
return new SummaryEntry(in);
}
#Override
public SummaryEntry[] newArray(int size) {
return new SummaryEntry[size];
}
};
}
I appreciate you taking the time to read all the way down this long post. Ideally I am looking for a solution to the String[] initialization problem, or if someone could post their working code for a nested parcel including a array, or lastly perhaps someone could point out an easier way to achieve passing these items without nesting two parcels.
You can do it like below code:
Use writeArray & readArray like this:
Try with below updated code:
package com.myapplication;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
String name;
int age;
String [] array;
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
dest.writeStringArray(this.array);
}
public User() {
}
protected User(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
this.array = in.createStringArray();
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
#Override
public User createFromParcel(Parcel source) {
return new User(source);
}
#Override
public User[] newArray(int size) {
return new User[size];
}
};
}
I am still unable to solve the second half of the original question (which was how to parcel arrays) but I was able to achieve a successful nested parcel implementation by redesigning my program slightly.
The new design breaks the implementation into 4 nested classes with a straight forward implementation. I also removed my arrays because I was unable to initialize them properly in the parcel.
EDIT: I did also stumble upon this website which automatically creates parcelables for you. Very useful and I wish I had known about it before!
Also note that apparently it is unnecessary to create parcel classes to parcel String and Float. You can instead call Float.class.getClassLoader() and String.class.getClassLoader() to use the parcel CREATORS for those classes.
Here is my code:
Parcelable class Q contains ArrayList of:
Parcelable class A which contains 2 Arraylists of Strings and Floats
By breaking down the parcel into smaller nested parcels I was able to incrementally test the parcel until I arrived at the finalized solution which was able to be passed through my Intent via this code:
ON THE SENDING END:
// create the parcel
Q parcel = new Q();
ArrayList<String> strings = new ArrayList<>();
ArrayList<Float> stats = new ArrayList<>();
// ... populate arraylists ... //
Intent I = new Intent(CURRENTACTIVITY.this, FUTUREACTIVITY.class);
// PUT THE THING
I.putExtra("Data", parcel);
startActivity(I);
ON THE RECIEVING END:
Q parcel = getIntent().getParcelableExtra("Data"); // get data
FULL CODE for the working parcel:
This code is very long and repetitive but the basic principle for setting up parcels is clear and easy to re-use if you want to create your own parcel.
// top level parcelable class Q
public class Q implements Parcelable {
private ArrayList<A> element;
public Q()
{
this.element = new ArrayList<A>();
}
public void addToA(A a)
{
element.add(a);
}
public ArrayList<A> getA() {
return element;
}
public void setA(ArrayList<A> a) {
this.element = a;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(this.element);
}
private Q (Parcel in){
element = new ArrayList<A>();
in.readTypedList(this.element, A.CREATOR);
}
public static final Parcelable.Creator<Q> CREATOR = new Parcelable.Creator<Q>() {
public Q createFromParcel(Parcel in) {
return new Q(in);
}
public Q[] newArray(int size) {
return new Q[size];
}
};
}
// nested parcel object A
public class A implements Parcelable {
private ArrayList<Float> f;
private ArrayList<String> s;
public A() {
f = new ArrayList<>();
s = new ArrayList<>();
}
public A(ArrayList<Float> f, ArrayList<String> s) {
this.f = f;
this.s = s;
}
public ArrayList<String> getS() {
return s;
}
public void setS(ArrayList<String> s) {
this.s = s;
}
public ArrayList<Float> getF() {
return f;
}
public void setF(ArrayList<Float> f) {
this.f = f;
}
protected A(Parcel in) {
if (in.readByte() == 0x01) {
f = new ArrayList<>();
in.readList(f, Float.class.getClassLoader());
} else {
f = null;
}
if (in.readByte() == 0x01) {
s = new ArrayList<>();
in.readList(s, String.class.getClassLoader());
} else {
s = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (f == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(f);
}
if (s == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(s);
}
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<A> CREATOR = new Parcelable.Creator<A>() {
#Override
public A createFromParcel(Parcel in) {
return new A(in);
}
#Override
public A[] newArray(int size) {
return new A[size];
}
};
}
I have an error when using a Parcelable Class.
I'm trying to pass an image as a ByteArray between activities, and eventually to a remote webservice.
java.lang.ClassNotFoundException: ...
Class not found when unmarshalling:
import java.util.UUID;
public class Property implements Parcelable {
#SerializedName("id")
#NonNull
private final String mId;
protected byte[] mImage;
public byte[] getmImage() { return mImage; }
public void setmImage(byte[] image){ mImage = image; }
#SerializedName("generalInformation")
private GeneralInformation mGeneralInformation;
#NonNull
public GeneralInformation getGeneralInformation() {
return mGeneralInformation;
}
public void setGeneralInformation(#NonNull final GeneralInformation generalInformation) {
mGeneralInformation = generalInformation;
}
#NonNull
public String getId() {
return mId;
}
#Override
public boolean equals(final Object o) {
return (o instanceof Property) && ((Property) o).mId.equals(mId);
}
#Override
public int hashCode() {
return mId.hashCode();
}
public static Property create() {
final Property model = new Property(UUID.randomUUID().toString());
model.setGeneralInformation(new GeneralInformation());
return model;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(#NonNull final Parcel dest, final int flags) {
if(mImage != null) {
dest.writeInt(mImage.length);
dest.writeByteArray(mImage);
}
dest.writeString(mId);
dest.writeParcelable(mGeneralInformation, 0);
}
private Property(#NonNull final String id) {
mId = id;
}
protected Property(final Parcel in) {
if(mImage != null)
{
mImage = new byte[in.readInt()];
in.createByteArray();
}
mId = in.readString();
mGeneralInformation = in.readParcelable(GeneralInformation.class.getClassLoader());
}
public static final Parcelable.Creator<Property> CREATOR = new Parcelable.Creator<Property>() {
public Property createFromParcel(#NonNull final Parcel source) {
return new Property(source);
}
#NonNull
public Property[] newArray(final int size) {
return new Property[size];
}
};
}
Now I have a very limited understanding of the problem(s) I have with this class.
mImage is null when reading the Parcel. (Even when I know for sure that the byteArray is not null when I assign it to the Parcel.) I have tried to initialize mImage in its declaration so it not null but then I have that Class not found when unmarshalling error...
I'm almost sure the problem lies in
protected Property(final Parcel in) {
if(mImage != null)
{
mImage = new byte[in.readInt()];
in.createByteArray();
}
But I don't know how to fix it.
I'm lost for 2 days with this thing :/
Please see the below code for constructor with Parcel object.
protected Property(final Parcel in) {
mImage = new byte[in.readInt()];
in.readByteArray(mImage); // this will read the byte array from Parcel object(in) and store the value in mImage member variable.
mId = in.readString();
mGeneralInformation = in.readParcelable(GeneralInformation.class.getClassLoader());
}
I have a bit trouble implementing Parcelable. Here's how I did it:
public class Player implements Parcelable{
String name;
int score;
#Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(score);
}
public Player(Parcel source){
score = source.readInt();
name = source.readString();
}
}
public class MyCreator implements Parcelable.Creator<Player> {
#Override
public Player createFromParcel(Parcel source) {
return new Player(source);
}
#Override
public Player[] newArray(int size) {
return new Player[size];
}
}
This was the whole code implementing Parcelable. Now I'm trying to create a new class object:
Player newPlayer = new Player(null);
newPlayer.name = text;
newPlayer.score = 0;
playersParceledData.add(newPlayer);
zacniIgro.putParcelableArrayListExtra("parceledData", playersParceledData);
This is the line that is bothering me:
Player newPlayer = new Player(null);
Is the fact that I just insrted "null" okay? Or do I have to insert something else between those ()? I was following this example and this isn't explained in it. It says that a new object should be created like this:
Player newPlayer = new Player();
But I am not allowed to do this since I made a constructor.
Create an additional constructor for use cases you're not using Parcel to construct your object, for example
public class Player implements Parcelable{
/* ... */
public Player(Parcel source){
/* ... */
}
public Player(int score, String name){
this.score = score;
this.name = name;
}
}
Then you can construct objects both from Parcel and using an int and an String:
final Player aPlayer = new Player(10000, "SomeRandomPlayerName");
Also, you read the int and the String in inverse order as you write them.
At the moment I have the problem to pass an ArrayList<MyClass> from one activity to another. I tried to implement Parcelable in MyClass and use an intent in the activity but it seems that I did something wrong. When I call startActivity with the intent the window of the app freezes and I get the Error "!!! Failed Bender Transaction !!!". I've read here and on some other places that the size of the transaction may cause the error. MyClass holds one Image. In the moment startActivity is called the ArrayList holds five elements with one bitmap for each element. The size of all images together is below 70kb.
Here is my actual code:
public class MyClass implements Parcelable {
public enum Type {
VIDEO, AUDIO
}
private int id;
private String name;
private String url;
private double longitude;
private double latitude;
private double gpsDistance;
private String description;
private String thumb;
private Bitmap thumbpic;
private Type type;
public MyClass() {
this.gpsDistance = 0;
}
public MyClass(Parcel in) {
readFromParcel(in);
}
/* Setter & Getter */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeString(url);
dest.writeDouble(longitude);
dest.writeDouble(latitude);
dest.writeDouble(gpsDistance);
dest.writeString(description);
dest.writeString(thumb);
dest.writeParcelable(thumbpic, flags);
dest.writeString((type == null) ? "" : type.name());
}
private void readFromParcel(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
this.url = in.readString();
this.longitude = in.readDouble();
this.latitude = in.readDouble();
this.gpsDistance = in.readDouble();
this.description = in.readString();
this.thumb = in.readString();
this.thumbpic = (Bitmap) in.readParcelable(getClass().getClassLoader());
try {
type = Type.valueOf(in.readString());
} catch (IllegalArgumentException x) {
type = null;
}
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public MyClass createFromParcel(Parcel in) {
return new MyClass(in);
}
public MyClass[] newArray(int size) {
return new MyClass[size];
}
};
}
Some code snippets from the activity (MainActivity) which starts the second one (ListActivity)
ArrayList<Content> contentElements = new ArrayList<Content>();
Intent intent = new Intent(MainActivity.this,ListActivity.class);
intent.putParcelableArrayListExtra("content", contentElements);
The handling in the ListActivity
Intent i = getIntent();
contentElements= i.getParcelableArrayListExtra("content");
Does someone have an idea what caused this problem and have solution or an alternative approach?
Thanks a lot!