4

I'm trying to enable a webview within a xamarin forms app to get the current GPS coordinates of an android device. Currently the webview/website will return the GPS coordinates when opened in a chrome browser on the phone or a laptop, however within the app will not. Trying to get this working as simply as possible and to expand on it after.

Code so far: XAML page:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="UITrial.Page2"
             BackgroundColor = "#f0f0ea">
    <Label Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" />

  <WebView Source="https://danu6.it.nuigalway.ie/OliverInternetProgramming/project/Loginproject.html" />

</ContentPage>

HTML PAGE:

<!DOCTYPE html>
<html>
<body>

<p>Click the button to get your coordinates.</p>

<button onclick="getLocation()">Try It</button>

<p id="demo"></p>

<script>
var x = document.getElementById("demo");

function getLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.watchPosition(showPosition);
    } else { 
        x.innerHTML = "Geolocation is not supported by this browser.";}
    }

function showPosition(position) {
    x.innerHTML="Latitude: " + position.coords.latitude + 
    "<br>Longitude: " + position.coords.longitude;
}
</script>

</body>
</html>
Cœur
  • 37,241
  • 25
  • 195
  • 267
Ollie
  • 63
  • 1
  • 5

3 Answers3

7

Currently the webview/website will return the GPS coordinates when opened in a chrome browser on the phone or a laptop, however within the app will not.

You need to use a custom WebChromeClient for WebView in Droid project. Please refer to Android WebView Geolocation.

In Xamarin.Forms you can follow the below steps to accomplish this:

  1. Create a custom Control for WebView in PCL project:

    public class GeoWebView:WebView
    {
    }
    

    And use it in Xaml:

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WebViewFormsDemo"
             x:Class="WebViewFormsDemo.MainPage">
    <local:GeoWebView 
        Source="https://danu6.it.nuigalway.ie/OliverInternetProgramming/project/Loginproject.html"></local:GeoWebView>
    

  2. Create a Custom Renderer for GeoWebView in Droid Project like below:

    [assembly:ExportRenderer(typeof(GeoWebView),typeof(GeoWebViewRenderer))]
    namespace WebViewFormsDemo.Droid
    {
        public class GeoWebViewRenderer:WebViewRenderer
        {
            protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged(e);
                Control.Settings.JavaScriptEnabled = true;
                Control.SetWebChromeClient(new MyWebClient());
            }
        }
    
        public class MyWebClient : WebChromeClient
        {
            public override void OnGeolocationPermissionsShowPrompt(string origin, GeolocationPermissions.ICallback callback)
            {
                callback.Invoke(origin, true, false);
            }
        }
    }
    
  3. Add Permissions to AndroidManifest.xml:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    

Then you will get your location in webview correctly.

Community
  • 1
  • 1
Elvis Xia - MSFT
  • 10,801
  • 1
  • 13
  • 24
2

Cheers Elvis managed to get everything working, had to make some minor changes so will post everything I did in detail:

In App.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;

namespace WebViewFormsDemo
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
            MainPage = new MainPage();
        }

    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
    }
}

}

In MainPage.xaml ensure that your website is 'https'

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WebViewFormsDemo"
             x:Class="WebViewFormsDemo.MainPage">

  <local:GeoWebView
    Source="https:// --- Your Website ----></local:GeoWebView>

</ContentPage>

As Elvis said then need to create a custom control in the PLC project. Right click and add new 'Class' to do this.

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

namespace WebViewFormsDemo
{
    public class GeoWebView : WebView
    {
    }
}

Following this create a Custom Render in the Droid Class. Had some errors here initially, mostly with missing 'using' directives and also with keywords needed in the 'assembly'.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Webkit;

