10

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?

Imanuel
  • 3,596
  • 4
  • 24
  • 46
  • 1
    my one cent: on device rotation, both activity and fragment are destroyed and then freshly created. https://developer.android.com/guide/components/fragments.html#Lifecycle – user1506104 Apr 08 '17 at 18:24
  • 1
    Thank you. I had the unsubstantiated impression the Fragment would not be recreated, maybe because the viewport is kept, but this can be passed of course. – Imanuel Apr 10 '17 at 16:17
  • Try checking `savedInstanceState` to see if your Activity is being built for the first time or if it is just a return to it. You seem to be overlaping fragments at each call. – gustavogbc Apr 11 '17 at 01:02
  • 1
    Got it solved thanks to gustavogbc! "You seem to be overlapping fragments" - I added `ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize` to my activity, now it works. Checking `savedInstanceState` (only create the `MapFragment` if it is `null` did not help, though). Please do post this as an answer so I can give you the bounty. – Imanuel Apr 11 '17 at 10:13
  • Thanks Pharaoh. I just posted an answer. Hope you can do great with your app! – gustavogbc Apr 13 '17 at 14:02

4 Answers4

5

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

App Pack
  • 1,512
  • 10
  • 9
  • 1
    It seems to me that `OnCreateView` ist part of the class `Fragment`. Are you saying I should extend `MapFragment`? – Imanuel Apr 10 '17 at 16:15
  • No, on MapActivity create an override for OnCreateView (which should be available). And put the call to initializemap instead of the current call in OnCreate – App Pack Apr 10 '17 at 18:50
  • Ok, `OnCreateView` is part of `Activity`, I didn't see that. But it doesn't help - camera location & zoom is preserved (as before, with `OnCreate`), but no listener is triggered after rotating the device. – Imanuel Apr 12 '17 at 20:51
4

By default, when the screen is rotated your Activity is killed and restarted. To make sure no data is lost, you need to properly save and restore your data using the lifecycle methods. See SavingPersistentState or savedInstanceState and Follow this Android Developer Documentation Hope This May helps.!

Atif AbbAsi
  • 5,633
  • 7
  • 26
  • 47
  • There is no data to be lost - the only thing that needs to be kept would be the camera position, but that is kept automatically. It's only the reattaching of the listeners that did not work with the code above. – Imanuel Apr 22 '17 at 06:22
4

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!!!

longi
  • 11,104
  • 10
  • 55
  • 89
  • 1
    In C#/Xamarin the functions start uppercase. Your code basically calls `InitializeMap()` in `OnAttachedToWindow` - I tried that now, but behaviour is the same. Your comment at the end surprises me a bit, because when I rotate the device, the map position is preserved. I assumed the `MapFragment` does this automatically. – Imanuel Apr 12 '17 at 21:01
  • Oh sorry, didn't saw the Xamarin-tag. Please ignore my comment. – longi Apr 18 '17 at 10:13
2

Try checking savedInstanceState to see if your Activity is being built for the first time or if it is just a return to it. You seem to be overlaping fragments at each call.

gustavogbc
  • 695
  • 11
  • 33
  • Thanks - although not 100% the answer, this pointed me in the right direction. Checking `savedInstanceState` and only creating the `MapFragment` if it is `null` did not help, but to avoid overlapping the fragments I added `ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize` to my activity. Now it works. – Imanuel Apr 13 '17 at 14:13