Skip to content

Commit 7f2a1d5

Browse files
ajaysubraclaude
authored andcommitted
fix: treat empty/missing android deep link as no-op instead of crash (#415)
* fix: treat empty/missing android deep link as no-op instead of crash When a form CTA is configured without an Android route (iOS-only or no URL), the JS bridge sends an empty string for the android field. Previously this threw an IllegalStateException in getDeepLink(), causing an ERROR log and skipping any downstream handling. Now the route is nullable — an empty or absent android field decodes to null, and deepLink() logs a warning and skips navigation instead of crashing. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test: Use any() matcher for log message to avoid brittle assertion Verify log level rather than exact message content, consistent with other test files in the project. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
1 parent 3489e3d commit 7f2a1d5

4 files changed

Lines changed: 53 additions & 17 deletions

File tree

sdk/forms/src/main/java/com/klaviyo/forms/bridge/KlaviyoNativeBridge.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ internal class KlaviyoNativeBridge() : NativeBridge {
114114
* There is a brief window between our overlay activity pausing and the next activity resuming.
115115
* We alleviate this race condition by postponing till next activity resumes if current activity is null.
116116
*/
117-
private fun deepLink(message: OpenDeepLink) = DeepLinking.handleDeepLink(message.route.toUri())
117+
private fun deepLink(message: OpenDeepLink) {
118+
message.route?.let { DeepLinking.handleDeepLink(it.toUri()) }
119+
?: Registry.log.warning("Deep link CTA with no Android route configured")
120+
}
118121

119122
/**
120123
* Instruct presentation manager to dismiss the form overlay activity

sdk/forms/src/main/java/com/klaviyo/forms/bridge/NativeBridgeMessage.kt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ internal sealed class NativeBridgeMessage {
5656
/**
5757
* Sent from the onsite-in-app-forms when a deep link is opened
5858
*
59-
* @param route The deep link route to be opened (usually a URL)
59+
* @param route The deep link route to be opened (usually a URL), or null if no Android route is configured
6060
*/
6161
data class OpenDeepLink(
62-
val route: String
62+
val route: String?
6363
) : NativeBridgeMessage()
6464

6565
/**
@@ -164,14 +164,9 @@ internal sealed class NativeBridgeMessage {
164164
}
165165

166166
/**
167-
* Parse out the android platform deep link
167+
* Parse out the android platform deep link, returning null if not present or empty
168168
*/
169-
private fun JSONObject.getDeepLink(): String {
170-
val routeString = optString("android")
171-
if (routeString.isNullOrEmpty()) {
172-
throw IllegalStateException("No android deeplink found in js payload")
173-
}
174-
return routeString
175-
}
169+
private fun JSONObject.getDeepLink(): String? =
170+
optString("android").takeIf { it.isNotEmpty() }
176171
}
177172
}

sdk/forms/src/test/java/com/klaviyo/forms/bridge/KlaviyoNativeBridgeTest.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,30 @@ internal class KlaviyoNativeBridgeTest : BaseTest() {
273273
verify { DeepLinking.handleDeepLink(mockUri) }
274274
}
275275

276+
@Test
277+
fun `openDeepLink with empty android route logs warning and does not navigate`() {
278+
/**
279+
* @see com.klaviyo.forms.bridge.KlaviyoNativeBridge.deepLink
280+
*/
281+
val emptyAndroidMessage = """
282+
{
283+
"type": "openDeepLink",
284+
"data": {
285+
"ios": "klaviyotest://settings",
286+
"android": ""
287+
}
288+
}
289+
""".trimIndent()
290+
291+
mockkObject(DeepLinking)
292+
every { DeepLinking.handleDeepLink(any<Uri>()) } returns Unit
293+
294+
postMessage(emptyAndroidMessage)
295+
296+
verify(exactly = 0) { DeepLinking.handleDeepLink(any<Uri>()) }
297+
verify { spyLog.warning(any(), null) }
298+
}
299+
276300
@Test
277301
fun `formDisappeared triggers close`() {
278302
/**

sdk/forms/src/test/java/com/klaviyo/forms/bridge/NativeBridgeMessageTest.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class NativeBridgeMessageTest : BaseTest() {
222222
}
223223

224224
@Test
225-
fun `deeplink does not have android field, throws error`() {
225+
fun `deeplink does not have android field, returns OpenDeepLink with null route`() {
226226
val deeplinkMessage = """
227227
{
228228
"type": "openDeepLink",
@@ -232,12 +232,26 @@ class NativeBridgeMessageTest : BaseTest() {
232232
}
233233
""".trimIndent()
234234

235-
every { Registry.log.error(any(), any<Throwable>()) } just Runs
235+
val result = NativeBridgeMessage.decodeWebviewMessage(deeplinkMessage) as NativeBridgeMessage.OpenDeepLink
236236

237-
// Act & Assert
238-
assertThrows(IllegalStateException::class.java) {
239-
NativeBridgeMessage.decodeWebviewMessage(deeplinkMessage)
240-
}
237+
assertEquals(NativeBridgeMessage.OpenDeepLink(route = null), result)
238+
}
239+
240+
@Test
241+
fun `deeplink with empty android field returns OpenDeepLink with null route`() {
242+
val deeplinkMessage = """
243+
{
244+
"type": "openDeepLink",
245+
"data": {
246+
"ios": "klaviyotest://settings",
247+
"android": ""
248+
}
249+
}
250+
""".trimIndent()
251+
252+
val result = NativeBridgeMessage.decodeWebviewMessage(deeplinkMessage) as NativeBridgeMessage.OpenDeepLink
253+
254+
assertEquals(NativeBridgeMessage.OpenDeepLink(route = null), result)
241255
}
242256

243257
@Test

0 commit comments

Comments
 (0)