Skip to content

Commit 03fd5ff

Browse files
Fix Connect JS alert after destroyed activity
Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com>
1 parent 662c99b commit 03fd5ff

2 files changed

Lines changed: 57 additions & 25 deletions

File tree

connect/src/main/java/com/stripe/android/connect/webview/StripeConnectWebView.kt

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import android.graphics.Bitmap
1111
import android.graphics.Rect
1212
import android.net.Uri
1313
import android.view.ViewTreeObserver
14+
import android.view.WindowManager
1415
import android.webkit.JavascriptInterface
1516
import android.webkit.JsResult
1617
import android.webkit.PermissionRequest
@@ -423,8 +424,13 @@ internal class StripeConnectWebView private constructor(
423424
}
424425
}
425426

426-
// Hook into the Activity lifecycle
427427
val activity = (view.findActivity() as? ComponentActivity)
428+
if (activity == null || activity.isFinishing || activity.isDestroyed) {
429+
result.cancel()
430+
return true
431+
}
432+
433+
// Hook into the Activity lifecycle
428434
val activityLifecycleObserver =
429435
object : DefaultLifecycleObserver {
430436
override fun onDestroy(owner: LifecycleOwner) {
@@ -433,7 +439,7 @@ internal class StripeConnectWebView private constructor(
433439
returnResult()
434440
}
435441
}
436-
activity?.lifecycle?.addObserver(activityLifecycleObserver)
442+
activity.lifecycle.addObserver(activityLifecycleObserver)
437443

438444
val okText = alert.buttons?.ok
439445
?: view.context.getString(android.R.string.ok)
@@ -444,30 +450,36 @@ internal class StripeConnectWebView private constructor(
444450
// Use the two-arg Builder to enforce an AppCompat theme. On some devices/OEM WebView
445451
// implementations, view.context may not carry AppCompat theme attributes, which causes
446452
// AppCompatDelegateImpl.createSubDecor to throw an IllegalStateException.
447-
AlertDialog.Builder(view.context, androidx.appcompat.R.style.Theme_AppCompat_Light_Dialog_Alert)
448-
.setCancelable(true)
449-
.setOnCancelListener {
450-
didConfirm = false
451-
}
452-
.setOnDismissListener {
453-
// Invoked on dialog dismissals but not configuration changes.
454-
returnResult()
455-
// Clean up the observer.
456-
activity?.lifecycle?.removeObserver(activityLifecycleObserver)
457-
}
458-
.setMessage(alert.message)
459-
.setPositiveButton(okText) { _, _ ->
460-
didConfirm = true
461-
}
462-
.apply {
463-
alert.title?.let { setTitle(it) }
464-
cancelText?.let {
465-
setNegativeButton(it) { _, _ ->
466-
didConfirm = false
453+
try {
454+
AlertDialog.Builder(view.context, androidx.appcompat.R.style.Theme_AppCompat_Light_Dialog_Alert)
455+
.setCancelable(true)
456+
.setOnCancelListener {
457+
didConfirm = false
458+
}
459+
.setOnDismissListener {
460+
// Invoked on dialog dismissals but not configuration changes.
461+
returnResult()
462+
// Clean up the observer.
463+
activity.lifecycle.removeObserver(activityLifecycleObserver)
464+
}
465+
.setMessage(alert.message)
466+
.setPositiveButton(okText) { _, _ ->
467+
didConfirm = true
468+
}
469+
.apply {
470+
alert.title?.let { setTitle(it) }
471+
cancelText?.let {
472+
setNegativeButton(it) { _, _ ->
473+
didConfirm = false
474+
}
467475
}
468476
}
469-
}
470-
.show()
477+
.show()
478+
} catch (e: WindowManager.BadTokenException) {
479+
logger.error("($loggerTag) Error showing alert dialog", e)
480+
activity.lifecycle.removeObserver(activityLifecycleObserver)
481+
result.cancel()
482+
}
471483
return true
472484
}
473485

connect/src/test/java/com/stripe/android/connect/webview/StripeConnectWebViewTest.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.content.Context
66
import android.content.ContextWrapper
77
import android.content.Intent
88
import android.net.Uri
9+
import android.webkit.JsResult
910
import android.webkit.ValueCallback
1011
import android.webkit.WebChromeClient
1112
import android.widget.FrameLayout
@@ -47,7 +48,8 @@ class StripeConnectWebViewTest {
4748

4849
private lateinit var webView: StripeConnectWebView
4950

50-
private val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().get()
51+
private val activityController = Robolectric.buildActivity(ComponentActivity::class.java).setup()
52+
private val activity = activityController.get()
5153
private val containerView = FrameLayout(activity)
5254

5355
@Before
@@ -110,6 +112,24 @@ class StripeConnectWebViewTest {
110112
verifyBlocking(mockDelegate) { onChooseFile(activity, filePathCallback, intent) }
111113
}
112114

115+
@Test
116+
fun `WebChromeClient onJsAlert cancels result when activity is destroyed`() {
117+
val result: JsResult = mock()
118+
119+
containerView.addView(webView)
120+
activityController.destroy()
121+
122+
assertThat(
123+
webView.stripeWebChromeClient.onJsAlert(
124+
webView,
125+
testUrl,
126+
"message",
127+
result
128+
)
129+
).isTrue()
130+
verify(result).cancel()
131+
}
132+
113133
@Test
114134
fun `JS fetchInitComponentProps is handled`() {
115135
val props = JsonObject(mapOf("foo" to JsonPrimitive("bar")))

0 commit comments

Comments
 (0)