How to pass objects within objects between activities with parcelable - java

Im pretty new in Android Studio.
I'm trying to pass an ArrayList from one activity to another using parcelable. Within the class Recipe I declare another ArrayList which I cannot get a hold of when starting the other activity.
Recipe.java:
public class Recipe implements Parcelable {
String name;
ArrayList<Ingredient> ingredients;
public Recipe(String name){
this.name = name;
this.ingredients = new ArrayList<>();
}
protected Recipe(Parcel in) {
name = in.readString();
}
public static final Creator<Recipe> CREATOR = new Creator<Recipe>() {
#Override
public Recipe createFromParcel(Parcel in) {
return new Recipe(in);
}
#Override
public Recipe[] newArray(int size) {
return new Recipe[size];
}
};
public void addIngredients(String[] amountList, String[] ingredientList, String[] unitList) {
for (int i = 0; i < ingredientList.length; i++) {
ingredients.add(new Ingredient(ingredientList[i], Double.parseDouble(amountList[i]), unitList[i]));
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
}
}
Ingredient.java:
public class Ingredient implements Parcelable {
private String ingrdnt;
private double amount;
private String unit;
private String cat;
private boolean checkedItem;
public Ingredient(String ingrdnt, double amount, String unit) {
this.ingrdnt = ingrdnt;
this.amount = amount;
this.unit = unit;
//this.cat = category;
this.checkedItem = false;
}
protected Ingredient(Parcel in) {
ingrdnt = in.readString();
amount = in.readDouble();
unit = in.readString();
cat = in.readString();
checkedItem = in.readByte() != 0;
}
public static final Creator<Ingredient> CREATOR = new Creator<Ingredient>() {
#Override
public Ingredient createFromParcel(Parcel in) {
return new Ingredient(in);
}
#Override
public Ingredient[] newArray(int size) {
return new Ingredient[size];
}
};
public double getAmount() {
return amount;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(ingrdnt);
parcel.writeDouble(amount);
parcel.writeString(unit);
parcel.writeString(cat);
parcel.writeByte((byte) (checkedItem ? 1 : 0));
}
}
In main:
private ArrayList<Recipe> recipes = new ArrayList<>();
//recipes obviously holds a bunch of recipes so it's not empty.
intent.putExtra("recipes", recipes);
System.out.println(recipes.get(0).ingredients.get(0).getAmount());
System.out: 2.0
In second activity:
recipes = this.getIntent().getParcelableArrayListExtra("recipes");
//Same print as above
System.out.println(recipes.get(0).ingredients.get(0).getAmount());
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.util.ArrayList.size()' on a null object reference
Have I implemented the parcelable in a wrong way or why can I not get a hold of the Ingredient objects?
I've read about other ways to pass objects between activities but it seems like parcelable might be the best way to do it.

Yes, you basically forgot to write the ingredients of the Recipe to the output Parcel which is given to the Recipe.writeToParcel method.
You can write the ArrayList<Parcelable> with writeTypedList and read it back with readTypedList.
So your Recipe constructor which accepts a Parcel should be like:
protected Recipe(Parcel in) {
name = in.readString();
ingredients = new ArrayList<>();
in.readTypedList(ingredients, Ingredient.CREATOR);
}
while your writeToParcel of the Recipe should become:
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeTypedList(ingredients);
}
The NullPointerException you are seeing is caused by the fact that you do not allocate a new ArrayList in the constructor of Recipe which accepts a Parcel, so when you call recipes.get(0).ingredients.get(0).getAmount() in the second Activity the ingredients ArrayList is null, thus the Exception.
Also note (but not related to the problem) that there exist a writeBoolean and a readBoolean with which you can write and read values of type boolean (I am saying this for the Ingredient class implementation).
Try those out and let us know if it worked properly.

Related

Use of ClassLoader in parcelable.readArrayList() in Android

