Search code examples
javaandroidandroid-studioparcelable

How to pass objects within objects between activities with parcelable


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.


Solution

  • 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.