Xamarin.Forms: Integrating Maps and Location Services

Intro

Within Xamarn.Forms, we may find ourselves having to implement a feature that’s dependent on a map and/or GPS. In order to implement this for Android and provide other controls to manage related services, we must implement a native Android page within our Xamarin.Forms app. In this post I will demonstrate how to load a native android page that contains a map that leverages GPS to identify a phones location.

Steps

1. Get access to the Google Maps API

2. Add a new class to your Xamarin.Forms project and call it “CustomPage”.
Solution Explorer:

SolutionExplorer_customPage

Class:

using Xamarin.Forms;
 
namespace Pickup
{
    public class CustomPage : ContentPage { }
}

2. In your Xamarin.Forms project, add a XAML file.

SolutionExplorer_xamlFile

Implement the following XAML:

<?xml version="1.0" encoding="utf-8" ?>
<local:CustomPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps" xmlns:local="clr-namespace:Pickup;assembly=Pickup" x:Class="Pickup.MapPage" />

3. Add a Layout folder to your Android project as well as an axml file.

SolutionExplorer_axmlFile

4. Implement the axml file as the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:layout_width="200dp" android:layout_height="200dp" class="com.google.android.gms.maps.MapFragment" />

  <TextView android:id="@+id/textView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="top"/>
  
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <Button android:id="@+id/button_code" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Locate Me!" />
</LinearLayout>

5. Add a new class named LocationService under your Android project:

LocationService

Provide the following implementation:

    using static Bizmonger.Patterns.MessageBus;

    public class LocationService : Java.Lang.Object, ILocationListener
    {
        LocationManager _locationManager = null;

        public void GetLocation()
        {
            _locationManager = Application.Context.GetSystemService(Context.LocationService) as LocationManager;

            var locationCriteria = new Criteria();

            locationCriteria.Accuracy = Accuracy.Coarse;
            locationCriteria.PowerRequirement = Power.Medium;

            var locationProvider = _locationManager.GetBestProvider(locationCriteria, true);

            if (locationProvider != null)
            {
                _locationManager.RequestLocationUpdates(locationProvider, 2000, 1, this);
            }
        }

        public void OnLocationChanged(Location location) =>
            Publish(Messages.LOCATION_CHANGED, new Core.Location(location.Latitude, location.Longitude));

        public void OnProviderDisabled(string provider)
        {
            throw new NotImplementedException();
        }

        public void OnProviderEnabled(string provider)
        {
            throw new NotImplementedException();
        }

        public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
        {
            throw new NotImplementedException();
        }
    }

6. Add a custom renderer to your Android project by adding a new class.
SolutionExplorer_maprenderer

7. Implement the renderer with the following code:

using static Bizmonger.Patterns.MessageBus;

[assembly: ExportRenderer(typeof(CustomPage),
                          typeof(MapPageRenderer))]
namespace Pickup.Droid
{
    public class MapPageRenderer : PageRenderer
    {
        Core.Location _location;
        Android.Views.View _view;
        GoogleMap _map;

        public MapPageRenderer()
        {
            Subscribe(Messages.LOCATION_CHANGED, obj =>
                {
                    _location = obj as Core.Location;

                    LatLng location = new LatLng(_location.Latitude, _location.Longitude);
                    CameraPosition.Builder builder = CameraPosition.InvokeBuilder();
                    builder.Target(location);
                    builder.Zoom(18);
                    CameraPosition cameraPosition = builder.Build();
                    CameraUpdate cameraUpdate = CameraUpdateFactory.NewCameraPosition(cameraPosition);

                    var activity = this.Context as Activity;
                    MapFragment mapFrag = (MapFragment)activity.FragmentManager.FindFragmentById(Resource.Id.map);
                    _map = mapFrag.Map;
                    _map?.MoveCamera(cameraUpdate);

                    var textview = _view.FindViewById<Android.Widget.TextView>(Resource.Id.textView);
                    textview.Text = $"{_location.Latitude} , {_location.Longitude}";
                });
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
        {
            base.OnElementChanged(e);

            var activity = this.Context as Activity;
            _view = activity.LayoutInflater.Inflate(Resource.Layout.MapLayout, this, false);

            var mapFragment = activity.FragmentManager.FindFragmentById<MapFragment>(Resource.Id.map);

            var codeButton = _view.FindViewById<Android.Widget.Button>(Resource.Id.button_code);
            codeButton.Click += OnButtonTapped;

            AddView(_view);
        }

        void OnButtonTapped(object sender, EventArgs e)
        {
            var locationService = new LocationService();
            locationService.GetLocation();
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
            var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
            _view.Measure(msw, msh);
            _view.Layout(0, 0, r - l, b - t);
        }
    }

8. When you run your app, you should see something like this:

defaultMap

9. Click the “Locate Me!” button and observe the map’s navigation to your location.

app_screenshot

Conclusion

In conclusion, we may find ourselves having to implement a feature that’s dependent on a map and/or GPS. In order to implement this for Android and provide other controls to manage related services, we must implement a native Android page within our Xamarin.Forms app. In this post I demonstrated how to load a native android page that contains a map that leverages GPS to identify a phones location.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: