Background
I have a larger application in which I had/have several problems with new Google Maps API. I tried to describe it in a different question but since it seems too complex I decided to start a new project, as simple as possible and try to reproduce problems. So here it is.
The situation
I'm using Fragments
and want to put MapView
inside. I don't want to use MapFragment
. The sample project I prepared may be not very beautiful but I tried to make it as simple as possible and it had to contain some elements (again simplified) from the original app.
I have one Activity
and my custom Fragment
with MapView
in it, added programatically. The Map
contains some points/Markers
. After clicking on a Marker
the InfoWindow
is shown and clicking on it causes next Fragment
being shown (with replace()
function) in content.
The problems
There are two issues I have:
When the
Map
withMarkers
is displayed screen rotation causesClass not found when unmarshalling
error with my customMyMapPoint
class - I have no idea why and what it means.I click the
Marker
and thenInfoWindow
. After this I press hardware back button. Now I can see theMap
but with noMarkers
and centered in0,0
point.
The code
MainActivity
public class MainActivity extends FragmentActivity {
private ArrayList<MyMapPoint> mPoints = new ArrayList<MyMapPoint>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mPoints.add(new MyMapPoint(1, new LatLng(20, 10),
"test point", "description", null));
mPoints.add(new MyMapPoint(2, new LatLng(10, 20),
"test point 2", "second description", null));
Fragment fragment = MyMapFragment.newInstance(mPoints);
getSupportFragmentManager().beginTransaction()
.add(R.id.contentPane, fragment).commit();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentPane"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
map_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.google.android.gms.maps.MapView
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MyMapFragment
public class MyMapFragment extends Fragment implements
OnInfoWindowClickListener {
public static final String KEY_POINTS = "points";
private MapView mMapView;
private GoogleMap mMap;
private HashMap<MyMapPoint, Marker> mPoints =
new HashMap<MyMapPoint, Marker>();
public static MyMapFragment newInstance(ArrayList<MyMapPoint> points) {
MyMapFragment fragment = new MyMapFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(KEY_POINTS, points);
fragment.setArguments(args);
return fragment;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
MyMapPoint[] points = mPoints.keySet().toArray(
new MyMapPoint[mPoints.size()]);
outState.putParcelableArray(KEY_POINTS, points);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Bundle extras = getArguments();
if ((extras != null) && extras.containsKey(KEY_POINTS)) {
for (Parcelable pointP : extras.getParcelableArrayList(KEY_POINTS)) {
mPoints.put((MyMapPoint) pointP, null);
}
}
} else {
MyMapPoint[] points = (MyMapPoint[]) savedInstanceState
.getParcelableArray(KEY_POINTS);
for (MyMapPoint point : points) {
mPoints.put(point, null);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.map_fragment, container, false);
mMapView = (MapView) layout.findViewById(R.id.map);
return layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mMapView.onCreate(savedInstanceState);
setUpMapIfNeeded();
addMapPoints();
}
@Override
public void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
public void onResume() {
super.onResume();
setUpMapIfNeeded();
mMapView.onResume();
}
@Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
};
private void setUpMapIfNeeded() {
if (mMap == null) {
mMap = ((MapView) getView().findViewById(R.id.map)).getMap();
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
mMap.setOnInfoWindowClickListener(this);
addMapPoints();
}
private void addMapPoints() {
if (mMap != null) {
HashMap<MyMapPoint, Marker> toAdd =
new HashMap<MyMapPoint, Marker>();
for (Entry<MyMapPoint, Marker> entry : mPoints.entrySet()) {
Marker marker = entry.getValue();
if (marker == null) {
MyMapPoint point = entry.getKey();
marker = mMap.addMarker(point.getMarkerOptions());
toAdd.put(point, marker);
}
}
mPoints.putAll(toAdd);
}
}
@Override
public void onInfoWindowClick(Marker marker) {
Fragment fragment = DetailsFragment.newInstance();
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.contentPane, fragment)
.addToBackStack(null).commit();
}
public static class MyMapPoint implements Parcelable {
private static final int CONTENTS_DESCR = 1;
public int objectId;
public LatLng latLng;
public String title;
public String snippet;
public MyMapPoint(int oId, LatLng point,
String infoTitle, String infoSnippet, String infoImageUrl) {
objectId = oId;
latLng = point;
title = infoTitle;
snippet = infoSnippet;
}
public MyMapPoint(Parcel in) {
objectId = in.readInt();
latLng = in.readParcelable(LatLng.class.getClassLoader());
title = in.readString();
snippet = in.readString();
}
public MarkerOptions getMarkerOptions() {
return new MarkerOptions().position(latLng)
.title(title).snippet(snippet);
}
@Override
public int describeContents() {
return CONTENTS_DESCR;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(objectId);
dest.writeParcelable(latLng, 0);
dest.writeString(title);
dest.writeString(snippet);
}
public static final Parcelable.Creator<MyMapPoint> CREATOR =
new Parcelable.Creator<MyMapPoint>() {
public MyMapPoint createFromParcel(Parcel in) {
return new MyMapPoint(in);
}
public MyMapPoint[] newArray(int size) {
return new MyMapPoint[size];
}
};
}
}
If you need to take a look at any other file - let me know. Here you can find a complete project, you just have to put your own Maps API KEY in AndroidManifest.xml
file.
EDIT
I managed to make the example even more simple and updated the code above.
Implements your fragment/activity with
GoogleMap.OnCameraChangeListener
and override methodonCameraChange
and into you can save the new camera position, andonResume
useCame here in search of any hints regarding
onSaveInstance
andNullPointerException
. I have tried various workarounds including the one mentioned by @Izydorr but have had no luck. The issue was withFragmentPagerAdapter
- the adapter of theViewPager
that was hosting my fragments that were embedding theMapView
s. After having it changed toFragmentStatePagerAdapter
the NPEs have gone away. Whew!Here is my fix to the first problem:
It seems that
Map
is trying to unparcel all the bundle, not just it's own information when I callmMap.onCreate(savedInstanceState)
and it has problem with it if I'm using my customParcelable
class. The solution that worked for me was removing my extras fromsavedInstanceState
as soon as I used them - before I call Map'sonCreate()
. I do it withsavedInstanceState.remove(MY_KEY)
. Another thing I had to do was to callmMap.onSaveInstanceState()
before adding my own information tooutState
inFragment's
onSaveInstanceState(Bundle outState)
function.And here's how I handled the second one:
I simplified the example project to the bare bones. I was adding raw
Markers
to the map and if I replace theFragment
with map with another one then after clicking "back" I still got "nulled" map. So I did two things:CameraPosition
inonPause()
function to restore it inonResume()
mMap
to null inonPause()
so when theFragment
comes back, theMarkers
are added again by theaddMapPoints()
function (I had to change it a little bit since I was saving and checkingMarkers
id's).Here are code samples:
...
...
And to update the camera position in
onResume()
I had to manually initialize maps. I did it insetUpMap()
:I realize that those aren't real solutions - just overrides but it's the best I can do for now and the project must go on. If anyone finds cleaner fixes I'll be grateful for letting me know about them.