rofl rofl - 3 months ago 69
Android Question

MapView crashes in fragment with Coordinator layout, when changing configuration

I have an issue with my custom fragment with

MapView
and
CoordinatorLayout
. I have made a simple application to illustrate the same. I have also tried to do the same on a standard Google API demo, and no issue occurred there, but probably that is because they are using
MapFragment
but not
MapView
.

Below, I have uploaded the code and layouts. At the end there is an error trace also been pasted.

public class BlankFragment extends Fragment implements OnMapReadyCallback {

private MapView mapView;
private Bundle BundleForMap;

@Override
public void onMapReady(GoogleMap googleMap) { }

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BundleForMap = savedInstanceState;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_blank, container, false);

mapView = (MapView) view.findViewById(R.id.map_event_mapview);
mapView.onCreate(BundleForMap);
mapView.getMapAsync(this);
return view;
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
}

@Override
public void onDetach() {
super.onDetach();
}

@Override
public void onResume() {
super.onResume();
mapView.onResume();
}

@Override
public void onPause() {

super.onPause();
mapView.onPause();
}

@Override
public void onDestroy() {
mapView.onDestroy();
super.onDestroy();
}

@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
mapView.onSaveInstanceState(savedInstanceState);
super.onSaveInstanceState(savedInstanceState);
}

}


Activity simply just calls that fragment and replaces container view with it:

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

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

BlankFragment blankFragment = new BlankFragment();
fragmentTransaction.replace(R.id.container, blankFragment, "");

fragmentTransaction.commit();

}


And here is the fragment layout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mapview_test.BlankFragment">

<com.google.android.gms.maps.MapView
android:id="@+id/map_event_mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<android.support.design.widget.CoordinatorLayout
android:id="@+id/map_event_bottom_sheet"
android:layout_height="match_parent"
android:layout_width="match_parent" >

<android.support.v4.widget.NestedScrollView
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="175dp"
android:elevation="16dp"
android:outlineProvider="bounds"
android:background="@android:color/white"
>

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:textSize="16sp"
android:id="@+id/testText"/>

</android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

</FrameLayout>


The problem is obviously with Coordinator layout and how it is Parceled.

Process: com.example.alovita.mapview_test, PID: 12989
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.alovita.mapview_test/com.example.alovita.mapview_test.MainActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.design.widget.CoordinatorLayout$SavedState
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2370)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2432)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3992)
at android.app.ActivityThread.access$1000(ActivityThread.java:154)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1327)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5310)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.design.widget.CoordinatorLayout$SavedState
at android.os.Parcel.readParcelableCreator(Parcel.java:2295)
at android.os.Parcel.readParcelable(Parcel.java:2245)
at android.os.Parcel.readValue(Parcel.java:2152)
at android.os.Parcel.readSparseArrayInternal(Parcel.java:2546)
at android.os.Parcel.readSparseArray(Parcel.java:1874)
at android.os.Parcel.readValue(Parcel.java:2209)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2485)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getBundle(Bundle.java:733)
at aac.a(:com.google.android.gms.DynamiteModulesB:74)
at maps.ad.u.a(Unknown Source)
at maps.ad.R.a(Unknown Source)
at wc.onTransact(:com.google.android.gms.DynamiteModulesB:66)
at android.os.Binder.transact(Binder.java:380)
at com.google.android.gms.maps.internal.IMapViewDelegate$zza$zza.onCreate(Unknown Source)
at com.google.android.gms.maps.MapView$zza.onCreate(Unknown Source)
at com.google.android.gms.dynamic.zza$3.zzb(Unknown Source)
at com.google.android.gms.dynamic.zza$1.zza(Unknown Source)
at com.google.android.gms.maps.MapView$zzb.zzbow(Unknown Source)
at com.google.android.gms.maps.MapView$zzb.zza(Unknown Source)
at com.google.android.gms.dynamic.zza.zza(Unknown Source)
at com.google.android.gms.dynamic.zza.onCreate(Unknown Source)
at com.google.android.gms.maps.MapView.onCreate(Unknown Source)
at com.example.alovita.mapview_test.BlankFragment.onCreateView(BlankFragment.java:45)
at android.app.Fragment.performCreateView(Fragment.java:2053)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:894)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1067)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1049)
at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1869)
at android.app.Activity.performCreateCommon(Activity.java:6860)
at android.app.Activity.performCreate(Activity.java:6867)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2323)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2432) 
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3992) 
at android.app.ActivityThread.access$1000(ActivityThread.java:154) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1327) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:135) 
at android.app.ActivityThread.main(ActivityThread.java:5310) 
at java.lang.reflect.Method.invoke(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:372) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699) 


The problem is with this line:

mapView.onCreate(BundleForMap);


When screen configuration changes, this parameter is obviously not
null
and app crashed. When passing here
null
instead of bundle, it works fine. When I remove
CoordinatorLayout
, app works fine.

Here are my dependencies:

android {
compileSdkVersion 24
buildToolsVersion "24.0.1"

defaultConfig {
applicationId "com.example.mapview_test"
minSdkVersion 22
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:support-v4:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
compile 'com.google.android.gms:play-services-appindexing:9.4.0'
compile 'com.google.android.gms:play-services-maps:9.4.0'
compile 'com.google.android.gms:play-services-location:9.4.0'
}


I am stuck with this from long time. Any help would be appreciated

Answer

It seems this is well known issue: Android Issue 6237

I have implemented my own solution based on this answer: Android Issue 6237 #C9

Implementation is as follows (based on #C9 answer):

I was able to get around it by saving the MapView's saved state on a separate Bundle and then adding it to the outgoing saved state bundle. Then in onCreate(), I would just grab the MapView's saved state from the incoming one and pass it into its onCreate() method, thus stopping the crashes.

@Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        final Bundle mapViewSaveState = new Bundle(savedInstanceState);
        mapView.onSaveInstanceState(mapViewSaveState);
        savedInstanceState.putBundle("mapViewSaveState", mapViewSaveState);

        Bundle customBundle = new Bundle();
        // put custom objects if needed to customBundle

        savedInstanceState.putBundle(ARG_CUSTOM_BUNDLE, customBundle);
        super.onSaveInstanceState(savedInstanceState);
    }

And then, use that mapViewSaveState in onCreate (or onCreateView if it is a fragment):

mapView.onCreate(savedInstanceState != null ? savedInstanceState.getBundle("mapViewSaveState") : null);
Comments