Support MapMapFragment disappears on rotation

I am working with a mapping application that creates an album view that includes a map and a web view. The problem occurs during rotation and works fine with com.android.support:support-v4:19.1, but doesn't work after upgrading to 22.2.0.

The map is displayed as expected in its original view, but after rotation, the map is no longer displayed. I have checked GoogleMap, Container View (which contains the map), and SupportMapFragment are all valid.

While debugging, I decided to check the SupportMapFragment view to see if that might be the cause. After the rotation, the getView () call in the SupportMapFragment returns null (I suspect why the map is not displayed, but I don't understand why it is really native and null after rotation).

I am at a loss as, 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 used 19.1, this code worked as expected.

Below are the relevant code snippets:

The 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 instance of the fragment showing 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 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 after rotating multiple pages and custom pages through the album

After some more tests, I also checked this code when there are multiple pages in the album. Sans rotation, I can scroll through the album without any problem. However, if I rotate the device and try to scroll through the album, the app crashes. Again, using version 19 of the support library, this code works as expected. It only started after updating to version 22.

in the setMapFragment () method in EventAlbumItemFragment:

fragmentManager.executePendingTransactions();

      

I am getting 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)

      

+3


source to share


1 answer


So there were 2 problems with the above code. First (and most critical), Activity (EventReportAlbumActivity) is responsible for creating the map fragment (and its associated fragments contain a link to it). During the rotation, the activity is (of course) destroyed and recreated. Thus, subsequent calls to getMapFragment will generate a new SupportMapFragment. Meanwhile, the map tiles still have a link to the previous map tile.

Whenever a call to remove was made on setMapFragment, it would reference the old SupportMapFragment and thus throw an exception.

It also caused the map to not display properly after spinning.

The fix is ​​to take out the call to delete in setMapFragment (ViewPager handles deletion from the previous page in onPageSelected) and add a "new" SupportMapFragment to setMapFragment as needed.



How the original code worked prior to the upgrade to version 22 is a mystery as it appears to be a bug regardless of the support version. And the solution is still not entirely correct, as the SupportMapFragment has setRetainInstance set to true, which implies that we want it to live for the entire life of the fragment, but the activity finishes overwriting when it is recreated during rotation. So, if anyone has a more elegant solution, I'd love to hear this.

The relevant updated code is below:

public class EventAlbumItemFragment extends Fragment
{
   public void setMapFragment ( @Nullable SupportMapFragment mapFragment )
  {
     final FragmentManager fragmentManager = getChildFragmentManager();

     // The working assumption is that our map fragment, if already added, is added to
     // our current fragment.
     if ( mapFragment != null && mapFragment.isAdded() == false )
     {
        // 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();
  }

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

     // We can get here in (at least) 3 different ways:
     // 1. The first time this fragment is created.
     //       In this case, we will not have a local reference to a map fragment and will will add
     //       it if we are the currently displaying fragment.
     // 2. After rotation
     //       Because the actual map fragment is maintained by our activity, the map fragment will
     //       have been recreated and our reference to it will no longer be valid.
     // 3. After a pause event (such as the device going into power saving mode).
     //       We should still have a reference to the current map fragment and it should be
     //       valid. We can test that by checking if the map fragment is attached. This is done
     //       in the setMapFragment() method.
     //
     final AlbumActivity activity = (AlbumActivity)getActivity();

     Fragment currentFragment = activity.getCurrentItemFragment() ;

     if ( m_mapFragment == null && this == currentFragment )
     {
        setMapFragment( activity.getMapFragment() );
     }
     else if ( m_mapFragment != null )
     {
        setMapFragment( activity.getMapFragment() );
     }

     configureMap();
   }
}

      

+1


source







All Articles