Issue with loading mobile map package .NET for Android Xamarin

1715
4
07-03-2017 11:26 AM
YifanLiu
New Contributor II

Hi,

I am developing a Xamarin app for Android using the arcGIS .NET for Android SDK. I am trying to load a mobile map package with this method shown below. My problem is that on line 19, the OpenAsync never return and the await never yield the control to the caller of this LoadMMPK() method. My application gets stuck here. 

A bit of explanation of the code: The mobile map package is in the Asset folder of my Android project, which gets copied to the android device (line 6 to 16). Then I call the OpenAsync on line 19 to load the mmpk. I checked the Android device, the map package was successfully written into the device, and the mmpkPath passed into the OpenAsync method is valid and points to the right mobile map package.

Anyone can help me point out what might be the cause of my issue here? Thank you

  private async Task<MobileMapPackage> LoadMMPK()
        {
            try
            {
                var mmpkName = "map.mmpk";
                AssetManager assestManager = this.Assets;

                // Open the map package asset
                using (var packageAsset = Assets.Open(mmpkName))
                {
                    // Copy it to a writable location
                    using (var packageTarget = OpenFileOutput(mmpkName, FileCreationMode.WorldWriteable))
                    {
                        packageAsset.CopyTo(packageTarget);
                    }
                }

                var mmpkPath = GetFileStreamPath(mmpkName).AbsolutePath;
                MobileMapPackage mmpk = await MobileMapPackage.OpenAsync(mmpkPath);
                
                return mmpk;
            }
            catch (FileNotFoundException ex)
            {
                Console.Write(ex.Message);
                return null;
            }
        }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
4 Replies
NagmaYasmin
Occasional Contributor III

Hi Yifan,

I haven't used Android but using WPF I am able to see the MMPK file. Once you open the MMPK file using OpenAsync() method, get the map layers from MMPK file as below and add it to the MapView:

MobileMapPackage mmpk = await MobileMapPackage.OpenAsync(<location_MMPK_File>);
Map map = mmpk.Maps[0];

MyMapView.Map = map;

Hope that helps. 

Nagma

0 Kudos
GregDeStigter
Esri Contributor

Yifan_Liu-esristaff‌ I'm using your code almost exactly in an Android test app that I have and it seems to be working as I expect. Perhaps it's related to your MMPK file. Could you try another and see if it works?

Here's the bulk of my test code for reference:

[Activity(Label = "LocalData.Droid", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
    protected override async void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        var mmpk = await OpenMmpkAsync();
        SetContentView(new MapView() { Map = mmpk.Maps.FirstOrDefault() });
    }

    private async Task<MobileMapPackage> OpenMmpkAsync()
    {
        try
        {
            var mmpkName = "multiplemaps.mmpk";
            using (var mmpkAsset = Assets.Open(mmpkName))
            using (var mmpkTarget = OpenFileOutput(mmpkName, FileCreationMode.WorldWriteable))
            {
                mmpkAsset.CopyTo(mmpkTarget);
            }
            var mmpkPath = GetFileStreamPath(mmpkName).AbsolutePath;
            return await MobileMapPackage.OpenAsync(mmpkPath);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
            return null;
        }
    }
}
0 Kudos
YifanLiu
New Contributor II

Thanks a lot @Greg DeStigter 

the mmpk file works. I've been trying different things and found out that adding ConfigureAwait(false) to the OpenAsync(mmpkPath) fixes this issue. See my new code below, note the change on line 26:

I am still researching why I have to add ConfigureAwait here. If you know the reason, can you please share it here?


    private async Task<MobileMapPackage> OpenMmpkAsync()
    {
        try
        {

                //string filePath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "map.mmpk");

                //MobileMapPackage mmpk = await MobileMapPackage.OpenAsync(filePath);
                //var mmpk = await MobileMapPackage.OpenAsync(Path.Combine(DownloadViewModel.GetDataFolder(), AppSettings.CurrentSettings.PortalItemName));
                var mmpkName = "map.mmpk";
                AssetManager assestManager = this.Assets;

                // Open the map package asset
                using (var packageAsset = Assets.Open(mmpkName))
                {
                    // Copy it to a writable location
                    using (var packageTarget = OpenFileOutput(mmpkName, FileCreationMode.WorldWriteable))
                    {
                        packageAsset.CopyTo(packageTarget);
                    }
                }

                var mmpkPath = GetFileStreamPath(mmpkName).AbsolutePath;
                
                return await MobileMapPackage.OpenAsync(mmpkPath).ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
            return null;
        }
    }

Greg DeStigter wrote:

Yifan Liu I'm using your code almost exactly in an Android test app that I have and it seems to be working as I expect. Perhaps it's related to your MMPK file. Could you try another and see if it works?

 

Here's the bulk of my test code for reference:

[Activity(Label = "LocalData.Droid", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
    protected override async void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        var mmpk = await OpenMmpkAsync();
        SetContentView(new MapView() { Map = mmpk.Maps.FirstOrDefault() });
    }

    private async Task<MobileMapPackage> OpenMmpkAsync()
    {
        try
        {
            var mmpkName = "multiplemaps.mmpk";
            using (var mmpkAsset = Assets.Open(mmpkName))
            using (var mmpkTarget = OpenFileOutput(mmpkName, FileCreationMode.WorldWriteable))
            {
                mmpkAsset.CopyTo(mmpkTarget);
            }
            var mmpkPath = GetFileStreamPath(mmpkName).AbsolutePath;
            return await MobileMapPackage.OpenAsync(mmpkPath);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
            return null;
        }
    }
}
0 Kudos
YifanLiu
New Contributor II

After researching on the await and async programming of C#, I realized my problem was because of a deadlock. setting the configureAwait(false) avoids the deadlock by allowing the MobileMapPackage.openAsync to pick up an idle thread from the threadpool instead of waiting for the UI SynchronizationContext. See the quote below on this issue:

The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.

Link is here: Async/Await - Best Practices in Asynchronous Programming 

0 Kudos