Android get height of webview content once rendered

I chose this approach

const val heightWebViewJSScript = "(function() {var pageHeight = 0;function findHighestNode(nodesList) { for (var i = nodesList.length - 1; i >= 0; i--) {if (nodesList[i].scrollHeight && nodesList[i].clientHeight) {var elHeight = Math.max(nodesList[i].scrollHeight, nodesList[i].clientHeight);pageHeight = Math.max(elHeight, pageHeight);}if (nodesList[i].childNodes.length) findHighestNode(nodesList[i].childNodes);}}findHighestNode(document.documentElement.childNodes); return pageHeight;})()"

webView.webViewClient = object : WebViewClient() {

            override fun onPageFinished(view: WebView, url: String) {
                webView.evaluateJavascript(heightWebViewJSScript
                ) { height ->
                    val params = itemView.layoutParams
                    // params.height
                }
            }
        }

You can use ViewTreeObserver on that WebView to get actual height after rendering its content.

here's the sample code.

ViewTreeObserver viewTreeObserver  = mWebView.getViewTreeObserver();

viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() {
                   @Override
                   public boolean onPreDraw() {                                
                           int height = mWebView.getMeasuredHeight();
                           if( height != 0 ){
                                   Toast.makeText(getActivity(), "height:"+height,Toast.LENGTH_SHORT).show();
                                   mWebView.getViewTreeObserver().removeOnPreDrawListener(this);
                           }
                           return false;
                   }
           });

I found this solution to be 100% reliable.

Subclass your WebView and there is a need to invoke javascript after the content has been loaded.

// callback made this way in order to get reliable html height and to avoid race conditions
    @SuppressLint("SetJavaScriptEnabled")
    override fun onPageFinished(view: WebView?, url: String?) {
        view?.let {
            it.settings.javaScriptEnabled = true
            it.addJavascriptInterface(WebAppInterface(it), "AndroidGetHeightFunction")
            it.loadUrl("javascript:AndroidGetHeightFunction.resize(document.body.scrollHeight)")
        }
    }

We can then get proper height and disable javascript in callback (for security and consistency):

inner class WebAppInterface(private val webView: WebView) {
    @JavascriptInterface
    fun resize(height: Float) {
        webView.post {
            heightMeasuredListener?.invoke(formatContentHeight(webView, height.toInt()))
            webView.settings.javaScriptEnabled = false
        }
    }
}

WebView must call post() as the code inside resize(...) is called in WebView thread!

After, make sure to scale your pixels to match density pixels!:

fun formatContentHeight(webView: WebView, height: Int): Int = Math.floor((height * webView.context.resources.displayMetrics.density).toDouble()).toInt()