From 18cab400291d1c57c3b6e9851de9be62d3b40972 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 3 Feb 2026 13:51:31 +0000 Subject: [PATCH 1/3] Fixed double callback problem for setEmail with auto push registration as false --- .../com/iterable/iterableapi/IterableApi.java | 10 +++++- .../iterable/iterableapi/IterableHelper.java | 8 +++++ .../iterableapi/IterableInAppManager.java | 2 +- .../iterable/iterableapi/IterableApiTest.java | 32 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 29b44bd94..c7b567017 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -451,7 +451,8 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, if (config.autoPushRegistration) { registerForPush(); } else if (_setUserSuccessCallbackHandler != null) { - _setUserSuccessCallbackHandler.onSuccess(new JSONObject()); // passing blank json object here as onSuccess is @Nonnull + _setUserSuccessCallbackHandler.onSuccess(IterableResponse.setEmailLocalSuccessResponse); + resetCallbackHandlers(); } getInAppManager().syncInApp(); @@ -747,6 +748,7 @@ private IterableHelper.SuccessHandler getSuccessHandler() { if (originalSuccessHandler != null) { originalSuccessHandler.onSuccess(data); + resetCallbackHandlers(); } }; } @@ -762,11 +764,17 @@ private IterableHelper.FailureHandler getFailureHandler() { if (originalFailureHandler != null) { originalFailureHandler.onFailure(reason, data); + resetCallbackHandlers(); } }; } return wrappedFailureHandler; } + + private void resetCallbackHandlers() { + _setUserFailureCallbackHandler = null; + _setUserSuccessCallbackHandler = null; + } //endregion //region SDK initialization diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java index a949a0727..dd3250317 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -6,6 +6,8 @@ import org.json.JSONObject; +import java.util.Map; + /** * Created by David Truong dt@iterable.com */ @@ -33,4 +35,10 @@ public interface FailureHandler { public interface SuccessAuthHandler { void onSuccess(@NonNull String authToken); } +} + +class IterableResponse { + static JSONObject setEmailLocalSuccessResponse = new JSONObject(Map.of("message", "setEmail was completed locally, but still requires the register device call. If possible, use autoPushRegistration.")); + + static JSONObject setReadLocalSuccessResponse = new JSONObject(Map.of("message", "setRead was completed locally, no remote call was done yet.")); } \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index 9a8589baf..288a66fc5 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -140,7 +140,7 @@ public synchronized void setRead(@NonNull IterableInAppMessage message, boolean public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { message.setRead(read); if (successHandler != null) { - successHandler.onSuccess(new JSONObject()); // passing blank json object here as onSuccess is @Nonnull + successHandler.onSuccess(IterableResponse.setReadLocalSuccessResponse); } notifyOnChange(); } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java index 463cf2b7c..b128af40c 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java @@ -1199,4 +1199,36 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingNotTriggeredWhenDis //endregion + @Test + public void testSetEmailWithCallbackAndManualRegisterDeviceToken_NoDoubleCallback() throws Exception { + // Setup: Mock server responses + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + IterableConfig config = new IterableConfig.Builder() + .setAutoPushRegistration(false) + .setPushIntegrationName("testIntegration") + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + + IterableApi.getInstance().setEmail("test@example.com", successHandler, null); + + verify(successHandler, times(1)).onSuccess(any(JSONObject.class)); + + IterableApi.getInstance().registerDeviceToken("test_token"); + + Thread.sleep(200); + shadowOf(getMainLooper()).idle(); + + verify(successHandler, times(1)).onSuccess(any(JSONObject.class)); + + RecordedRequest registerRequest = server.takeRequest(1, TimeUnit.SECONDS); + assertNotNull("Should have made registerDeviceToken request", registerRequest); + assertTrue("Should be registerDeviceToken endpoint", registerRequest.getPath().contains("registerDeviceToken")); + } + + //endregion + } From 863649e9ef4bdabf297eefd157b54c574d0891a7 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 3 Feb 2026 17:41:15 +0000 Subject: [PATCH 2/3] Fixed failure path for setEmail not clearing the handlers --- .../src/main/java/com/iterable/iterableapi/IterableApi.java | 1 + 1 file changed, 1 insertion(+) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index c7b567017..319968d6b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -444,6 +444,7 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, IterableLogger.w(TAG, "Cannot complete user login - JWT auth enabled but no validated authToken present"); if (_setUserFailureCallbackHandler != null) { _setUserFailureCallbackHandler.onFailure("JWT authentication is enabled but no valid authToken is available", null); + resetCallbackHandlers(); } return; } From e34e8b1b34cbfe252200630adf0d4d4a7c3a0935 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 3 Feb 2026 18:14:46 +0000 Subject: [PATCH 3/3] setEmailLocalSuccessResponse and setReadLocalSuccessResponse as final and with better messages --- .../main/java/com/iterable/iterableapi/IterableHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java index dd3250317..ffebfbc78 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -38,7 +38,7 @@ public interface SuccessAuthHandler { } class IterableResponse { - static JSONObject setEmailLocalSuccessResponse = new JSONObject(Map.of("message", "setEmail was completed locally, but still requires the register device call. If possible, use autoPushRegistration.")); + final static JSONObject setEmailLocalSuccessResponse = new JSONObject(Map.of("message", "setEmail was completed locally.")); - static JSONObject setReadLocalSuccessResponse = new JSONObject(Map.of("message", "setRead was completed locally, no remote call was done yet.")); + final static JSONObject setReadLocalSuccessResponse = new JSONObject(Map.of("message", "setRead was completed locally.")); } \ No newline at end of file