56

Given a custom class org.example.app.MyClass implements Parcelable, I want to write a List<MyClass> to a Parcel. I did the marshalling with

 List<MyClass> myclassList = ...
 parcel.writeList(myclassList);

whenever I try to unmarshall the class with

 List<MyClass> myclassList = new ArrayList<MyClass>();
 parcel.readList(myclassList, null);

there is an "BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass" exception.

What is wrong here? Why is the class not found?

Flow
  • 23,572
  • 15
  • 99
  • 156
  • 1
    I got this error in a different context - calling `bundle.keySet()` on a bundle that contained a Parcelable. When running an individual test class against the code in question, it passed, but running the entire Test suite resulted in the `BadParcelableException`. The "fix" was to do `bundle.setClassloader(MyClass.class.getClassLoader())` before `bundle.keySet()`. – Stan Kurdziel May 22 '15 at 22:17
  • Does this answer your question? [BadParcelableException: ClassNotFoundException when unmarshalling](https://stackoverflow.com/questions/11393751/badparcelableexception-classnotfoundexception-when-unmarshalling) – m0skit0 Sep 03 '21 at 07:57

4 Answers4

112

Don't unmarshall a custom class (i.e. one provided by your application and not by the Android framework) with the framework class loader that is used when you give null as the ClassLoader argument. Use the application class loader:

parcel.readList(myclassList, getClass().getClassLoader());

Whenever a Parcel.read*() method also has a ClassLoader as an argument (e.g. Parcel.readList(List outVal, ClassLoader loader)) and you want to read an application class from a Parcel, use the application class loader that can be retrieved with getClass().getClassLoader().

Background: Android comes with two class loaders: the system class loader, that is able to load all system classes but the ones provided by your application; and the application class loader, which has set the system class loader as its parent and therefore is able to load all classes. If you give null as the class loader, Parcel.readParcelableCreator() will use the framework class loader, causing a ClassNotFoundException.

Thanks to alexanderblom for providing the hint that led me on the right track.

Pang
  • 9,564
  • 146
  • 81
  • 122
Flow
  • 23,572
  • 15
  • 99
  • 156
19

I believe a more correct form of this would be:

List<MyClass> myclassList = new ArrayList<MyClass>();
parcel.readList(myclassList, MyClass.class.getClassLoader());

Because here you are explicitly using the class loader for the List's generic type.

Tanner Perrien
  • 3,133
  • 1
  • 28
  • 35
  • 7
    That's basically the same as my answer. There is no difference between `MyClass.class` and `getClass()`, both return the same class object since `getClass()` is called within MyClass. Also, you could use any class here, as long as it's an custom class and not one provided by the framework. – Flow Jan 29 '14 at 08:10
  • 9
    I did not say you were incorrect, just that this form might be more appropriate. Also consider that you are making an additional method call to `getClass()` that is not necessary when you reference the class statically. – Tanner Perrien Nov 24 '14 at 20:36
  • 1
    It is not working in my case .Using classLoader as mentioned. – SimpleCoder Jan 26 '19 at 10:07
8

I have faced the same problem and instead of parcleable I used bundle

intent.setExtrasClassLoader(getClassLoader());
/* Send optional extras */
Bundle bundle = new Bundle();
bundle.putParcelable("object", mObject);
bundle.putString("stringVal", stringVal);

intent.putExtra("bundle",bundle);

And in my intent class I used this to retrieve the value:

Bundle oldBundle = intent.getBundleExtra("bundle");
ResultReceiver receiver = oldBundle.getParcelable("object");
String stringVal = oldBundle.getString("stringVal");
intent.setExtrasClassLoader(getClassLoader());

Hope it will be helpful for some.

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
Wasif Kirmani
  • 1,277
  • 3
  • 22
  • 43
5

You can get this error if you subclass a custom View incorrectly.

Assume you are subclassing BottomNavigationView and you want to add saved state to the superstate in onSaveInstanceState().

An incorrect implementation of the Parcelable boilerplate (copied from another class or a template) would look like this:

static class State extends BaseSavedState {

    Bundle stateBundle;

    //incorrect as super state uses ClassLoaderCreator
    public static final Creator<State> CREATOR = new Creator<State>() {
        public State createFromParcel(Parcel in) {
            return new State(in);
        }

        public State[] newArray(int size) {
            return new State[size];
        }
    };

    State(Parcel source) {
        super(source);
        this.stateBundle = source.readBundle(getClass().getClassLoader());
    }

    State(Parcelable superState) {
        super(superState);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeBundle(stateBundle);
    }
}

This would not work as the superstate from BottomNavigationView requires a classloader. Instead you should carefully inspect the SavedState class from BottomNavigationView and use the correct ClassLoaderCreator rather than Creator:

static class State extends AbsSavedState {

    Bundle stateBundle;

    public static final Creator<State> CREATOR = new ClassLoaderCreator<State>() {
        public State createFromParcel(Parcel in, ClassLoader classLoader) {
            return new State(in, classLoader);
        }

        @Override
        public State createFromParcel(Parcel source) {
            return new State(source, null);
        }

        public State[] newArray(int size) {
            return new State[size];
        }
    };

    State(Parcel source, ClassLoader classLoader) {
        super(source, classLoader);
        this.stateBundle = source.readBundle(classLoader);
    }

    State(Parcelable superState) {
        super(superState);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeBundle(stateBundle);
    }
}

Note that extending android.support.v4.view.AbsSavedState may be a better choice than BaseSavedState or android.view.AbsSavedState since it will allow you to pass a class loader to the superclass:

SavedState(Parcel source, ClassLoader classLoader) {
    super(source, classLoader); //available in android.support.v4.view.AbsSavedState
    this.stateBundle = source.readBundle(classLoader);
}
David Rawson
  • 20,912
  • 7
  • 88
  • 124