Event handling

Our widgets will publish events to the following interfaces if they are available:

window.Android.postMessage

window.webkit.messageHandlers.ios.postMessage

window.ReactNativeWebView.postMessage

Events are sent as a JSON string with the following format:

{
	name,
	type,
	metadata
}

We also dispatch these events to the top-level window with window.dispatchEvent (payload in event.details)

Example Android code to set up the window.Android.postMessage and handle the event in Kotlin:

// WebAppInterface.kt

import android.webkit.JavascriptInterface

class WebAppInterface(private val fragment: DashboardFragment) {

    @JavascriptInterface
    public fun postMessage(message: String) {
        // Handle the received message
        fragment.activity?.runOnUiThread {
            fragment.handleMessageFromWebView(message)
        }
    }
}

// Your view fragment

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment
import com.example.basicnav.databinding.FragmentDashboardBinding

import org.json.JSONObject

const val WIDGET_URI = "https://widget-domain-that-we-provide"

class DashboardFragment : Fragment() {

    private var _binding: FragmentDashboardBinding? = null
    private val binding get() = _binding!!

    private lateinit var webView: WebView

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
        val root: View = binding.root

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true)
        }

        webView = binding.webView
        webView.webViewClient = WebViewClient()
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.addJavascriptInterface(WebAppInterface(this), "Android")
        return root
    }

    fun handleMessageFromWebView(message: String) {
        Log.d("DashboardFragment", "Received message from WebView: $message")
        try {
            val jsonObject = JSONObject(message)
            when (val type = jsonObject.getString("type")) {
                "externalLink" -> {
                    val url = jsonObject.getString("url")
                    openExternalLink(url)
                }
                else -> {
                    Log.d("DashboardFragment", "Received message $type")
                }
            }
        } catch (e: Exception) {
            Log.e("DashboardFragment", "Error parsing JSON message", e)
        }
    }

    private fun openExternalLink(url: String) {
        try {
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
            startActivity(intent)
        } catch (e: Exception) {
            Log.e("DashboardFragment", "Error opening external link", e)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Retrieve the fragment from the arguments
        val fragment = arguments?.getString("fragment")

        val widgetId = "your-widget-id"
        val token = "token-for-user"

        Log.d("DashboardFragment", "URL fragment received: $fragment")

        // Load the WebView with the received parameters
        val url = "$WIDGET_URI?widget_id=$widgetId&token=$token#$fragment"
        webView.loadUrl(url)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Example iOS Code:

import SwiftUI
import WebKit
import Foundation

struct Payload: Codable {
    let type: String
    let url: String
}

struct WebView: UIViewRepresentable {
    var url: URL
    var javascript: String
    var messageHandler: WKScriptMessageHandler

    func makeUIView(context: Context) -> WKWebView {
        let preferences = WKPreferences()
        let configuration = WKWebViewConfiguration()
        configuration.preferences = preferences
        configuration.userContentController.add(messageHandler, name: "ios")
        let webView = WKWebView(frame: .zero, configuration: configuration)
        webView.navigationDelegate = context.coordinator
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(URLRequest(url: url))
    }
    
}

class MessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "ios", let jsonString = message.body as? String {
            print("Received message from JS: \(jsonString)")
            guard let jsonData = jsonString.data(using: .utf8) else {
                fatalError("Failed to convert string to data")
            }
            do {
                let decoder = JSONDecoder()
                let payload = try decoder.decode(Payload.self, from: jsonData)
                
                if payload.type == "externalLink", let url  = URL(string: payload.url) {
                    print("OPEN URL: \(payload.url)")
                    if UIApplication.shared.canOpenURL(url) {
                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
                    } else {
                        print("Cannot open URL")
                    }
                }
            } catch {
                print("Error decoding JSON:", error)
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        let fragment = "your-fragment-string" // Retrieved from universal link
        let widgetId = "your-widget-id" // supplied to you
        let token = "token-for-user" // retrieved from your backend
        let baseUri = "https://widget-domain"
        
        // Construct URL
        let urlString = "\(baseUri)?widget_id=\(widgetId)&token=\(token)#\(fragment)"
        let url = URL(string: urlString)!

        return WebView(url: url)
    }
}


What’s Next