2

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.

JFreeman
  • 974
  • 1
  • 10
  • 26

2 Answers2

1

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];
        }
    };
}
Chetan Joshi
  • 5,582
  • 4
  • 30
  • 43
  • what is the variable 'size' in `for( int i = 0; i < size; i++)`? I unfortunately do not now what size I want for my array... – JFreeman Feb 01 '19 at 21:28
  • Thank you for your help. But unfortunately, I also get an error on this line: *`a = new String[objects.length];`* "Attempt to get length of null array". That implies that the `in.readArray(null)` didn't find the item. – JFreeman Feb 01 '19 at 22:09
  • @JFreeman You can see the change in Constructor where your program was break; – Chetan Joshi Feb 04 '19 at 07:01
  • Okay great! Thank you for that! – JFreeman Feb 05 '19 at 06:36
0

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];
        }
    };
}
JFreeman
  • 974
  • 1
  • 10
  • 26