15

Running slightly modified example of Google Maps throws BadParcelableException in the Google Maps code. The LatLng class is parcelable but it cannot be found. It seems that Google Maps code is trying to unparcel the object that was not parcelled by it. What cases the problem?

package com.example.mapdemo;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import android.os.Bundle;

public class RawMapViewDemoActivity extends android.support.v4.app.FragmentActivity {
    private MapView mMapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.raw_mapview_demo);

        mMapView = (MapView) findViewById(R.id.map);
        mMapView.onCreate(savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mMapView.onSaveInstanceState(outState);

        outState.putParcelable("marker", new LatLng(0, 0));
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        LatLng ll = savedInstanceState.getParcelable("marker");
    }
}

...

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity 
    ComponentInfo{com.example.mapdemo/com.example.mapdemo.RawMapViewDemoActivity}: 
    android.os.BadParcelableException: ClassNotFoundException when unmarshalling: 
    com.google.android.gms.maps.model.LatLng
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
   at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:2832)
   at android.app.ActivityThread.access$1600(ActivityThread.java:117)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)
   at android.os.Handler.dispatchMessage(Handler.java:99)
   at android.os.Looper.loop(Looper.java:130)
   at android.app.ActivityThread.main(ActivityThread.java:3683)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
   at dalvik.system.NativeStart.main(Native Method)

Caused by: android.os.BadParcelableException: 
ClassNotFoundException when unmarshalling: com.google.android.gms.maps.model.LatLng
   at android.os.Parcel.readParcelable(Parcel.java:1958)
   at android.os.Parcel.readValue(Parcel.java:1846)
   at android.os.Parcel.readMapInternal(Parcel.java:2083)
   at android.os.Bundle.unparcel(Bundle.java:208)
   at android.os.Bundle.getBundle(Bundle.java:1078)
   at com.google.android.gms.maps.internal.MapStateHelper
       .getParcelableFromMapStateBundle(MapStateHelper.java:41)
   at maps.y.ae.a(Unknown Source)
   at maps.y.bm.onCreate(Unknown Source)
   at com.google.android.gms.maps.internal.IMapViewDelegate$Stub
       .onTransact(IMapViewDelegate.java:66)
   at android.os.Binder.transact(Binder.java:279)
   at com.google.android.gms.maps.internal.IMapViewDelegate$a$a
       .onCreate(Unknown Source)
   at com.google.android.gms.maps.MapView$b.onCreate(Unknown Source)
   at com.google.android.gms.internal.c$3.a(Unknown Source)
   at com.google.android.gms.internal.i.b(Unknown Source)
   at com.google.android.gms.maps.MapView$a.a(Unknown Source)
   at com.google.android.gms.maps.MapView$a.a(Unknown Source)
   at com.google.android.gms.internal.c.a(Unknown Source)
   at com.google.android.gms.internal.c.onCreate(Unknown Source)
   at com.google.android.gms.maps.MapView.onCreate(Unknown Source)
   at com.example.mapdemo.RawMapViewDemoActivity
       .onCreate(RawMapViewDemoActivity.java:40)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
   ... 12 more
JJD
  • 50,076
  • 60
  • 203
  • 339
user1611728
  • 446
  • 6
  • 13
  • where is RawMapViewDemoActivity.java:40 line in your code – ρяσѕρєя K Dec 16 '12 at 10:21
  • mMapView.onCreate(savedInstanceState); Sorry I edited the code for brevity. – user1611728 Dec 16 '12 at 10:49
  • if you comment `mMapView.onCreate(savedInstanceState);` line then it's working or not? – ρяσѕρєя K Dec 16 '12 at 10:53
  • This line is needed to initialize the maps. Without it code throws other exceptions and eventually removing more code you end up with a blank screen. – user1611728 Dec 16 '12 at 15:51
  • 1
    Any luck with this? I'm having the same issue with a custom class that implements Parcelable and used to unmarshall just fine before I introduced these new maps. I ended up adding an empty override of onConfigurationChanged to prevent this from happening when changing the orientation of the device. I also am passing null into MapView.onCreate when the activity is restored after being shut down by the system. This causes some Markers, etc. to disappear, but there is no exception, and everything is restored when the user starts interacting with the map again. – Jeff Hillman Feb 21 '13 at 04:37
  • I reported this in the Google Maps issue tracker at: https://code.google.com/p/gmaps-api-issues/issues/detail?id=5083 – louielouie Mar 08 '13 at 17:52

6 Answers6

30

Just to add to the existing answers here, I had been removing my Parcels from the saved state before calling mapView.onCreate and it was working fine.

However after adding a ViewPager to my Fragment I didn't realise that the BadParcelableException had returned and the code made it to production. It turns out that the ViewPager saves its state too and, because it's part of the Support Library, the Google Map cannot find the class to unparcel it.

So I opted to invert the process, instead of removing Parcels from the Bundle that I knew about, I opted to create a new Bundle for the map copying over only the map's state.

private final static String BUNDLE_KEY_MAP_STATE = "mapData";

