Search code examples
javascriptxamarin.formswebviewmaui.net-6.0

Is there a way to create bi-directional communication between a shell app and a webview in .NET 6 MAUI?


Recently I've had the assignment to create a bi-directional interop bridge between a shell app and a webpage in .NET MAUI. Not finding any way to solve this I had the idea of creating it in Xamarin.Forms first seeing as MAUI is a continuation on it.

After having created this app, I've tried to convert it over to MAUI using Microsoft's instructions on the dotnet/maui github wiki.

The main problem i'm encountering right now is that I've been using extensions on Android's WebViewRenderer, WebViewClient and Java.Lang.Object to be able to send and receive javascript to and from the WebView.

public class ExtendedWebViewRenderer : WebViewRenderer
    {
        private const String JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";

        public ExtendedWebViewRenderer(Context context) : base(context)
        {
        }

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

            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((ExtendedWebView)Element).Cleanup();
            }
            if (e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
            }
        }
    }

public class JavascriptWebViewClient : WebViewClient
    {
        private readonly String _javascript;

        public JavascriptWebViewClient(String javascript)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(WebView view, String url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }

public class JsBridge : Java.Lang.Object
    {
        private readonly WeakReference<ExtendedWebViewRenderer> _extendedWebViewMainRenderer;

        public JsBridge(ExtendedWebViewRenderer extendedRenderer)
        {
            _extendedWebViewMainRenderer = new WeakReference<ExtendedWebViewRenderer>(extendedRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(String data)
        {
            if (_extendedWebViewMainRenderer != null && _extendedWebViewMainRenderer.TryGetTarget(out var extendedRenderer))
            {
                ((ExtendedWebView)extendedRenderer.Element).InvokeAction(data);
            }
        }
    }

All three of these are either not available right now, or will not be implemented in MAUI, since a lot of platform dependent code has been automated now. Which leaves me with the problem that I can't seem to figure out how to change my current code to accomplish the same thing in MAUI.

Seeing as MAUI is currently not even fully released, I was wondering if this is currently just not possible or if there is some workaround to make it possible.

Any help would be greatly appreciated.


Solution

  • MAUI's default WebView has the Eval and EvaluateJavaScriptAsync functions to call JavaScript code from C#:

    • Eval just executes the script string you pass in a fire-and-forget way.
    • EvaluateJavaScriptAsync needs to be awaited but also returns a string with a stringified result of the data that the script returned.

    If you want to use callback/bridge methods to automatically receive data from the JavaScript side without any input from the C# side of the app, you will have to extend the default per-platform renderers to add that functionality. The good news is that there is an official tutorial on how to do it for Xamarin Forms at Customizing a WebView which is almost straightforward to port to .NET MAUI - you only have to change how renderers are registered.