From b4f4bd63309f7cb79c7a5cf457d851d309c05668 Mon Sep 17 00:00:00 2001 From: ChiragKV-Juspay Date: Tue, 30 Sep 2025 15:35:10 +0530 Subject: [PATCH] fix: added webview warmup for resourceNotFoundExceptions --- .../io/hyperswitch/lite/WebViewFragment.kt | 36 +++++++++++++++++-- .../java/io/hyperswitch/lite/WebViewUtils.kt | 8 ++++- .../java/io/hyperswitch/lite/WebViewWarmUp.kt | 32 +++++++++++++++++ .../hyperswitch/lite/WebViewWarmUpHelper.kt | 26 ++++++++++++++ .../DefaultPaymentSessionLauncherLite.kt | 5 +++ 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUp.kt create mode 100644 hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUpHelper.kt diff --git a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewFragment.kt b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewFragment.kt index 1dea1e8e..cce4146e 100644 --- a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewFragment.kt +++ b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewFragment.kt @@ -163,7 +163,8 @@ open class WebViewFragment : Fragment() { */ @SuppressLint("SetJavaScriptEnabled") private fun createWebView(): WebView { - return WebView(context).apply { + return try { + WebView(context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) @@ -187,13 +188,37 @@ open class WebViewFragment : Fragment() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) + // Re-establish focus and key listeners when page finishes + // activity?.runOnUiThread { + // setupBackPressHandling() + // mainWebView.requestFocus() + // } } } webChromeClient = object : WebChromeClient() { + // override fun onProgressChanged(view: WebView?, newProgress: Int) { + // super.onProgressChanged(view, newProgress) + // if (newProgress == 100) { + // val url = view?.url + // Log.d("WebViewFragment", "Page loaded 100%: $url") + // // Check if this is the problematic white screen + // if (url != null && url.contains("stripe.com")) { + // Log.d("WebViewFragment", "Stripe page detected, re-establishing focus") + // activity?.runOnUiThread { + // // Force focus back to our views + // mainWebView.requestFocus() + // webViewContainer.requestFocus() + // view?.requestFocus() + // } + // } + // } + // } + override fun onCreateWindow( view: WebView?, dialog: Boolean, userGesture: Boolean, resultMsg: Message ): Boolean { + Log.d("WebViewFragment", "Creating new window") val newWebView = createNewWebView() webViews.add(newWebView) webViewContainer.addView(newWebView) @@ -227,8 +252,13 @@ open class WebViewFragment : Fragment() { } else { WebAppInterfaceWithoutScanCard(activity,this@WebViewFragment,this,bundleUrl) } - addJavascriptInterface(webAppInterface, "AndroidInterface") - loadUrl(bundleUrl) + addJavascriptInterface(webAppInterface, "AndroidInterface") + loadUrl(bundleUrl) + } + } catch (e: Exception) { + Log.e("WebViewFragment", "Failed to create WebView", e) + // Re-throw the exception to maintain existing error handling behavior + throw e } } diff --git a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewUtils.kt b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewUtils.kt index e43584e9..1a12761d 100644 --- a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewUtils.kt +++ b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewUtils.kt @@ -13,6 +13,12 @@ import io.hyperswitch.paymentsheet.PaymentSheet */ class WebViewUtils(private val activity: Activity) : SDKInterface { + init { + // Initialize WebView warm-up to prevent Resources$NotFoundException crashes + // This ensures WebViewFactory is initialized safely before any WebView instances are created + WebViewWarmUpHelper.warmUpWhenIdle(activity) + } + /** * The WebViewFragment used for displaying payment sheets. * @@ -67,4 +73,4 @@ class WebViewUtils(private val activity: Activity) : SDKInterface { transaction.attach(webFragment).commit() return true } -} \ No newline at end of file +} diff --git a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUp.kt b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUp.kt new file mode 100644 index 00000000..06bf3acd --- /dev/null +++ b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUp.kt @@ -0,0 +1,32 @@ +package io.hyperswitch.lite + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.webkit.WebView +import java.util.concurrent.atomic.AtomicBoolean + +object WebViewWarmUp { + private val done = AtomicBoolean(false) + + fun isDone(): Boolean = done.get() + + fun init(context: Context) { + if (done.get()) return + + Looper.getMainLooper().queue.addIdleHandler { + var success = false + try { + val wv = WebView(context.applicationContext) + Handler(Looper.getMainLooper()).post { + runCatching { wv.destroy() } + } + success = true + } catch (e: Throwable) { + } finally { + if (success) done.set(true) + } + false + } + } +} diff --git a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUpHelper.kt b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUpHelper.kt new file mode 100644 index 00000000..25a42fa3 --- /dev/null +++ b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/lite/WebViewWarmUpHelper.kt @@ -0,0 +1,26 @@ +package io.hyperswitch.lite + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.os.Handler +import android.os.Looper + +object WebViewWarmUpHelper { + private val idleHandlerQueued = java.util.concurrent.atomic.AtomicBoolean(false) + + fun warmUpWhenIdle(activity: Activity) { + if (WebViewWarmUp.isDone()) { + return + } + + // Prevent multiple idle handlers from being queued + if (idleHandlerQueued.compareAndSet(false, true)) { + Looper.getMainLooper().queue.addIdleHandler { + WebViewWarmUp.init(activity.applicationContext) + false // Remove this IdleHandler after execution + } + } + } + +} diff --git a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/paymentsession/DefaultPaymentSessionLauncherLite.kt b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/paymentsession/DefaultPaymentSessionLauncherLite.kt index 4e14ba7f..dd426e7d 100644 --- a/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/paymentsession/DefaultPaymentSessionLauncherLite.kt +++ b/hyperswitch-sdk-android-lite/src/main/java/io/hyperswitch/paymentsession/DefaultPaymentSessionLauncherLite.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.os.Bundle import io.hyperswitch.PaymentConfiguration import io.hyperswitch.lite.WebViewUtils +import io.hyperswitch.lite.WebViewWarmUpHelper import io.hyperswitch.paymentsheet.PaymentSheet import io.hyperswitch.paymentsheet.PaymentSheetResult @@ -29,6 +30,10 @@ open class DefaultPaymentSessionLauncherLite( customParams ) } + + // Early WebView warm-up to prevent Resources$NotFoundException crashes + // This happens before WebView preloading, ensuring WebViewFactory is initialized safely + WebViewWarmUpHelper.warmUpWhenIdle(activity) } // Method to initialize the payment session