Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to preserve/rebind event listeners on MapFragment after rotating the device (portrait / landscape)?

I am developing an Android app with Xamarin (version 7.1). It displays as map and draws PolyLines, doing so in OnCameraIdle().

The MapFragment is generated programmatically in OnCreate. I am fetching the GoogleMap in OnResume via GetMapAsync and binding the listeners in OnMapReady.
They work fine, but only in the beginning. As soon as the device is rotated (portrait -> landscape OR vice versa), the camera movement does not trigger the listeners any more.
The map works, however - I (the user) can still move the camera just fine. I (the app) just can't work with it anymore.

This is the bare code, only map creation and handling. Everything else (actual drawing) is removed:

public class MapActivity : Activity, IOnMapReadyCallback, 
    GoogleMap.IOnCameraIdleListener, GoogleMap.IOnCameraMoveStartedListener
{
    private GoogleMap _map;
    private MapFragment _mapFragment;

    private void InitializeMap()
    {
        _mapFragment = MapFragment.NewInstance();
        var tx = FragmentManager.BeginTransaction();
        tx.Add(Resource.Id.map_placeholder, _mapFragment);
        tx.Commit();
    }

    private void SetMapListeners()
    {
        Log.Debug("MyApp/ Map", "SetMapListeners");
        _map.SetOnCameraIdleListener(this);
        _map.SetOnCameraMoveStartedListener(this);
    }

    /* Activity */

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Log.Debug("MyApp / Map", "OnCreate");
        SetContentView(Resource.Layout.Map);
        InitializeMap();
    }

    protected override void OnStart()
    {
        base.OnStart();
        Log.Debug("MyApp / Map", "OnStart");
    }

    protected override void OnResume()
    {
        base.OnResume();
        if (_map == null)
            _mapFragment.GetMapAsync(this);
        Log.Debug("MyApp / Map", "OnResume");
    }

    protected override void OnPause()
    {
        base.OnPause();
        Log.Debug("MyApp / Map", "OnPause");
    }

    protected override void OnStop()
    {
        base.OnStop();
        Log.Debug("MyApp / Map", "OnStop");
    }

    protected override void OnDestroy()
    {
        base.OnStop();
        Log.Debug("MyApp/ Map", "OnDestroy");
    }


    /* IOnMapReadyCallback */   

    public void OnMapReady(GoogleMap googleMap)
    {
        Log.Debug("MyApp / Map", "Map is ready!");
        _map = googleMap;       
        SetMapListeners();
    }


    /* IOnCameraIdleListener */

    public void OnCameraIdle()
    {
        Log.Debug("MyApp / Map", "Camera is idle.");
        // Drawing routine is called here
    }


    /* IOnCameraMoveStartedListener */

    public void OnCameraMoveStarted(int reason)
    {
        Log.Debug("MyApp / Map", "Camera move started.");
    }
}

As you can see in the following log excerpt, the listeners do work in the beginning, but once the device is rotated (at least) once, they are gone.
I also tried calling SetMapListeners only once in the lifecycle, the first time OnMapReady is called, but that did not change anything.

04-03 20:29:06.486 D/MyApp / Map( 7446): OnCreate
04-03 20:29:06.688 I/Google Maps Android API( 7446): Google Play services client version: 10084000
04-03 20:29:06.695 I/Google Maps Android API( 7446): Google Play services package version: 10298438
04-03 20:29:07.394 D/MyApp / Map( 7446): OnStart
04-03 20:29:07.399 D/MyApp / Map( 7446): OnResume
04-03 20:29:07.432 D/MyApp / Map( 7446): Map is ready!
04-03 20:29:07.438 D/MyApp / Map( 7446): SetMapListeners
04-03 20:29:07.568 D/MyApp / Map( 7446): Camera is idle.
04-03 20:29:09.231 D/MyApp / Map( 7446): Camera move started.
04-03 20:29:09.590 D/MyApp / Map( 7446): Camera is idle.
04-03 20:29:12.350 D/MyApp / Map( 7446): Camera move started.
04-03 20:29:12.751 D/MyApp / Map( 7446): Camera is idle.

## Listeners are responding, now rotating the device.

04-03 20:29:15.503 D/MyApp / Map( 7446): OnPause
04-03 20:29:15.508 D/MyApp / Map( 7446): OnStop
04-03 20:29:15.572 D/MyApp / Map( 7446): OnDestroy
04-03 20:29:15.595 I/Google Maps Android API( 7446): Google Play services package version: 10298438
04-03 20:29:15.596 D/MyApp / Map( 7446): OnCreate
04-03 20:29:15.628 I/Google Maps Android API( 7446): Google Play services package version: 10298438
04-03 20:29:15.655 D/MyApp / Map( 7446): OnStart
04-03 20:29:15.655 D/MyApp / Map( 7446): OnResume
04-03 20:29:15.690 D/MyApp / Map( 7446): Map is ready!
04-03 20:29:15.691 D/MyApp / Map( 7446): SetMapListeners

## Map is rotated, camera position was preserved. 
## Now moving the camera, but no listeners are responding.

04-03 20:29:24.436 D/MyApp / Map( 7446): OnPause
04-03 20:29:31.288 D/MyApp / Map( 7446): OnStop
04-03 20:29:31.359 D/MyApp / Map( 7446): OnDestroy

The interesting thing here for me is that when I switch back to the previous activity and open the map once again, it starts fresh and works again. However, as you see in the log, while rotating the device, the activity is also destroyed and freshly created. As far as I know, the fragment is not, so maybe that's hint. I don't know.

I also tried removing the listeners in OnDestroy (by setting null), but that, too, did not change anything.

Have you got any idea what I might be doing wrong?

like image 990
Pharaoh Avatar asked Apr 03 '17 19:04

Pharaoh


2 Answers

I had similar issues, because we have the MapFragment nested within another Fragment, so we have to re-add the MapFragment everytime. Since you have it directly embedded in an Activity, @App Pack should also work, but I will paste the code anyway.

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();  //onCreateView() or onResume() should work too

    FragmentManager fragmentManager = ((AppCompatActivity) getContext()).getSupportFragmentManager();
    SupportMapFragment mapFragment = SupportMapFragment.newInstance();
    fragmentManager.beginTransaction()
        .replace(R.id.mapContainer, mapFragment)
        .commit();
    mapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    this.googleMap = googleMap;

    UiSettings uiSettings = googleMap.getUiSettings();
    uiSettings.setZoomControlsEnabled(false);

    //do your initialization + recreate your last map position from BundleSavedIntance: check this answer: http://stackoverflow.com/questions/16697891/google-maps-android-api-v2-restoring-map-state/16698624#16698624
}

And please: functions should start lowercase!!!

like image 45
longi Avatar answered Oct 12 '22 13:10

longi


Try putting the InitializeMap() call into the override of OnCreateView() method instead of OnCreate()

like image 108
App Pack Avatar answered Oct 12 '22 11:10

App Pack