Kotlin 1.5.0 NPE when properly destroying a WebView

130 views Asked by At

My app destroys webviews as per the following (from here).

 parentConstraintLayout.removeView(webView)
 webView.apply{
     clearHistory()
     clearCache(true)
     loadUrl("about:blank")
     onPause()
     removeAllViews()
 }

This has been working fine up until upgrading the Kotlin version from 1.4.32 to 1.5.0 With Kotlin 1.5.0, the webView variable becomes null after parentConstraintLayout.removeView(webView) is executed. clearHistory() throws an NPE and the app crashes. webView and parentConstraintLayout are both declared in the XML.

I've verified that everything else being equal, the issue occurs with Kotlin 1.5.0 / 1.5.20 but not with 1.4.32. Why does this happen with upgrading Kotlin, and what can I do to fix it?

Here is webView

<WebView
     android:id="@+id/webView"
     android:layout_width="0dp"
     android:layout_height="0dp"
     app:layout_constraintTop_toBottomOf="@id/webViewToolbarContainer"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent"
     app:layout_constraintBottom_toBottomOf="parent"
/>

and parentConstraintLayout - it's direct parent

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/parentConstraintLayout"
>

Here is the crash log

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.webkit.WebView.clearHistory()' on a null object reference
       at com.myapp.android.screens.sweepstakes.ui.DetailActivity.destroyWebView(DetailActivity.java:25)
       at com.myapp.android.screens.ui.DetailActivity.close(DetailActivity.java:21)
       at com.myapp.android.screens.impl.DetailParentPresenter.onTerminationDialogOptionSubmitClicked(DetailParentPresenter.java:69)
       at com.myapp.android.screens.ui.DetailActivity$showSubsequentTerminationDialog$1.invoke(DetailActivity.java:26)
       at com.myapp.android.screens.ui.DetailActivity$showSubsequentTerminationDialog$1.invoke(DetailActivity.java:2)
       at com.afollestad.materialdialogs.callbacks.DialogCallbackExtKt.invokeAll(DialogCallbackExtKt.java:26)
       at com.afollestad.materialdialogs.MaterialDialog.onActionButtonClicked$core(MaterialDialog.java:34)
       at com.afollestad.materialdialogs.internal.button.DialogActionButtonLayout$onFinishInflate$1.onClick(DialogActionButtonLayout.java:8)
       at android.view.View.performClick(View.java:7448)
       at android.view.View.performClickInternal(View.java:7425)
       at android.view.View.access$3600(View.java:810)
       at android.view.View$PerformClick.run(View.java:28305)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:223)
       at android.app.ActivityThread.main(ActivityThread.java:7664)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2

There are 2 answers

0
Henry On

Kotlin synthetics on 1.5.0 calls findViewById() for every time you reference it, instead of using a cached one on 1.4.x. Since you removed it from the view, findViewById() returns null.

You can use this instead:

 webView.apply{
     parentConstraintLayout.removeView(this)
     clearHistory()
     clearCache(true)
     loadUrl("about:blank")
     onPause()
     removeAllViews()
 }
0
Tejas On

you can override loadWebView. Before you load web view you can clear the data and cookies

    override fun loadWebView(userAccessToken: Token) {
    mWebView.clearCache(true)
    mWebView.clearFormData()
    mWebView.clearHistory()
    CookieManager.getInstance().removeAllCookies(null)
    CookieManager.getInstance().flush()
    mWebView.webViewClient = object : BaseWebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
           // TODO url validator return true/false

        }
    }
    mWebView.loadUrl(uri.toString())
}