Quantcast
Channel: Xamarin.Forms — Xamarin Community Forums
Viewing all articles
Browse latest Browse all 58056

Using Android's JavascriptInterface with Xamarin Forms WebView

$
0
0

Hello,

I've spent the the last two days trying to get Android WebView's JavascriptInterface to work with a Xamarin Forms WebView - and finally I did. But the solution I found seems like an ugly hack. Now I'm wondering if I'm doing something wrong, or if a more elegant solution might exist.

But let me first describe my actual problem: I'm trying to use the WebView as a kind of a rich text formatter. I have some (simple) HTML-formatted text with embedded images and a custom font. The data is local, so I'm using a HtmlWebViewSource as Xamarin Forms WebView's htmlSource. I needed the background to be transparent, so I created a custom webview renderer. I also needed to set the height of the webview to match the height of the content, however this turned out to be quite a nightmare.

There are quite a few examples on the Internet about how to accomplish this. None of them worked. Using the custom webview renderer I tried setting the height on OnPageFinished by using the ComputeVerticalScrollRange method. Unfortunately content height was still zero at that time. Another failed experiment was adding an event handler to WebKit WebView's LayoutChange event, where the suggestion was to set the view's ContentHeight, which was also 0. Binding to the Touch event did work, though, the page was obviously loaded by the time I touched the control, but this was not really a solution, just something to try and see if the ContentHeight was ever set to a correct value.

Finally I deduced that the only way to make sure that the content has loaded was to use the JavascriptInterface. After quite a few hours of banging my head to the wall trying to get the interface to do anything I managed to actually do it. As it turns out I was pretty close to my solution the whole time. The interface didn't do anything until I called LoadDataWithBaseUrl in the custom webview renderer with the data from HtmlWebViewSource, like so:

webView.LoadDataWithBaseURL("file:///android_asset/", (this.browser.Source as Xamarin.Forms.HtmlWebViewSource).Html, "text/html", "utf-8", null); 

Somehow this seemed to kick the JavascriptInterface to life, and I was able call a method to set the content height based on the value I get from Javascript. Here's the complete code of the custom webview renderer:

using System;
using Mobilizer.Droid;
using Android.Webkit;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Java.Interop;

[assembly: Xamarin.Forms.ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]

namespace Mobilizer.Droid
{
    public class CustomWebViewRenderer : WebRenderer
    {
        protected Xamarin.Forms.WebView browser;
        protected WebView webView; 

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement == null)
            {
                webView = (WebView)Control;
                webView.SetBackgroundColor(new Color(0, 0, 0, 0));
                webView.Settings.JavaScriptEnabled = true;
                browser = e.NewElement; 
                Action<string> heightTarget = SetHeight; 
                webView.AddJavascriptInterface(new CustomJavascriptInterface(heightTarget), "External");
                webView.LoadDataWithBaseURL("file:///android_asset/", (this.browser.Source as Xamarin.Forms.HtmlWebViewSource).Html, "text/html", "utf-8", null); 
            }
        }

        public void SetHeight(string height)
        {
            Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
            {
                this.browser.HeightRequest = Convert.ToInt32(height);
            });
        }

    }

    public class CustomJavascriptInterface : Java.Lang.Object
    {
        public CustomJavascriptInterface(Action<string> callback)
        {
            _callback = callback; 
        }

        private Action<string> _callback;

        [Export("SetHeight")]
        [JavascriptInterface]
        public void SetHeight(Java.Lang.String message)
        {
            _callback.Invoke(message.ToString());
        }

    }

}

Still it's not perfect. For some reason the height of the content returned from JavaScript seems to be a bit more than the real height. This means that if the content is longer than the screen height, the content can be scrolled slightly too far with some empty space at the bottom. That's not a big deal though. Another strange thing is that when I scroll the page down, at the moment the view goes past the real bottom position of the data there's a very brief black flicker on the screen, which I haven't been able to figure out.

So, any thoughts or suggestions?


Viewing all articles
Browse latest Browse all 58056

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>