How to call evaluateJavascript, outside of webview creation, in jetpack compose?

Viewed 243

I have business logic outside of composable creation, which needs to be able to feed data to webview -> javascript. However, the webview shouldn't be saved, as stated here:

Note: Constructing the view in the AndroidView viewBlock is the best practice. Do not hold or remember a direct view reference outside AndroidView. https://developer.android.com/jetpack/compose/interop/interop-apis

How can I call webview's evaluateJavascript() outside of the composable creating the webview?

This is the code (lateinit as an example of what doesn't work):

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun WebPageScreen(urlToRender: String, someViewModel: SomeViewModel) {
    AndroidView(factory = {
        WebView(it).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            webViewClient = WebViewClient()
            settings.javaScriptEnabled = true

            addJavascriptInterface(someViewModel.createWebAppInterface(context), "Android")
            loadUrl(urlToRender)

            someViewModel.webViewObj = this //<<--- won't work like this.
    }, update = {
        it.loadUrl(urlToRender)
    })
}


class SomeViewModel(application: Application): AndroidViewModel(application) {
    lateinit var webViewObj: WebView  // <<--"This field leaks a context object"
    fun callJs() {
        webViewObj.evaluateJavascript("updateSomething(123)",null)
    }

    fun createWebAppInterface(mContext: Context) : WebAppInterface {
        return WebAppInterface(mContext)
    }
    
    inner class WebAppInterface(private val mContext: Context) {
        @JavascriptInterface
        fun webViewCallJs() {
            callJs()
        }
    }
}

Here the idea is, that callJs() could be called both from webView (e.g. a HTML UI button), and from within business logic (SomeViewModel). How can we achieve this when webview cannot be remembered, but we should be able call its function from outside?

1 Answers

In the accompanist-webview library, they've used a local webview state like below:

var webView by remember { mutableStateOf<WebView?>(null) }

AndroidView(
        factory = { context ->
            WebView(context).apply {
                ...
            }.also { webView = it }
        },

    ) {
        ...
    }

You can then use webView.post{ webview.evaluateJavaScript() } to execute JS functions

Related