@Override
public void onSaveInstanceState(Bundle outState) {
    // Save the map state to it's own bundle
    Bundle mapState = new Bundle();
    mapView.onSaveInstanceState(mapState);
    // Put the map bundle in the main outState
    outState.putBundle(BUNDLE_KEY_MAP_STATE, mapState);
    super.onSaveInstanceState(outState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_map, container, false);
    mapView = (MapView) view.findViewById(R.id.mapView);
    mapView.getMapAsync(this);

    Bundle mapState = null;
    if (savedInstanceState != null) {
        // Load the map state bundle from the main savedInstanceState
        mapState = savedInstanceState.getBundle(BUNDLE_KEY_MAP_STATE);
    }

    mapView.onCreate(mapState);
    return view;
}
Ahorner
  • 214
  • 5
  • 13
darnmason
  • 2,672
  • 1
  • 17
  • 23
12

I tried the two previous answers without success, but I found another workaround :

When saving the state, be careful to forward the MapView.onSaveInstanceState call before adding your data to the Bundle (you did it well) :

@Override
public void onSaveInstanceState(Bundle outState) {

    // Forward the call BEFORE adding our LatLng array, else it will crash :
    _mapView.onSaveInstanceState(outState);

    // Put your Parcelable in the bundle:
    outState.putParcelableArray("myLatLng", new LatLng(0, 0) );
}

When restoring the state in the onCreate / onRestore, check if the bundle is not null, if so, get your parcel, and then remove it from the bundle before forwarding the call :

@Override
public void onCreate(Bundle savedInstanceState) {

    // If the bundle is not null, get your Parcelable :
    LatLng myLatLng = null;
    if(savedInstanceState != null) {

        myLatLng = (LatLng) savedInstanceState.getParcelable("myLatLng");

        // Remove your Parcelable from the bundle, else it will crash :
        savedInstanceState.remove("myLatLng");
    }

    // Forward the call :
    _mapView.onCreate(savedInstanceState);
    setUpMapIfNeeded();
}
Tim Autin
  • 6,043
  • 5
  • 46
  • 76
3

This issue as been filed on code.google.com here in case you would want to star it.

In the mean time I've manage to get around this issue by simply adding my Parcelable in to a Bundle before adding it to the final onSaveInstanceState Bundle. This works because a Bundle is a known class by the MapView's internal ClassLoader.

I've create two very small util method to do this for me. Here's the code

public static Parcelable unbundleParcelable(String key, Bundle src) {
    Bundle b = src.getBundle(key);
    if (b != null) {
        return b.getParcelable("bundle_parcelable_util_key");
    }
    return null;
}

public static void bundleParcelable(String key, Bundle dest, Parcelable parcelable) {
    Bundle b = new Bundle();
    b.putParcelable("bundle_parcelable_util_key", parcelable);
    dest.putBundle(key, b);
}

I've modified the code in one of the previous post to use my temporary solution. Here's how I use it.

@Override
public void onSaveInstanceState(Bundle outState) {

    // Forward the call BEFORE adding our LatLng array, else it will crash :
    _mapView.onSaveInstanceState(outState);

    // Put your Parcelable in the bundle:
    bundleParcelable("myLatLng", outState, new LatLng(0, 0));
}

@Override
public void onCreate(Bundle savedInstanceState) {
    // Forward the call :
    _mapView.onCreate(savedInstanceState);

    LatLng myLatLng = null;
    if(savedInstanceState != null) {
        // Extract your Parcelable
        myLatLng = (LatLng) unbundleParcelable("myLatLng", savedInstanceState);
    }

    setUpMapIfNeeded();
}

This should work for any custom Parcelable that you use in your project.

Miguel
  • 19,793
  • 8
  • 56
  • 46
0

I found a workaround (kinda). In the place when it happens just try to do it for second time. It always catches the Exception and the second time the problem does not seem to happen in this place. That worked for me.

try {
    mapIntent.putExtra(Intents.EXTRA_LATLNG, new LatLng(
        store.getLatitude(), store.getLongitude()));
} catch (BadParcelableException e) {
    mapIntent.putExtra(Intents.EXTRA_LATLNG, new LatLng(
        store.getLatitude(), store.getLongitude()));
}
Yaroslav Mytkalyk
  • 16,950
  • 10
  • 72
  • 99
0

I have found a simpler workaround to this. Providing your Fragment or Activity's class loader to the bundle before perfoming any operations as such:

savedInstanceState.setClassLoader(getClass().getClassLoader());

This does not seem to work when passing directly into the MapFragment or MapView's onCreateView or onCreate though, so you'll manually have to parcel out the map's CameraPosition and pass a null bundle into those functions.

purdyk
  • 16
  • 1
0

I also had a BadParcelableException when I was leaving my app (FragmentActivity with tabs and a Fragment in each tab). I solved this by calling first mapmpvMap.onSaveInstanceState(outState); and then super.onSaveInstanceState(outState);

Fr4nz
  • 1,616
  • 6
  • 24
  • 33