Solving the Xamarin.Forms.Maps permission problem

Iede Snoek
November 6, 2019 0 Comment

I was busy working on a Xamarin app which needed to have a map showing the users current position. Therefore I googled and found a wonderful package called Xamarin.Forms.Maps. I followed the instructions and got this rather bewildering message:

It turns out that a map is loaded asynchronously, and the permissionrequest is done synchronously (that is at least as I understand it).

Now, how to solve this problem?

The easieast way would be to not ask for the userlocation at all. However, for many apps this is not sufficient as many are reliant on that location.

Then I remembered that there is always an App in Xamarin, which you could think of as a kind of coordinator for everything. Maybe signalling the permission granted (or the permission denied as the user wishes) would be a good idea.

But how to get hold of the App in your Android’s app MainActivity? That turned out to be more straightforward than I thought, just put this in your MainActivity (in your Android project of course):

private App parentApp;

Before I put this in I followed all the instructions on https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/map/setup

However, we still need a way to communicate the fact that we got the permissions to the main app. How do we do that? Since Xamarin.Forms works with both Android and iOS (and technically UWP too, but that is outside the scope of this article) we need to generalize some permissions, like this

public enum GeneralPermission
{
  Camera,
  Location,
  Internet
}

I know, these are not all of the permission, but just the most frequent ones in my experience. Feel free to extend this enum.

Next we need an interface our mainActivity can talk to.

public interface IPermissionsChanged
{
   void permissionsChanged(GeneralPermission[] changedPermissions);
}

Now we to add the interface to the App class

public partial class App : Application,IPermissionsChanged

And of course, we need to implement it as well

public void permissionsChanged(GeneralPermission[] changedPermissions)
{
  if (changedPermissions.Any())
  {
    if (changedPermissions.Contains(GeneralPermission.Location))
    {
      if (MainPage is MainPage)
      {
        var page = MainPage as MainPage;
        page.toggleUserOnMap();
      }
     }
  }
}

However, neither the App nor the MainActivity (nor the AppDelegate in iOS) have access to the map-element on the main page (as it should be). Therefore we need to implement a method on the main page as follows:

public void toggleUserOnMap()
{
  this.userMap.IsShowingUser = true;
}

In this ‘userMap’ is the name of the map-element on my page. This has been set to false initially.

However we still need to do some things in the Android project’s MainActivity.

protected override void OnCreate(Bundle savedInstanceState)
{
 TabLayoutResource = Resource.Layout.Tabbar;
 ToolbarResource = Resource.Layout.Toolbar;

 base.OnCreate(savedInstanceState);

 Xamarin.Essentials.Platform.Init(this, savedInstanceState);
 global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
 Xamarin.FormsMaps.Init(this,savedInstanceState);
 this.parentApp = new App();
 LoadApplication(parentApp);
}

As you can see, apart from the standard map initialization code, we also set our parentApp field and use it to load the application.

However in the OnRequestPermission method, quite a lot has changed:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
 if (requestCode == RequestLocationId)
 {
   if ((grantResults.Length == 2) && (grantResults[0] == (int) Android.Content.PM.Permission.Granted))
   {

      var parent = parentApp as IPermissionsChanged;
                    
      parent.permissionsChanged(new GeneralPermission[]{GeneralPermission.Location});
    }
                
   }
   else
   {
                
     base.OnRequestPermissionsResult(requestCode,permissions,grantResults);
   }
}

Since we are asking for two permission here (coarse location and fine location) we need to check the length is 2 and that the permissions have been granted. If so, the method on the App class is invoked and the mainpage gets set up accordingly.

What about iOS?

Well, this problem doesn’t seem to exist for iOS but I might be wrong. I therefore could change the MainPage constructor as follows:

public MainPage()
{
 InitializeComponent();
 if (Device.RuntimePlatform == Device.iOS)
 {
  this.userMap.IsShowingUser = true;
 }
}

This just checks whether it is running on iOS and sets the map property accordingly.

Well, I hope you like this solution. It might not be the most elegant solution (I am open to other ideas, naturally).

A very small example project using this technique can be found at https://github.com/isnoek/MapsPermissions