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:
Class:
using Xamarin.Forms; namespace Pickup { public class CustomPage : ContentPage { } }
2. In your Xamarin.Forms project, add a XAML file.
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.
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:
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.
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:
9. Click the “Locate Me!” button and observe the map’s navigation to your location.
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