[assembly: ExportRenderer(typeof(WebViewFormsDemo.GeoWebView), typeof(WebViewFormsDemo.Droid.GeoWebViewRenderer))]
namespace WebViewFormsDemo.Droid
{
    public class GeoWebViewRenderer : WebViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            Control.Settings.JavaScriptEnabled = true;
            Control.SetWebChromeClient(new MyWebClient());
        }
    }

    public class MyWebClient : WebChromeClient
    {
        public override void OnGeolocationPermissionsShowPrompt(string origin, GeolocationPermissions.ICallback callback)
        {
            callback.Invoke(origin, true, false);
        }
    }
}

Following these changes everything worked perfectly. Thanks again Elvis!

Ollie
  • 63
  • 1
  • 5
  • 1
    It doesn't go as smoothly for me. I did as you describe so fine. But I get this error, when javascript tries getting the geolocation: `01-11 22:16:27.544 E/cr_LocationProvider(10680): Caught security exception while registering for location updates from the system. The application does not have sufficient geolocation permissions. 01-11 22:16:27.546 E/cr_LocationProvider(10680): newErrorAvailable application does not have sufficient geolocation permissions.` I have enabled both fine and coarse location permission. – Munk Jan 11 '19 at 21:18
  • 1
    I believe that happens when you target API 23 or above. The code works fine on lower SDKs. For Marshmellow and above, at some point there needs to be permission explicitly granted through a user-dialog, but I'm not sure where or how. – A. Niese Apr 18 '19 at 03:32
  • 1
    I use this in c# - follow James Montemagno permissions. This will add the extra check that you are missing var hasPermission = await UtilsPermissions.CheckPermissions(Permission.Location); – Pxaml Jun 18 '19 at 02:08
  • Please this anyone get this working for API 23 and Above? – Haybee Mar 15 '22 at 11:18
0

For Android API 23 and above, this is similarly accomplishable using the Xamarin.Essentials namespace.

  1. Ensure that you have requested all of the proper permissions in the Manifest file. At the time of writing this, these are the ones I needed:
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
  1. Create a custom renderer for WebView, and a custom WebChromeClient to use.
using Android.Content;
using Android.Webkit;

using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer( typeof( Xamarin.Forms.WebView ), typeof( Namespace.Droid.Renderers.WebViewRenderer ) )]
namespace Namespace.Droid.Renderers
{
    /// <summary>
    /// The <see cref="Xamarin.Forms.WebView"/> renderer.
    /// Implements the <see cref="WebViewRenderer" />
    /// </summary>
    /// <seealso cref="WebViewRenderer" />
    public class WebViewRenderer : Xamarin.Forms.Platform.Android.WebViewRenderer 
    {
        public WebViewRenderer( Context context )
            : base( context ) { }

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

            if( e.NewElement != null )
            {
                GeoWebViewClient cwc = new GeoWebViewClient();
                Control.SetWebChromeClient( cwc );
            }
        }

        /// <summary>
        /// A custom Chrome Web Client used to process geolocation permission in Android.
        /// Implements the <see cref="WebChromeClient" />
        /// </summary>
        /// <seealso cref="WebChromeClient" />
        public class GeoWebViewClient : WebChromeClient
        {
            /// <summary>
            /// Called when the geolocation prompt is requested through the WebView.
            /// </summary>
            /// <param name="origin">The origin.</param>
            /// <param name="callback">The callback.</param>
            public override async void OnGeolocationPermissionsShowPrompt( string origin, GeolocationPermissions.ICallback callback )
            {
                // Check if we have location permissions already granted.
                var locationWhenInUsePermissionStatus = await Xamarin.Essentials.Permissions.CheckStatusAsync<Xamarin.Essentials.Permissions.LocationWhenInUse>();

                // If not, request them.
                if( locationWhenInUsePermissionStatus != PermissionStatus.Granted )
                {
                    await Xamarin.Essentials.Permissions.RequestAsync<Xamarin.Essentials.Permissions.LocationWhenInUse>();
                }

                callback.Invoke( origin, true, true );
            }
        }
    }
}

Voila!

Android Permission Requesting

Bart
  • 367
  • 2
  • 11