I've got a Class named as FilterData which implements Parcelable. I had a member variable private ArrayList<String> listPropertyType; When implementing the parcelable interface in my class, parcel.readArrayList(null) ,the parameter is shown as ClassLoader object. With this member variable the FilterData class works as intended. But I wanted to implement a scenario where ClassLoader object is passed to readArrayList() method.
So what I've gathered from the documentation which is unclear about the public ArrayList readArrayList (ClassLoader loader) that if the ArrayList contains non primitive class ,we have to use ClassLoader.
What's the use case in this scenerio of using a ClassLoader and how to use it? I wanted to use the ClassLoader in the following matter. Added a member variable private ArrayList<RandomClass> listRandom; RandomClass randomClass; to implement this.
My FilterData class holds :
public FilterData(Parcel parcel)
{
listPropertyType=parcel.readArrayList(null);
listRandom=parcel.readArrayList(randomClass);
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeList(listPropertyType);
parcel.writeList(listRandom);
}
And my RandomClass is :
public class RandomClass extends ClassLoader{
//this class is for testing classloader in ArrayList in parcelable .readArrayList()
String name;
int age;
public RandomClass(String name, int age) {
this.name = name;
this.age = age;
}
}
This implementation doesn't work. So how to use this?
First of all, if we want to get a class Parcelable, we can use a website www.parcelabler.com. It will generate the syntax of the class. Secondly If we wanted to use an ArrayList of a class of our own. Making a Parcelable class containing the ArrayList won't work. We need to make the custom class Parcelable as well.
If a class that needs to be parcelable contains a member variable ArrayList<String> list; , the class which is generated from the website, should be :
public class MyParcelable implements Parcelable {
ArrayList<String> list;
public MyParcelable(ArrayList<String> list) {
this.list = list;
}
protected MyParcelable(Parcel in) {
if (in.readByte() == 0x01) {
list = new ArrayList<String>();
in.readList(list, String.class.getClassLoader());
} else {
list = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (list == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(list);
}
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
#Override
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
#Override
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
}
And if the Class contains an ArrayList of a class made by us like this ArrayList<DummyClass> list; , the code should be:
public class MyComplexParcelable implements Parcelable {
ArrayList<DummyClass> list;
public MyComplexParcelable(ArrayList<DummyClass> list) {
this.list = list;
}
#Override
public String toString() {
return "MyComplexParcelable{" +
"list=" + list +
'}';
}
protected MyComplexParcelable(Parcel in) {
if (in.readByte() == 0x01) {
list = new ArrayList<DummyClass>();
in.readList(list, DummyClass.class.getClassLoader());
} else {
list = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (list == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(list);
}
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<MyComplexParcelable> CREATOR = new Parcelable.Creator<MyComplexParcelable>() {
#Override
public MyComplexParcelable createFromParcel(Parcel in) {
return new MyComplexParcelable(in);
}
#Override
public MyComplexParcelable[] newArray(int size) {
return new MyComplexParcelable[size];
}
};
}
If we compare both cases , there is no difference in the syntax. Both class uses in.readList(list, String.class.getClassLoader()); and in.readList(list, DummyClass.class.getClassLoader()); which uses ClassLoader object of the correspondent classes.
But in the latter case, we need to make the DummyClass parcelable. Like this:
public class DummyClass implements Parcelable {
int age;
String name;
public DummyClass(int age, String name) {
this.age = age;
this.name = name;
}
#Override
public String toString() {
return "DummyClass{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
protected DummyClass(Parcel in) {
age = in.readInt();
name = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<DummyClass> CREATOR = new Parcelable.Creator<DummyClass>() {
#Override
public DummyClass createFromParcel(Parcel in) {
return new DummyClass(in);
}
#Override
public DummyClass[] newArray(int size) {
return new DummyClass[size];
}
};
}
If the array list you're reading from the parcel is subclass you've made, and not a standard java list subclass, or if it contains non-primitive types, you need to pass MyCustomList.class.getClassLoader().
You need to do it this way because parcelables can be inflated everywhere, anytime, so the code must know the ClassLoader required to load it. Most of the time you won't need it because you will be parcelling stuff inside your own app, but it is a good practice.
In fact, you said the list is a simple ArrayList<String>, so you don't need to do anything. It would be different if it was a MyListSubclass<String>, a ArrayList<MyObject>, or an ArrayList<HashMap<Object, Object>> that may contain other subclassed objects. You get the idea.

Can't instantiate parceable Object

I created a parceable class and wanted to use it for data transfer from fragment A to B. I did it like in many other tutorials but I can't instantiate from parceable object class. it always says, that I have to put in 'Parcel in' as parameter.
Here my object class:
public class DataObject implements Parcelable {
private int number1 = 0;
private int number2 = 0;
private String name = "";
public int getNumber1() {
return number1;
}
public void setNumber1(int number1) {
this.number1 = number1;
}
public int getNumber2() {
return number2;
}
public void setNumber2(int number2) {
this.number2 = number2;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected DataObject(Parcel in) {
number1 = in.readInt();
number2 = in.readInt();
name = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(number1);
dest.writeInt(number2);
dest.writeString(name);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<DataObject> CREATOR = new Parcelable.Creator<DataObject>() {
#Override
public DataObject createFromParcel(Parcel in) {
return new DataObject(in);
}
#Override
public DataObject[] newArray(int size) {
return new DataObject[size];
}
};
}
And here is how I use objects from this type:
DataObject bla = new DataObject();
bla.setNumber1(1);
bla.setNumber2(2);
bla.setName("TestName");
When I hover the red highlighted constructor it says:
DataObject (Parcel) in DataObject cannot be applied
and compiler says:
Error:(30, 50) error: constructor PointCardMainData in class
PointCardMainData cannot be applied to given types;
required: Parcel
found: no arguments
reason: actual and formal argument lists differ in length
Has anybody an idea what is missing? Do I have to set something in the manifest- oder gradle-file? do I have to do something before building the project?
best regards
You've written just one constructor in the DataObject class, and it specifies how to make a DataObject from a Parcel. Now you're trying to make a DataObject without a parcel. You need to either add a Parcel to the line DataObject bla = new DataObject(); or add a new constructor.
So you might write
Parcel theParcel = new Parcel();
DataObject bla = new DataObject(theParcel);
Or you might have a constructor in your DataObject class like
public DataObject(){
}
Add a constructor in your DataObject class
public DataObject()
{}

How do I make my realm object a java object?

I have a database in realm and need to pass a realm object as a java object I have created in order for my adapter to recognise it.
public final static Parcelable.Creator<MoviePOJO> CREATOR = new Creator<MoviePOJO>() {
#SuppressWarnings({
"unchecked"
})
public MoviePOJO createFromParcel(Parcel in) {
MoviePOJO instance = new MoviePOJO();
instance.posterPath = ((String) in.readValue((String.class.getClassLoader())));
instance.overview = ((String) in.readValue((String.class.getClassLoader())));
instance.releaseDate = ((String) in.readValue((String.class.getClassLoader())));
instance.id = ((Integer) in.readValue((Integer.class.getClassLoader())));
instance.originalTitle = ((String) in.readValue((String.class.getClassLoader())));
instance.backdropPath = ((String) in.readValue((String.class.getClassLoader())));
instance.voteAverage = ((Double) in.readValue((Double.class.getClassLoader())));
return instance;
}
public MoviePOJO[] newArray(int size) {
return (new MoviePOJO[size]);
}
};
is the movie creator and my RealmObject is:
public class FavoritesItem extends RealmObject implements Parcelable {
#PrimaryKey private Integer movieId;
private String moviePoster;
private String movieBackdrop;
private String movieTitle;
private String movieOverview;
private Double movieRating;
private String movieReleaseDate;
}
How do I get one to the other?
Presuming that, as #cricket_007 suggests, you want to get an object from a Realm DB and then send it somewhere on an Intent (or vice-versa), you have a couple options.
By far the best would be to avoid sending the whole object. If you are just sending it, say, to an Activity or an IntentService, within your own app, just send the primary key (probably movieId). The recipient can open a Realm instance and select the same object. This will be far faster than even parcelling.
If you absolutely must parcel, you are going to want an adapter, like this:
public class MovieParcelable implements Parcelable {
public static final Creator<MovieParcelable> CREATOR = new Creator<MovieParcelable>() {
#Override
public MovieParcelable createFromParcel(Parcel in) {
int id = in.readInt();
String poster = in.readString();
// ...
double rating = in.readDouble();
String releaseDate = in.readString();
return new MovieParcelable(id, poster, ..., rating, releaseDate);
}
#Override
public MovieParcelable[] newArray(int size) {
return new MovieParcelable[size];
}
};
private final Movie movie;
public MovieParcelable(Movie movie) {
this.movie = movie;
}
MovieParcelable(int id, String poster, ..., double rating, String releaseDate) {
this(new Movie(id, poster, ..., rating, releaseDate));
}
public Movie getMovie() { return movie; }
#Override
public int describeContents() { return 0; }
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(movie.getId());
dest.writeString(movie.getPoster());
// ...
dest.writeDouble(movie.getRating());
dest.writeString(movie.getReleaseDate());
}
}

How to pass a custom object with a 2D ArrayList field using Parcelable

Inside my Exercise class, I have a 2D ArrayList. I have managed to pass all of the fields from Activity to another using Parcelable. However, I cannot figure out how to pass the 2D ArrayList within my object using Parcelable.
public class Exercise implements Parcelable{
private String name, equipmentRequired;
private ArrayList musclesWorked;
private HashMap personalBest;
private boolean selected;
private float minAccelLevel, minAccelChanges;
private ArrayList<ArrayList<Double>> sets = new ArrayList<>();
...
private Exercise(Parcel in) {
name = in.readString();
equipmentRequired = in.readString();
musclesWorked = in.readArrayList(ArrayList.class.getClassLoader());
personalBest = in.readHashMap(HashMap.class.getClassLoader());
selected = in.readInt() != 0;
// sets = ???????
}
public static final Parcelable.Creator<Exercise> CREATOR = newParcelable.Creator<Exercise>() {
public Exercise createFromParcel(Parcel in) {
return new Exercise(in);
}
public Exercise[] newArray(int size) {
return new Exercise[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(equipmentRequired);
dest.writeList(musclesWorked);
dest.writeMap(personalBest);
dest.writeInt(selected ? 1 : 0);
// ?????
}
}
According to Google Documentation and this other question, I would suggest going with
private Exercise(Parcel in) {
name = in.readString();
equipmentRequired = in.readString();
musclesWorked = in.readArrayList(ArrayList.class.getClassLoader());
personalBest = in.readHashMap(HashMap.class.getClassLoader());
selected = in.readInt() != 0;
sets = in.readArrayList(null);
}
This would be because, as the doc states:
The given class loader will be used to load any enclosed Parcelables.
Managed to solve the problem myself. Thanks.
...
private Exercise(Parcel in) {
...
sets = in.readArrayList(ArrayList.class.getClassLoader());
}
#Override
public void writeToParcel(Parcel dest, int flags) {
...
dest.writeList(sets);
}
}

Parcelable is really usefull?

I tried to send a parcel from the mainActivity to a fragment and i noticed a strange effect.
When you implement parcelable, android require to implement "writeToParcel" and "describeContents".
The strange effect is, if you write something inside "writeToParcel" or nothing, it doesn't matter. Android never call it.
Why these functions are required ?
Here is a working source code :
public class Movie implements Parcelable {
private String title;
private Float score;
public Movie(String title, float score) {
this.title = title;
this.score = score;
}
...... Getter and Setter......
//Parcelable Methods
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
// Don't need to write anything in this area
/*
dest.writeString(title);
dest.writeFloat(score);
*/
}
public static final Parcelable.Creator<Movie> CREATOR = new Parcelable.Creator<Movie>() {
//--------------> You can return null, it's never called and work perfectly!
public Movie createFromParcel(Parcel in) {
return null;//new Movie(in);
}
public Movie[] newArray(int size) {
return null;//new Movie[size];
}
};
}
Any idear ?
Regards,

Categories