I am working with a map based application that creates an album view that includes a map and a web view. The issue occurs during rotation and had been working fine with com.android.support:support-v4:19.1, but is not working once I updated to 22.2.0.
The map appears as expected in the initial view, but after rotation, the map no longer appears. I verified the GoogleMap, container View (that holds the map), and SupportMapFragment are all valid.
While debugging, I decided to check the view of the SupportMapFragment to see if that might be the cause. After rotation, a call to getView() on the SupportMapFragment returns null (which I suspect is why the map does not appear, but I do not understand why it is valid initially and is null after rotation).
I am at a loss because as far as I can tell, I have a valid SupportMapFragment, a valid GoogleMap and a container view with the appropriate size (in this case 680x800). As I said above, with the previous version I was using 19.1, this code worked as expected.
Below are relevant snippets of the code:
SupportMapManager creation is in the EventReportAlbumActivity class
public class EventReportAlbumActivity extends FragmentActivity
{
@Nullable protected SupportMapFragment m_mapFragment;
@NotNull
public SupportMapFragment getMapFragment ()
{
if( m_mapFragment == null )
{
GoogleMapOptions options = new GoogleMapOptions();
options.compassEnabled( false );
options.rotateGesturesEnabled( false );
options.scrollGesturesEnabled( false );
options.tiltGesturesEnabled( false );
options.zoomControlsEnabled( false );
options.zoomGesturesEnabled( false );
m_mapFragment = SupportMapFragment.newInstance( options );
m_mapFragment.setRetainInstance( true );
}
return m_mapFragment;
}
}
The actual Fragment instance that displays the map and web view
public class EventAlbumItemFragment extends Fragment
{
@Nullable
protected SupportMapFragment m_mapFragment;
public static EventAlbumItemFragment newInstance ( )
{
final EventAlbumItemFragment fragment = new EventAlbumItemFragment();
fragment.setRetainInstance( true );
return fragment;
}
@Override
public View onCreateView ( @NotNull LayoutInflater inflater,
@NotNull ViewGroup container,
@Nullable Bundle savedInstanceState )
{
final View view = inflater.inflate( R.layout.event_album_item, container, false );
View mapContainer = view.findViewById( R.id.mapview_container );
// make map fragment view invisible until we configure it so the coordinates will be correct
mapContainer.setVisibility( View.INVISIBLE );
// other code removed for brevity...
}
/**
* Setter for the map fragment and its UI
*
* @param mapFragment The map fragment which should be added to this fragment's view, or null to remove the current fragment
*/
public void setMapFragment ( @Nullable SupportMapFragment mapFragment )
{
final FragmentManager fragmentManager = getChildFragmentManager();
if( m_mapFragment != null && m_mapFragment.isAdded() )
{
// Remove the existing map manager
fragmentManager.beginTransaction()
.remove( m_mapFragment )
.commit();
}
if( mapFragment != null )
{
// Add the map view to the current view
fragmentManager.beginTransaction()
.add( R.id.mapview_container, mapFragment, MAP_FRAGMENT_TAG )
.commit();
}
fragmentManager.executePendingTransactions();
m_mapFragment = mapFragment;
configureMap();
}
protected void configureMap ()
{
if ( m_mapFragment != null && m_eventMapFeature != null && m_eventMapFeature.haveValidLocation() == true )
{
final EventAlbumItemFragment localThis = this ;
m_mapFragment.getMapAsync( new OnMapReadyCallback()
{
@Override
public void onMapReady( GoogleMap googleMap )
{
final View mapContainer = getView().findViewById( R.id.mapview_container );
final int mapWidth = mapContainer.getWidth();
final int mapHeight = mapContainer.getHeight();
// we have our map and the coordinates should be correct. Make map visible and proceed
mapContainer.setVisibility( View.VISIBLE );
// This line below crashes because view is NULL after rotation.
// Left here to illustrate what I believe to be the root cause of the issue
// m_mapFragment.getView().setVisibility( View.VISIBLE );
// Remove all overlays from the map
googleMap.clear();
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
final float zoom = (float)GeoUtils.getZoomForMetersWide( MAP_MINIMUM_METER_SPAN,
mapWidth / displayMetrics.scaledDensity,
m_eventMapFeature.getCoordinate().latitude );
// Change the map bounds
CameraUpdate cameraUpdate;
// draw some items on the map
final LatLngBounds bounds = m_eventMapFeature.getBounds();
cameraUpdate = CameraUpdateFactory.newLatLngZoom( bounds.southwest, zoom );
googleMap.moveCamera( cameraUpdate );
}
} );
}
}
/** Assign the map fragment variable on resume. This is necessary for orientation change events */
@Override
public void onResume ()
{
super.onResume();
// If the map fragment hasn't been set, but this is the current item, get the map fragment from the parent
final EventReportAlbumActivity activity = (EventReportAlbumActivity)getActivity();
if( m_mapFragment == null && this == activity.getCurrentItemFragment() )
{
setMapFragment( activity.getMapFragment() );
}
//
configureMap();
}
}
UPDATE: setMapFragment above also crashes when there are multiple pages and the user pages through the album after rotation
After some more testing, I also tested this code when there are multiple pages in the album. Sans rotation, I can scroll through an album with no problems. However, if I rotate the device and attempt to scroll through the album, the app crashes. Again, using version 19 of the support library, this code works as expected. This only started happening after upgrading to version 22.
in the method setMapFragment() in EventAlbumItemFragment:
fragmentManager.executePendingTransactions();
I get the following stack trace:
java.lang.IllegalStateException: Could not execute method of the activity
at android.view.View$1.onClick(View.java:4020)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
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:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at android.view.View$1.onClick(View.java:4015)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
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:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.util.ArrayList.set(int, java.lang.Object)' on a null object reference
at android.support.v4.app.FragmentManagerImpl.makeInactive(FragmentManager.java:1192)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1099)
at android.support.v4.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1235)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:710)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1501)
at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:490)
at crc.carsapp.fragments.MapAlbumItemFragment.setMapFragment(MapAlbumItemFragment.java:78)
at crc.carsapp.listeners.OnMapAlbumScrollListener.onPageSelected(OnMapAlbumScrollListener.java:46)
at crc.carsapp.listeners.OnEventAlbumViewScrollListener.onPageSelected(OnEventAlbumViewScrollListener.java:34)
at android.support.v4.view.ViewPager.dispatchOnPageSelected(ViewPager.java:1786)
at android.support.v4.view.ViewPager.scrollToItem(ViewPager.java:568)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:552)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:513)
at android.support.v4.view.ViewPager.setCurrentItem(ViewPager.java:505)
at crc.carsapp.activities.AlbumActivity.scrollToPrevious(AlbumActivity.java:133)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at android.view.View$1.onClick(View.java:4015)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
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:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
So, there were 2 issues with the above code. First (and the most critical), the Activity (EventReportAlbumActivity) is responsible for the creation of the map fragment (and its associated fragments keep a reference to it). During rotation, the activity is (of course) destroyed and recreated. Thus, subsequent calls to getMapFragment will generate a new SupportMapFragment. Meanwhile, the fragments displaying the map still have a reference to the prior map fragment.
Whenever the call to remove was being made in setMapFragment, it was referencing the old SupportMapFragment and thus throwing an exception.
This also caused the map to not display properly after rotation.
The fix is to take out the call to remove in setMapFragment (the ViewPager handles the remove from the prior page in onPageSelected) and to add the 'new' SupportMapFragment in setMapFragment as needed.
How the original code was working before the upgrade to version 22 is a mystery as this appears to be a bug no matter the support version. And, the solution still does not feel quite right as SupportMapFragment has its setRetainInstance set to true which implies we want it to live throughout the lifecycle of the fragment, but the activity winds up overwriting it when it is recreated during rotation. So, if someone has a more elegant solution, I would love to hear it.
Relevant updated code is below: