[SDK-717] Fix stale customAction replay when handler returns false - #1089
[SDK-717] Fix stale customAction replay when handler returns false#1089joaodordio wants to merge 4 commits into
Conversation
The IterableCustomActionHandler interface documents its boolean return value as 'Reserved for future use'. Clients routinely return false, as does the React Native SDK wrapper. Since the SDK-307 fix (3.6.5), returning false from the handler left pendingAction alive, causing the handler to replay on every app foreground and every initialize() call. Fix: treat invocation of the handler as consumed regardless of return value. Only keep pendingAction alive when the handler is null (SDK not yet initialized) — the original SDK-307 intent. Also remove the duplicate processPendingAction() call early in initialize(), before loadLastSavedConfiguration() runs. A single call after full initialization is sufficient. Affected versions: 3.6.5–3.10.1.
The previous approach always returned true from callCustomActionIfSpecified when a handler was present. That broke the openApp fallback in handlePushAction: 'if (openApp && !handled)' never fired, so the launcher activity was not started for push taps where the handler returned false. Move the fix to processPendingAction instead. Before dispatching, capture whether customActionHandler was present. After dispatching, clear pendingAction if handled=true OR if the handler was present — invocation itself is treated as consumed. The handler's return value is preserved as-is so the openApp fallback in handlePushAction continues to work.
|
This solution makes the openApp fallback work correctly, but customHandlerPresent does not necessarily mean the custom handler was invoked. From what i checked, an unhandled openUrl action is also cleared when an unrelated custom-action handler is configured, preventing its foreground retry. I though about adding improving readability with something internal like this enum ActionDispatchResult { This would make it easier to distinguish when to clear the pending action or when to run it. I think having this boolean when it actually can represent more things can be a bit confusing to debug and to understand what is the intended behavior (like the bug itself) |
Replace the customHandlerPresent boolean with a typed enum that cleanly
separates the two concerns a single boolean cannot express:
- whether pendingAction should be cleared
- whether the openApp launcher fallback should fire
NOT_DISPATCHED — handler was null, keep pendingAction for retry (SDK-307)
DISPATCHED_WITH_FALLBACK — handler ran but returned false, or URL failed;
clear pendingAction, allow openApp fallback
HANDLED — handler returned true or URL opened; clear pendingAction,
suppress openApp fallback
Also fixes Franco's callout: the previous customHandlerPresent check would
incorrectly clear a failed openUrl action whenever an unrelated custom
action handler was configured. dispatchPendingAction now checks handler
presence only for non-URL actions, so URL and custom action paths are
independently reasoned about.
|
I was checking this PR with some tests and realized that we introduced a new problem, currently if the app is closed and a push url is passed, we silently fail it. Currently on the initializeForPush we just pass the android context, not the iterable config. We could rename NOT_DISPATCHED to RETRY_LATER and return it instead in this case, so a failed URL before full initialization should produce RETRY_LATER, not DISPATCHED_WITH_FALLBACK. |
When only initializeForPush ran (_apiKey null), the SDK lacks the full config (urlHandler, deep link handlers). A URL push action would fall through dispatchPendingAction to DISPATCHED_WITH_FALLBACK, clearing pendingAction and silently losing the action before initialize() ran. Fix: URL actions now check _apiKey == null and return RETRY_LATER, so they are deferred alongside custom actions when the SDK is not yet fully initialized. Also renames NOT_DISPATCHED -> RETRY_LATER throughout — clearer intent. Adds testBackgroundUrlActionDeferredUntilSDKInit and a matching fixture to cover the openApp=false + URL action + pre-init path.
📝 Summary
Fix stale
IterableCustomActionHandlerreplay when handler returnsfalse, a regression present in 3.6.5–3.10.1.🎟️ Jira Ticket: SDK-717
📖 Description
The
IterableCustomActionHandlerinterface documents its boolean return value as "Reserved for future use". Clients commonly returnfalse(Kotlin default, follows the javadoc), and the React Native SDK wrapper always does too. Since the SDK-307 fix (3.6.5, PR #975), returningfalsewas treated as "action not consumed", leavingpendingActionalive and causing the handler to fire again on every foreground and everyinitialize()call.Three replay paths:
onForeground()callsprocessPendingActionon every app foregroundinitialize()called it twice — before and afterloadLastSavedConfiguration()super.onResume()triggersonActivityResumed → onForeground → processPendingActionon the stale action beforehandlePushActionsets the new oneIterableActionRunner.java—callCustomActionIfSpecifiednow invokes the handler and always returnstrue(consumed). Returnsfalseonly when the handler isnull(SDK not yet initialized), keeping the SDK-307 retry intent intact.IterableApi.java— removed the duplicateprocessPendingAction()call beforeloadLastSavedConfiguration()ininitialize(). One call after full initialization is enough.🧪 How to test?
Added
testCustomActionHandlerReturnFalseDoesNotReplayOnForegroundtoIterablePushActionReceiverTest: sends a push with a handler that returnsfalse, simulates a foreground event viaprocessPendingAction, and asserts the handler fired exactly once.Existing
testBackgroundCustomActionProcessedAfterSDKInitcontinues to cover the SDK-307 scenario (handler null at push time, retried after initialize).🧾 Changelog
Added a
Fixedentry toCHANGELOG.mdunder[Unreleased]calling out affected versions 3.6.5–3.10.1.📹 Loom recording if applicable
N/A
🐞 Github Issues solved
N/A
📚 Docs PR if applicable
N/A