2

I get a ClassNotFoundException when trying to unmarshall a List<List<LatLng> in a custom class which implements Parcelable. How do I get rid of this exception?

Here's my class:

public class Country implements Parcelable {

    public static final Parcelable.Creator<Country> CREATOR = new Creator<Country>() {

        @Override
        public Country[] newArray(int size) {
            return new Country[size];
        }

        @SuppressWarnings("unchecked")
        @Override
        public Country createFromParcel(Parcel source) {
            Country country = new Country();
            country.name = source.readString();
            country.borders = source.readArrayList(Country.class
                    .getClassLoader());
            return country;
        }
    };

    public String name;

    public List<List<LatLng>> borders;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeList(borders);
    }

}

And here's my logcat:

03-08 22:09:43.749: E/AndroidRuntime(16991): FATAL EXCEPTION: main
03-08 22:09:43.749: E/AndroidRuntime(16991): java.lang.RuntimeException: Unable to start activity ComponentInfo{codeguru.worldtour/codeguru.worldtour.MainActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: codeguru.worldtour.Country
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1654)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1670)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:2844)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.access$1600(ActivityThread.java:117)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Looper.loop(Looper.java:130)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.main(ActivityThread.java:3695)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at java.lang.reflect.Method.invokeNative(Native Method)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at java.lang.reflect.Method.invoke(Method.java:507)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at dalvik.system.NativeStart.main(Native Method)
03-08 22:09:43.749: E/AndroidRuntime(16991): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: codeguru.worldtour.Country
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readParcelable(Parcel.java:1958)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readValue(Parcel.java:1846)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readListInternal(Parcel.java:2092)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readArrayList(Parcel.java:1536)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readValue(Parcel.java:1867)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Parcel.readMapInternal(Parcel.java:2083)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Bundle.unparcel(Bundle.java:208)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Bundle.getBundle(Bundle.java:1078)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at ekg.a(SourceFile:74)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at maps.e.al.a(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at maps.e.bj.a(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at eir.onTransact(SourceFile:66)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.os.Binder.transact(Binder.java:279)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.maps.internal.IMapViewDelegate$a$a.onCreate(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.maps.MapView$a.onCreate(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.dynamic.a$3.b(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.dynamic.a$1.a(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.maps.MapView$b.eb(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.maps.MapView$b.a(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.dynamic.a.a(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.dynamic.a.onCreate(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at com.google.android.gms.maps.MapView.onCreate(Unknown Source)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at codeguru.worldtour.MainActivity.onCreate(MainActivity.java:63)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
03-08 22:09:43.749: E/AndroidRuntime(16991):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1618)
03-08 22:09:43.749: E/AndroidRuntime(16991):    ... 12 more

Marshalling:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putParcelableArrayList(COUNTRIES_KEY, countries);
    savedInstanceState.putInt(COUNTRIES_INDEX_KEY, countryIndex);
}

Unmarshalling (in onCreate()):

    if (savedInstanceState != null) {
        countryIndex = savedInstanceState.getInt(COUNTRIES_INDEX_KEY, -1);
        countries = savedInstanceState
                .getParcelableArrayList(COUNTRIES_KEY);
    }
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268

4 Answers4

1

Edit:

Make sure LatLng implements Parcelable. Otherwise the activity will not know how to marshall and unmarshall it when it has to marshall/unmarshall Country (which is why you may be getting the error: The inferred type List<LatLng> is not a valid substitute for the bounded parameter <T extends Parcelable>.

This should work - create a class to extend ArrayList<LatLng>. The idea is to use the CREATOR of ParcelableArrayList (the class below) to create your list. It will only work if you instantiate your list as an ArrayList (as another answer mentioned you should do).

public class ParcelableArrayList extends ArrayList<LatLng> implements 
    Parcelable {

public ParcelableArrayList(){
    super();
}

protected ParcelableArrayList(Parcel in) {
    in.readList(this, LatLng.class.getClassLoader());
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeList(this);
}   

public static final Parcelable.Creator<ParcelableArrayList> CREATOR = new Parcelable.Creator<ParcelableArrayList>() {
    public ParcelableArrayList createFromParcel(Parcel in) {
        return new ParcelableArrayList(in);
    }

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

Now, in your Country class:

public class Country implements Parcelable {

public static final Parcelable.Creator<Country> CREATOR = new Creator<Country>() {

    @Override
    public Country[] newArray(int size) {
        return new Country[size];
    }

    @SuppressWarnings("unchecked")
    @Override
    public Country createFromParcel(Parcel source) {
        return new Country(in);
    }
};

public String name;
public List<ParcelableArrayList> borders;

public Country(Parcel source) {

    country.name = source.readString();

    country.borders = new ArrayList<ParcelableArrayList()>();
    country.borders = source.readTypedList(ParcelableArrayList.CREATOR);

}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(name);
    dest.writeList(borders);
}

}

Edit 2:

Okay, instead of reading with Parcel#readTypedList() , try using Parcel#readList() instead. So replace:

    country.borders = new ArrayList<ParcelableArrayList()>();
    country.borders = source.readTypedList(ParcelableArrayList.CREATOR);

with

    country.borders = new ArrayList<ParcelableArrayList()>();
    country.borders = source.readList(country.borders, ParcelableArrayList.class.getClassLoader());

I tested this in an app of my own and it worked with no compile time or runtime errors (as far as I can see).

u3l
  • 3,342
  • 4
  • 34
  • 51
  • I have no control over the `LatLng` class. It is from the Google Maps API. – Code-Apprentice Mar 09 '14 at 17:33
  • From here (https://developers.google.com/maps/documentation/android/reference/com/google/android/gms/maps/model/LatLng) I see that `LatLng` already implements `Parcelable`, so you're good to go. Have you tried using the above approach? – u3l Mar 09 '14 at 20:47
  • "The inferred type List is not a valid substitute for the bounded parameter ." is caused by the fact that `T` is referring to a `List` (i.e. I am trying to marshall a list of lists.) – Code-Apprentice Mar 10 '14 at 01:47
  • See my edit. `readList` instead of `readTypedList` should be able to read the `List of ParcelableArrayList`s without error as far as I know . – u3l Mar 10 '14 at 09:48
0

Following the thread here and all comments on answers : https://stackoverflow.com/a/4915219/693752

It may be interesting to :

  • declare borders as an array list (not a list)
  • use parcel.writeTypedList(List) to write the list of borders
  • use parcel.createTypedArrayList to read it from the parcel. It looks like readArrayListis difficult to handle.
Community
  • 1
  • 1
Snicolas
  • 37,840
  • 15
  • 114
  • 173
  • `writeTypedList()` gives a compiler error: Bound mismatch: The generic method writeTypedList(List) of type Parcel is not applicable for the arguments (List>). The inferred type List is not a valid substitute for the bounded parameter – Code-Apprentice Mar 09 '14 at 06:09
  • Can't you replace your list of list with a parcelabel data structure ? – Snicolas Mar 10 '14 at 10:43
0

Add a constructor that takes parcel as argument and Add a Creator to your class.

public Country(Parcel source) {
    country.name = source.readString();
    country.borders = source.readArrayList(Country.class
                .getClassLoader());
}

@SuppressWarnings("rawtypes")
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
    public Country createFromParcel(Parcel in) {
        return new Country(in);
    }

    public Country[] newArray(int size) {
        return new Country[size];
    }
};
danny117
  • 5,581
  • 1
  • 26
  • 35
0

I tackled this with breaking List<List<LatLng>> into List<CustomClass> where CustomClass extends ArrayList<LatLng>>

here is my code of CustomClass

import java.util.ArrayList;

import android.os.Parcel;
import android.os.Parcelable;

public class Lister extends ArrayList<AlertEvaluation> implements Parcelable {

    protected Lister(Parcel in) {
        this.addAll(in.readArrayList(AlertEvaluation.class.getClassLoader()));
    }

    public static final Creator<Lister> CREATOR = new Creator<Lister>() {
        @Override
        public Lister createFromParcel(Parcel in) {
            return new Lister(in);
        }

        @Override
        public Lister[] newArray(int size) {
            return new Lister[size];
        }
    };

    @Override public int describeContents() {
        return 0;
    }

    @Override public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeList(this);
    }
}
Sandeep
  • 1,504
  • 7
  • 22
  • 32
Anuj Garg
  • 1
  • 1