Xamarin.Forms – Mock Locations

This Project you can find at: https://github.com/officialdoniald/Xamarin.Forms.MockLocation.

Forms:

We can detect Location changing in the Forms Project: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile/MainPage.xaml.cs. If we will get a Mock Location this will detect it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Xamarin.Forms.MockLocation.Mobile
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            Thread thread = new Thread(async () =>
            {
                while (true)
                {
                    var request = new GeolocationRequest(GeolocationAccuracy.Medium);
                    var location = await Geolocation.GetLocationAsync(request);

                    if (location != null)
                    {
                        if (location.IsFromMockProvider)
                        {
                            //Detect mocking location
                        }
                        else
                        {
                            //Detect normal location
                        }
                    }
                }
            });
                    }
    }
}

The two platforms need to be discussed separately. In Android we can make an Implementation for Mock Location, but in iOS, we have to use a script file and we have to add to the emulator, so we can’t make this programmatically.

Android:

First, we have to request permissions in the Manifest and in the MainActivity (Runtime).

Manifest: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Properties/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.xamarin.forms.mocklocation.mobile">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
  <application android:label="Xamarin.Forms.MockLocation.Mobile.Android"></application>
  
  <uses-permission android:name="android.permission.PRIORITY_HIGH_ACCURACY" />
  <uses-feature android:name="android.hardware.location" android:required="false" />
  <uses-feature android:name="android.hardware.location.gps" android:required="false" />
  <uses-feature android:name="android.hardware.location.network" android:required="false" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

MainActivity: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/MainActivity.cs

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

            base.OnCreate(savedInstanceState);

            Context = this.ApplicationContext;

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.AccessFineLocation }, 1);
            
            LoadApplication(new App());
        }

Allright! We have to send the mock(test) location via LocationManager. Let’s create an interface in the Forms Project and call the Android implementation via DependencyService.

Forms: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Implementation/IMockLocationProvider.cs.

namespace Xamarin.Forms.MockLocation.Implementation
{
    public interface IMockLocationProvider
    {
        void SendMockLocation(MockPosition mockPosition);
    }

    public class MockPosition
    {
        public double Longitude { get; set; }

        public double Latitude { get; set; }

        /// <summary>
        /// Default value: 1.0.
        /// </summary>
        public double Altitude { get; set; } = 1.0;
    }
}

Android: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Implementations/MockLocationProvider.cs.

using System;
using Android.Content;
using Android.Hardware;
using Android.Locations;
using Xamarin.Forms.MockLocation.Implementation;
using Xamarin.Forms.MockLocation.Mobile.Droid.Implementations;

[assembly: Xamarin.Forms.Dependency(typeof(MockLocationProvider))]
namespace Xamarin.Forms.MockLocation.Mobile.Droid.Implementations
{
    public class MockLocationProvider : IMockLocationProvider
    {
        public void SendMockLocation(MockPosition position)
        {
            Location location = new Location(LocationManager.GpsProvider)
            {
                Latitude = position.Latitude,
                Longitude = position.Longitude,
                Altitude = position.Altitude,
                Time = DateTime.Now.Ticks,
                ElapsedRealtimeNanos = 100,
                Speed = 0.0f,
                Bearing = 0.0f,
                Accuracy = 0
            };

            LocationManager locationManager = MainActivity.Context.GetSystemService(Context.LocationService) as LocationManager;

            locationManager.AddTestProvider(LocationManager.GpsProvider, false, false, false, false, false, false, false, Power.Low, SensorStatus.AccuracyHigh);
            locationManager.SetTestProviderLocation(LocationManager.GpsProvider, location);
            locationManager.SetTestProviderEnabled(LocationManager.GpsProvider, true);
        }
    }
}

Deploy your app to the phone and close it.

One more important thing: only the System app can send mock locations, so we have to call for the operation system, that this will send mock locations. So if you haven’t already, let’s enable developer mode on your phones and go to developer options.

Select the Mock Location, and select you app. Run the app again and it will work.

iOS:

So, as above said, we can mock the Location programmatically. We have to create a script fiel and add it to the Emulator. Here is a good website for the gpx file generating: https://www.gpxgenerator.com/.

<?xml version="1.0"?>
<gpx version="1.1" creator="gpxgenerator.com">
<wpt lat="50.84633694747007" lon="4.364619255065918">
    <time>2017-02-10T10:48:50Z</time>
</wpt>
<wpt lat="50.845889847299254" lon="4.36431884765625">
    <time>2017-02-10T10:49:28Z</time>
</wpt>
<wpt lat="50.84507692692001" lon="4.363868236541748">
    <time>2017-02-10T10:50:36Z</time>
</wpt>
<wpt lat="50.84460271682405" lon="4.363675117492676">
    <time>2017-02-10T10:51:14Z</time>
</wpt>
<wpt lat="50.84374912650492" lon="4.363245964050293">
    <time>2017-02-10T10:52:25Z</time>
</wpt>
<wpt lat="50.84296326764228" lon="4.36281681060791">
    <time>2017-02-10T10:53:31Z</time>
</wpt>
<wpt lat="50.84281578200512" lon="4.36181902885437">
    <time>2017-02-10T10:54:22Z</time>
</wpt>
<wpt lat="50.84310709422066" lon="4.360542297363281">
    <time>2017-02-10T10:55:30Z</time>
</wpt>
<wpt lat="50.84268028733167" lon="4.359984397888184">
    <time>2017-02-10T10:56:13Z</time>
</wpt>
<wpt lat="50.84245672026023" lon="4.359222650527954">
    <time>2017-02-10T10:56:55Z</time>
</wpt>
<wpt lat="50.843019023277556" lon="4.35808539390564">
    <time>2017-02-10T10:58:07Z</time>
</wpt>
<wpt lat="50.84339840461739" lon="4.357527494430542">
    <time>2017-02-10T10:58:48Z</time>
</wpt>
<wpt lat="50.84356777029096" lon="4.357130527496338">
    <time>2017-02-10T10:59:12Z</time>
</wpt>
<wpt lat="50.844069089081536" lon="4.356368780136108">
    <time>2017-02-10T11:00:07Z</time>
</wpt>
<wpt lat="50.84437550047113" lon="4.3559181690216064">
    <time>2017-02-10T11:00:40Z</time>
</wpt>
<wpt lat="50.84555269015878" lon="4.354877471923828">
    <time>2017-02-10T11:02:27Z</time>
</wpt>
<wpt lat="50.84620302096338" lon="4.354770183563232">
    <time>2017-02-10T11:03:18Z</time>
</wpt>
<wpt lat="50.8467178597545" lon="4.354383945465088">
    <time>2017-02-10T11:04:03Z</time>
</wpt>
<wpt lat="50.84665428412764" lon="4.3536436557769775">
    <time>2017-02-10T11:04:40Z</time>
</wpt>
<wpt lat="50.84662380036855" lon="4.353042840957642">
    <time>2017-02-10T11:05:10Z</time>
</wpt>
<wpt lat="50.846430736098434" lon="4.3524473905563354">
    <time>2017-02-10T11:05:43Z</time>
</wpt>
<wpt lat="50.846213961280625" lon="4.351991415023804">
    <time>2017-02-10T11:06:11Z</time>
</wpt>
<wpt lat="50.84603783095749" lon="4.3516212701797485">
    <time>2017-02-10T11:06:34Z</time>
</wpt>
<wpt lat="50.845837989977895" lon="4.351256489753723">
    <time>2017-02-10T11:06:58Z</time>
</wpt>
<wpt lat="50.84563476098514" lon="4.350859522819519">
    <time>2017-02-10T11:07:23Z</time>
</wpt>
<wpt lat="50.84547556432245" lon="4.350618124008179">
    <time>2017-02-10T11:07:40Z</time>
</wpt>
<wpt lat="50.84531975429677" lon="4.350387454032898">
    <time>2017-02-10T11:07:56Z</time>
</wpt>
<wpt lat="50.845167330942075" lon="4.350119233131409">
    <time>2017-02-10T11:08:14Z</time>
</wpt>
<wpt lat="50.84499536286211" lon="4.3499743938446045">
    <time>2017-02-10T11:08:29Z</time>
</wpt>
</gpx>

If we get a gpx file, we have to script the Emulator. Open the AppleScript App on the Mac and create a new script:

on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    set the item_list to every text item of this_text
    set AppleScript's text item delimiters to the replacement_string
    set this_text to the item_list as string
    set AppleScript's text item delimiters to ""
    return this_text
end replace_chars
 
on run arg
--> arg is a collection of the command line parameters
    
    set xmlPathParam to item 1 of arg --> first param is the location of the gpx file
    set sleepParam to item 2 of arg --> second param is the time (seconds) to sleep between each iteration
 
    tell application "System Events"
        tell XML file xmlPathParam
            tell XML element "gpx"
                set wptElements to every XML element whose name = "wpt" -->put wpt elements in a collection for later use
            end tell
    end tell
    
    tell process "Simulator"
        set frontmost to true
            repeat with c from 1 to count of wptElements
                set wptElement to item c of wptElements
                tell wptElement
                    set lon to value of XML attribute "lon" of wptElement --> putting the lon value of the wpt element in a variable for later use
                    set lat to value of XML attribute "lat" of wptElement --> putting the lan value of the wpt element in a variable for later use
                end tell
                    
                click menu item "Custom Location…" of menu of menu item "Location" of menu "Debug" of menu bar 1
                set popup to window "Custom Location"
                set value of text field 1 of popup to my replace_chars(lat, ".", ",")
                set value of text field 2 of popup to my replace_chars(lon, ".", ",")
                click button "OK" of popup
                    
                delay sleepParam --> sleep to simulate 'natural' movement
            end repeat
        end tell
    end tell
end run

With this script this will open our gpx file and processes it.

iOS Blog Source: https://blog.pieeatingninjas.be/2017/02/10/custom-location-and-movement-on-xamarin-remoted-ios-simulator-for-windows/.

Another way: you can create a Xamarin UI Test Project and you can mock the location too. Just call the SetLocation function. (On Android it is still required that change the Mock Location app and request permissions.)

Xamarin.Forms get user location when it change

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange

When you need to update the user’s location when it change you can make it in Xamarin.Forms too via DependencyService or using Xamarin.Essentials and create a Timer and get the actual Location per seconds (don’t do that :D).

Before coding, we have to satisfy the users permissons.

Andorid: (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange.Android/Properties/AndroidManifest.xml)

Turn ACCESS_COARSE_LOCATION on.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.Xamarin.Forms.DetectUserLocationCange" android:installLocation="auto">
	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
	<application android:label="Xamarin.Forms.DetectUserLocationCange.Android"></application>
</manifest>

iOS: (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange.iOS/Info.plist)

    <key>NSLocationAlwaysUsageDescription</key>
    <string>Can we use your location at all times?</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Can we use your location when your app is being used?</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>Can we use your location at all times?</string>

ATTENTION: if you will publish on the App Store, you have to pay attention for the privacy. You have to ask the user every time before you will get her/his location. So best way to do that, create a user’s settings, where the user can turn in or off, that you get her/his location. If turned off, you have to show a popup with some text: Can we use your location? and then subcribe to the event. If turned on, you don’t have to show popup.

We have to create an interface in the StandardLibrary. (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange/Services/ILocationUpdateService.cs)

using System;

namespace Xamarin.Forms.DetectUserLocationCange.Services
{
    public interface ILocationUpdateService
    {
        void GetUserLocation();
        event EventHandler<ILocationEventArgs> LocationChanged;
    }

    public interface ILocationEventArgs
    {
        double Latitude { get; set; }
        double Longitude { get; set; }
    }
}

We have to implement this interface in the platforms.

Android: (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange.Android/Services/LocationUpdateService.cs)

using System;
using Android.Content;
using Android.Locations;
using Android.OS;
using Android.Runtime;
using Xamarin.Forms;
using Xamarin.Forms.DetectUserLocationCange.Droid.Services;
using Xamarin.Forms.DetectUserLocationCange.Services;

[assembly: Dependency(typeof(LocationUpdateService))]
namespace Xamarin.Forms.DetectUserLocationCange.Droid.Services
{
    public class LocationEventArgs : EventArgs, ILocationEventArgs
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
    }

    public class LocationUpdateService : Java.Lang.Object, ILocationUpdateService, ILocationListener
    {
        LocationManager locationManager;

        public void GetUserLocation()
        {
#pragma warning disable CS0618 // Type or member is obsolete
            locationManager = (LocationManager)Forms.Context.GetSystemService(Context.LocationService);
#pragma warning restore CS0618 // Type or member is obsolete
            locationManager.RequestLocationUpdates(
                provider: LocationManager.NetworkProvider,
                minTime: 0,//millisec
                minDistance: 0,//metres
                listener: this);
        }

        ~LocationUpdateService()
        {
            locationManager.RemoveUpdates(this);
        }

        public void OnLocationChanged(Location location)
        {
            if (location != null)
            {
                LocationEventArgs args = new LocationEventArgs
                {
                    Latitude = location.Latitude,
                    Longitude = location.Longitude
                };
                LocationChanged(this, args);
            };
        }

        public event EventHandler<ILocationEventArgs> LocationChanged;
        
        event EventHandler<ILocationEventArgs>
            ILocationUpdateService.LocationChanged
        {
            add
            {
                LocationChanged += value;
            }
            remove
            {
                LocationChanged -= value;
            }
        }

        public void OnProviderDisabled(string provider) { }

        public void OnProviderEnabled(string provider) { }

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

iOS: (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange.iOS/Services/LocationUpdateService.cs)

using System;
using CoreLocation;
using Xamarin.Forms.DetectUserLocationCange.iOS.Services;
using Xamarin.Forms.DetectUserLocationCange.Services;

[assembly: Xamarin.Forms.Dependency(typeof(LocationUpdateService))]
namespace Xamarin.Forms.DetectUserLocationCange.iOS.Services
{
    public class LocationEventArgs : EventArgs, ILocationEventArgs
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
    }

    public class LocationUpdateService : ILocationUpdateService
    {
        CLLocationManager locationManager;

        public event EventHandler<ILocationEventArgs> LocationChanged;

        event EventHandler<ILocationEventArgs> ILocationUpdateService.LocationChanged
        {
            add
            {
                LocationChanged += value;
            }
            remove
            {
                LocationChanged -= value;
            }
        }

        public void GetUserLocation()
        {
            locationManager = new CLLocationManager
            {
                DesiredAccuracy = CLLocation.AccuracyBest,
                DistanceFilter = CLLocationDistance.FilterNone
            };

            locationManager.LocationsUpdated +=
                (object sender, CLLocationsUpdatedEventArgs e) =>
                {
                    var locations = e.Locations;
                    var strLocation =locations[locations.Length - 1].Coordinate.Latitude.ToString();

                    strLocation = strLocation + "," + locations[locations.Length - 1].Coordinate.Longitude.ToString();

                    LocationEventArgs args = new LocationEventArgs();
                    args.Latitude = locations[locations.Length - 1].Coordinate.Latitude;
                    args.Longitude = locations[locations.Length - 1].Coordinate.Longitude;

                    LocationChanged(this, args);
                };

            locationManager.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs e) =>
            {
                if (e.Status ==
                    CLAuthorizationStatus.AuthorizedWhenInUse)
                {
                    locationManager.StartUpdatingLocation();
                }
            };

            locationManager.RequestWhenInUseAuthorization();
        }

        ~LocationUpdateService()
        {
            locationManager.StopUpdatingLocation();
        }
    }
}

Use theese implementations via DependencyService: (https://github.com/officialdoniald/Xamarin.Forms.DetectUserLocationCange/blob/master/Xamarin.Forms.DetectUserLocationCange/Xamarin.Forms.DetectUserLocationCange/App.xaml.cs)

using System;
using Xamarin.Forms;
using Xamarin.Forms.DetectUserLocationCange.Services;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Xamarin.Forms.DetectUserLocationCange
{
    public partial class App : Application
    {
        public static ILocationUpdateService LocationUpdateService;

        public App()
        {
            InitializeComponent();

            MainPage = new MainPage();

            LocationUpdateService.LocationChanged += LocationUpdateService_LocationChanged; ;
        }

        private void LocationUpdateService_LocationChanged(object sender, ILocationEventArgs e)
        {
            //Here you can get the user's location from "e" -> new Location(e.Latitude, e.Longitude);
            //new Location is from Xamarin.Essentials Location object.
        }

        protected override void OnStart()
        {
            // Handle when your app starts
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
        }
    }
}