nicodp
nicodp

Reputation: 2392

BadParcelableException: ClassNotFoundException

I'm getting this error when trying to pass a Parcelable through an Intent. Basically, I've implemented my class as Parcelable, but I can't pass the object to another activity because it crashes when reading it. I tried several answers for this problem, but none has helped me to solve this issue.

So far, this is my code for WeatherData.java (sorry, the class is a bit large):

public class WeatherData implements Parcelable {

private final static String ICON_ADDR = "http://openweathermap.org/img/w/";

static class City implements Parcelable {
    String name;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        Bundle bundle = new Bundle();
        bundle.putString("cityName", name);
        dest.writeBundle(bundle);
    }

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

        @Override
        public City createFromParcel(Parcel source) {
            // read the bundle containing key value pairs from the parcel
            Bundle bundle = source.readBundle();

            // instantiate the forecast info using values from the bundle
            return new City(bundle.getString("cityName"));
        }

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

    };

    private City (String cityName) {
        name = cityName;
    }
}

static class Weather implements Parcelable {
    String description;
    String icon;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        Bundle bundle = new Bundle();
        bundle.putString("description", description);
        bundle.putString("icon", icon);
        dest.writeBundle(bundle);
    }

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

        @Override
        public Weather createFromParcel(Parcel source) {
            // read the bundle containing key value pairs from the parcel
            Bundle bundle = source.readBundle();

            // instantiate the forecast info using values from the bundle
            return new Weather(bundle.getString("description"),
                    bundle.getString("icon"));
        }

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

    };

    private Weather (String description, String icon) {
        icon = icon;
        description = description;
    }

}

static class Temp implements Parcelable {
    float day;
    float min;
    float max;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        Bundle bundle = new Bundle();

        bundle.putFloat("day", day);
        bundle.putFloat("min", min);
        bundle.putFloat("max", max);

        dest.writeBundle(bundle);
    }

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

        @Override
        public Temp createFromParcel(Parcel source) {
            // read the bundle containing key value pairs from the parcel
            Bundle bundle = source.readBundle();

            // instantiate the forecast info using values from the bundle
            return new Temp(bundle.getFloat("day"),
                    bundle.getFloat("min"), bundle.getFloat("max"));
        }

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

    };

    private Temp (float tDay, float tMin, float tMax) {
        day = tDay;
        min = tMin;
        max = tMax;
    }
}

static class ForecastInfo implements Parcelable {
    Temp temp;
    ArrayList<Weather> weather;

    String getTemperatureInCelsius() {
        float t = temp.day - 273.15f;
        return String.format("%.0f" + (char) 0x00B0, t);
    };

    public String getIconAddress() {
        return ICON_ADDR + weather.get(0).icon + ".png";
    };

    public String getDescription() {
        if (weather != null && weather.size() > 0)
            return weather.get(0).description;
        return null;
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        Bundle bundle = new Bundle();

        bundle.putParcelable("temp", this.temp);
        bundle.putParcelableArrayList("weatherList", this.weather);

        dest.writeBundle(bundle);
    };

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

        @Override
        public ForecastInfo createFromParcel(Parcel source) {
            // read the bundle containing key value pairs from the parcel
            Bundle bundle = source.readBundle();

            // instantiate the forecast info using values from the bundle
            return new ForecastInfo((Temp) bundle.get("temp"),
                    (ArrayList<Weather>) bundle.get("weatherList"));
        }

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

    };

    private ForecastInfo (Temp temp, ArrayList<Weather> weatherList) {
        this.temp = temp;
        this.weather = weatherList;
    };
}

// Relevant data for WeatherData
// The city (which only has a name in it)
City city;
// The array of forecast info extracted from the JSON
ArrayList<ForecastInfo> list;

private WeatherData (City city, ArrayList<ForecastInfo> forecastList) {
    city = city;
    list = forecastList;
}

// A method that converts temperature from Kelvin degrees to Celsius
String getTemperatureInCelsius(int day) {
    return list.get(day).getTemperatureInCelsius();
}

// getIconAddress concatenates the base address and the specific code for
// the icon
public String getIconAddress(int day) {
    return list.get(day).getIconAddress();
}

public String getName() {
    return city.name;
}

public String getDescription(int day) {
    return list.get(day).getDescription();
}

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

@Override
public void writeToParcel(Parcel dest, int flags) {
    // create a bundle for the key value pairs
    Bundle bundle = new Bundle();

    // insert the key value pairs to the bundle
    bundle.putString("cityName", this.getName());
    bundle.putParcelableArrayList("forecastList", this.list);

    // write the key value pairs to the parcel
    dest.writeBundle(bundle);
}

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

    @Override
    public WeatherData createFromParcel(Parcel source) {
        // read the bundle containing key value pairs from the parcel
        Bundle bundle = source.readBundle();
        return new WeatherData(bundle.getString("cityName"),
                new ArrayList<ForecastInfo>());

    }

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

};

private WeatherData (String name, ArrayList<ForecastInfo> forecastInfos) {
    City cityNew = new City(name);
    city = cityNew;
    forecastInfos.add(new ForecastInfo(
            new Temp(1.0f, 1.0f, 1.0f),
            new ArrayList<Weather>()));
    list = forecastInfos;
}

private WeatherData (String name) {
    City cityNew = new City(name);
    city = cityNew;
}  
}

Then, in my MainActivity, I call the following function (sorry about the multiple calls to setExtrasClassLoader):

public void showDetails(View view) {
    Intent intent = new Intent(getApplicationContext(), ShowDetailsActivity.class);
    intent.setExtrasClassLoader(WeatherData.class.getClassLoader());
    intent.setExtrasClassLoader(WeatherData.ForecastInfo.class.getClassLoader());
    intent.setExtrasClassLoader(WeatherData.City.class.getClassLoader());
    intent.setExtrasClassLoader(WeatherData.Weather.class.getClassLoader());
    intent.setExtrasClassLoader(WeatherData.Temp.class.getClassLoader());
    intent.putExtra("bundleObject", weatherData.list);
    startActivity(intent);
}

After that, I'm trying to get the content of the intent in the new activity inside the onCreate method by:

    Intent intent = getIntent();
    intent.setExtrasClassLoader(WeatherData.class.getClassLoader());
    intent.setExtrasClassLoader(WeatherData.ForecastInfo.class.getClassLoader());
    ArrayList<WeatherData.ForecastInfo> list = intent.getParcelableArrayListExtra("bundleObject");

If I pass weatherData.getName() instead of weatherData.list in showDetails, it works but just for the string name of the city. When I try to put an ArrayList<ForecastInfo>, it breaks and raises this error:

Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.android.climapp.WeatherData$Temp at android.os.Parcel.readParcelableCreator(Parcel.java:2411) at android.os.Parcel.readParcelable(Parcel.java:2337) ...

Upvotes: 1

Views: 695

Answers (1)

nicodp
nicodp

Reputation: 2392

Solved!

To anyone who is struggling with this issue, it's because the new Bundle inside the createFromParcel method doesn't know about the ForecastInfo class. Thus, it could be solved telling the bundle to load the data of the missing class through setClassLoader like this:

       @Override
            public WeatherData createFromParcel(Parcel source) {
                // read the bundle containing key value pairs from the parcel
                Bundle bundle = source.readBundle();
                bundle.setClassLoader(ForecastInfo.class.getClassLoader());
                // instantiate the weather using values from the bundle
bundle.getParcelable("forecastList"));
                return new WeatherData(bundle.getString("cityName"),
                        new ArrayList<ForecastInfo>());

            }

Upvotes: 2

Related Questions