JavaScript Bridge is a way to allow communication between the native MAUI app and a web application rendered in a WebView
. It enables a seamless two-way interaction, allowing native functionality to interact with web content and vice versa.
With a JavaScript bridge, you can:
- Enable two-way communication between the native and the web applications
- Access native device functionality from the web app
- Reuse the existing web application in your MAUI project and avoid code duplication
MAUI to Web communication
MAUI project code
You’ve got your Web application rendered in a WebView in the MAUI project. Now if you want to call a JavaScript function defined on the Web Application, you can simply execute code like this from your WebView page:
// Call JavaScript from the WebView
await myWebView.EvaluateJavaScriptAsync("window.nativeAppCallbacks.updateNativeSettings(data)");
The script parameter can be anything you want. A simple alert
function or something specifically defined for the MAUI to Web communication in your application.
Web project code
window.nativeAppCallbacks = {
updateNativeSettings(settings) {
// some code to update the state
},
};
Web to MAUI communication
MAUI project code
If your MAUI application is cross-platform, for example, it can be run on both Android and iOS operating systems, you’ll want to create two platform-specific services defining the functionality that can be executed from the web app.
Android
using Android.Webkit;
using Java.Interop;
namespace MyApp.Platforms.Android;
public class JavascriptBridge(LoggedInWebView loggedInWebView) : Java.Lang.Object
{
[JavascriptInterface]
[Export("fetchNativeAppSettings")]
public void FetchNativeAppSettings()
{
MainThread.InvokeOnMainThreadAsync(() => MyWebView.FetchNativeAppSettings());
}
...
}
The [JavascriptInterface]
attribute is used to mark a class or interface in your Android MAUI project so that it can be accessed by JavaScript running in a WebView
. It tells the Android runtime that this class or object will serve as a bridge between JavaScript and native code. Without this attribute, the methods in the class cannot be invoked from JavaScript.
The [Export("methodName")]
attribute is applied to methods within a class to specify the exact method name the web project will use to call this function.
Then, you can create a HandlerService class which will be used to register your new JavaScript bridge on a WebView.
using Microsoft.Maui.Handlers;
namespace MyApp.Platforms;
public class MyWebViewHandlerService : IMyWebViewHandlerService
{
public void SetHandler(IWebViewHandler handler, IWebView webView)
{
handler.PlatformView.Settings.JavaScriptEnabled = true;
var javascriptBridge = new JavascriptBridge((MyWebView) webView);
handler.PlatformView.AddJavascriptInterface(javascriptBridge, "nativeApp");
}
}
Please note, that although the namespace does not specify the platform used, this code should be in the Android folder. The namespace is inaccurate here so that the code can be accessible from shared non-platform-specific folders. For example, from the page class itself.
Wherever in your MAUI app code you deal with the IWebViewHandler
, you should enable JavaScript execution for your WebView, which for security reasons by default is disabled. Most importantly, add your JavaScript bridge to the Android-specific WebView control handler.PlatformView
.
nativeApp
is a JavaScript object which will contain all the JS functions defined in the bridge. So the web app can call nativeApp.fetchNativeAppSettings()
.
iOS
Implementing the JavaScript on the iOS side is slightly different, but not too complicated.
Again, you want to create a JavaScript bridge class, just like for Android.
using Foundation;
using Microsoft.Extensions.Logging;
using WebKit;
namespace MyApp.Platforms.iOS;
public class JavascriptBridge : NSObject, IWKScriptMessageHandler
{
private readonly WeakReference<MyWebView> _myWebViewRenderer;
internal JavascriptBridge(MyWebView myWebView)
{
_myWebViewRenderer = new WeakReference<MyWebView>(myWebView);
}
public void DidReceiveScriptMessage(
WKUserContentController userContentController,
WKScriptMessage message)
{
if (!_myWebViewRenderer.TryGetTarget(out var renderer))
{
return;
}
switch (message.Name)
{
case "fetchNativeAppSettings":
renderer.FetchNativeAppSettings();
break;
default:
return;
}
}
}
iOS JS Bridge has to extend IWKScriptMessageHandler
and implement its member DidReceiveScriptMessage
. Whenever the JavaScript function is called, DidReceiveScriptMessage
will be executed. This is where we check which JavaScript function was called and act accordingly. We can get the JavaScript function name with message.Name
and the parameters by looking at the message.Body
.
Great, now let’s create a HandlerService in the iOS folder which will be used to expose all these functions from the bridge to the WebView. This has to be done by building a string with actual JavaScript code.
using Foundation;
using Microsoft.Maui.Handlers;
using WebKit;
namespace MyApp.Platforms;
public class MyWebViewHandlerService : IMyWebViewHandlerService
{
private const string JavascriptCode =
"""
window.nativeApp = {};
window.nativeApp.fetchNativeAppSettings = function(request) {
window.webkit.messageHandlers.fetchNativeAppSettings.postMessage(request);
}
""";
public void SetHandler(IWebViewHandler handler, IWebView webView)
{
var javascriptBridge = new MyJavascriptBridge((MyWebView) webView);
var script = new WKUserScript(new NSString(JavaScriptCode), WKUserScriptInjectionTime.AtDocumentEnd, false);
handler.PlatformView.Configuration.UserContentController.AddUserScript(script);
handler.PlatformView.Configuration.UserContentController.AddScriptMessageHandler(javascriptBridge, "fetchNativeAppSettings");
}
}
This code:
- creates JavaScript code string which defines what happens when the Web app makes calls
window.nativeApp.fetchNativeAppSettings()
function. When that happens, it will post a message containing the name of the function and parameters to afetchNativeAppSettings
handler -
AddUserScript
adds this JavaScript code to the iOS-specific platform controller -
AddScriptMessageHandler
installs a message handler that we call from the script (JavascriptCode
) that we just added to the controller
Web project code
What your web code looks like will depend on your Web App Framework. Most likely, you’ll have a JavaScript code that is something like this. It’s that simple.
if (window.nativeApp && app.fetchNativeAppSettings) {
const appVersion = app.fetchNativeAppSettings();
}
Benefit during migration
JavaScript bridge is an excellent and pretty straightforward way to enable two-way communication for your native app rendering a web one.
On my project, we performed a Xamarin to MAUI cross-platform native app migration. This is when we implemented a MAUI JavaScript bridge for interaction with our Vue app. We found that we didn’t need to change a single line of code in the web project.
Currently, we are looking to migrate from Vue to the Blazor Web application. Having done some investigation and written a bit of code, it’s clear to see that although we’ll have a completely new and different .NET web application, there will be no changes that we’ll have to make to the native MAUI app codebase.
Readings ✨
Understanding handlers in MAUI
Please look at the Apple Documentation for more details on JavaScript message handlers
Top comments (0)