From 1102f24928f7eaeaa781ecb16663d28a4ebb9a80 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 20 Jan 2026 17:25:24 +0000 Subject: [PATCH 1/5] Comments and changes to fix the double callback problem --- .../com/iterable/iterableapi/IterableApi.java | 16 ++++++++++------ .../iterable/iterableapi/UnknownUserMerge.java | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 1f5ed6a38..5c61d4bf3 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -405,7 +405,7 @@ private void onLogin( boolean isUnknown, @Nullable IterableHelper.FailureHandler failureHandler ) { - if (!isInitialized()) { + if (!isInitialized()) { //todo: If we get here and it is not initialized, isn't it possible to have leftover data that was already set by setEmail? setAuthToken(null); return; } @@ -413,7 +413,7 @@ private void onLogin( getAuthManager().pauseAuthRetries(false); if (authToken != null) { setAuthToken(authToken); - attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); + attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); //todo: Why do we need to do this again if we did this on setEmail? } else { getAuthManager().requestNewAuthToken(false, data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler)); } @@ -449,8 +449,9 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, } if (config.autoPushRegistration) { - registerForPush(); - } else if (_setUserSuccessCallbackHandler != null) { + registerForPush(); //TODO: FIX, THE LOGIN NEVER CALLS THE CALLBACK IF THE AUTOPUSH REGISTRATION IS TRUE + } + if (_setUserSuccessCallbackHandler != null) { // todo: why is there an else if it can be both true _setUserSuccessCallbackHandler.onSuccess(new JSONObject()); // passing blank json object here as onSuccess is @Nonnull } @@ -738,7 +739,7 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user private IterableHelper.SuccessHandler getSuccessHandler() { IterableHelper.SuccessHandler wrappedSuccessHandler = null; if (_setUserSuccessCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { - final IterableHelper.SuccessHandler originalSuccessHandler = _setUserSuccessCallbackHandler; + final IterableHelper.SuccessHandler originalSuccessHandler = null; //todo: MAYBE THIS HAS TO DO WITH THE ERROR, IF THE PERSON IS SETTING EMAIL AND REGISTERING DEVICE TOKEN THEY WILL GET THE WRONG CALLBACK CALLED wrappedSuccessHandler = data -> { trackConsentOnDeviceRegistration(); @@ -1023,6 +1024,9 @@ public void setEmail(@Nullable String email, @Nullable String authToken, @Nullab if (_email != null && _email.equals(email)) { checkAndUpdateAuthToken(authToken); + if (successHandler != null) { + successHandler.onSuccess(new JSONObject()); + } return; } @@ -1133,7 +1137,7 @@ private boolean isReplay(@Nullable IterableIdentityResolution iterableIdentityRe private void attemptMergeAndEventReplay(@Nullable String emailOrUserId, boolean isEmail, boolean merge, boolean replay, boolean isUnknown, IterableHelper.FailureHandler failureHandler) { if (config.enableUnknownUserActivation && getVisitorUsageTracked()) { - if (emailOrUserId != null && !emailOrUserId.equals(_userIdUnknown)) { + if (emailOrUserId != null && !emailOrUserId.equals(_userIdUnknown)) { //todo: when would the userIdUnknown be the same? attemptAndProcessMerge(emailOrUserId, isEmail, merge, failureHandler, _userIdUnknown); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserMerge.java b/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserMerge.java index 4a63e3222..db4485318 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserMerge.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserMerge.java @@ -5,7 +5,7 @@ public class UnknownUserMerge { void tryMergeUser(IterableApiClient apiClient, String unknownUserId, String destinationUser, boolean isEmail, boolean merge, MergeResultCallback callback) { IterableLogger.v(TAG, "tryMergeUser"); - if (unknownUserId != null && merge) { + if (unknownUserId != null && merge) { //todo: why can we try to merge and have merge false? String destinationEmail = isEmail ? destinationUser : null; String destinationUserId = isEmail ? null : destinationUser; apiClient.mergeUser(null, unknownUserId, destinationEmail, destinationUserId, data -> { From 3f9c55614bfe7aeb558941606693d9f39dcd2da3 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Fri, 23 Jan 2026 11:25:29 +0000 Subject: [PATCH 2/5] New Success Callbacks to handle different scenarios --- .../iterableapi/IterableApiResponseTest.java | 4 +- .../com/iterable/iterableapi/IterableApi.java | 90 ++--- .../iterableapi/IterableApiClient.java | 20 +- .../iterableapi/IterableAuthManager.java | 20 +- .../iterableapi/IterableEmbeddedManager.kt | 5 +- .../iterable/iterableapi/IterableHelper.java | 196 +++++++++- .../iterableapi/IterableInAppManager.java | 6 +- .../iterableapi/IterableRequestTask.java | 23 +- .../iterableapi/IterableResponseObject.kt | 51 +++ .../iterableapi/OfflineRequestProcessor.java | 13 +- .../iterableapi/OnlineRequestProcessor.java | 4 +- .../iterableapi/RequestProcessor.java | 4 +- .../IterableApiAuthSecurityTests.java | 4 +- .../iterableapi/IterableApiRequestTest.java | 4 +- .../iterable/iterableapi/IterableApiTest.java | 24 +- .../IterableAsyncInitializationTest.java | 4 +- .../iterableapi/IterableHelperUnitTest.java | 343 ++++++++++++++++++ .../IterableInAppHTMLNotificationTest.java | 1 + .../IterableInAppManagerSyncTest.java | 1 + .../iterableapi/IterableInAppManagerTest.java | 1 - .../iterableapi/IterableInboxTest.java | 12 +- .../IterablePushRegistrationTaskTest.java | 4 +- .../iterableapi/TaskSchedulerTest.java | 4 +- .../unit/IterableHelperUnitTest.java | 26 -- 24 files changed, 718 insertions(+), 146 deletions(-) create mode 100644 iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt create mode 100644 iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java delete mode 100644 iterableapi/src/test/java/com/iterable/iterableapi/unit/IterableHelperUnitTest.java diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java index afab7676b..bcda39d86 100644 --- a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java +++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java @@ -77,7 +77,7 @@ public void testResponseCode200() throws Exception { final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); stubAnyRequestReturningStatusCode(200, responseData); - IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.SuccessHandler() { + IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.IterableSuccessCallback() { @Override public void onSuccess(@NonNull JSONObject data) { assertEquals(responseData.toString(), data.toString()); @@ -222,7 +222,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) { "}"); stubAnyRequestReturningStatusCode(200, responseData); - new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.SuccessHandler() { + new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.IterableSuccessCallback() { @Override public void onSuccess(@NonNull JSONObject successData) { try { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 5c61d4bf3..248be3f28 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -43,7 +43,7 @@ public class IterableApi { private IterableNotificationData _notificationData; private String _deviceId; private boolean _firstForegroundHandled; - private IterableHelper.SuccessHandler _setUserSuccessCallbackHandler; + private IterableHelper.IterableSuccessCallback _setUserSuccessCallbackHandler; private IterableHelper.FailureHandler _setUserFailureCallbackHandler; IterableApiClient apiClient = new IterableApiClient(new IterableApiAuthProvider()); @@ -310,7 +310,7 @@ public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull Iterable * @param onFailure */ - public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { if (!checkSDKInitialization()) { return; } @@ -328,7 +328,7 @@ public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull Iterable * @param onSuccess * @param onFailure */ - void getEmbeddedMessages(@NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void getEmbeddedMessages(@NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { if (!checkSDKInitialization()) { return; } @@ -415,7 +415,8 @@ private void onLogin( setAuthToken(authToken); attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); //todo: Why do we need to do this again if we did this on setEmail? } else { - getAuthManager().requestNewAuthToken(false, data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler)); + IterableHelper.AuthTokenCallback callback = data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); + getAuthManager().requestNewAuthToken(false, callback); } } @@ -449,16 +450,21 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, } if (config.autoPushRegistration) { - registerForPush(); //TODO: FIX, THE LOGIN NEVER CALLS THE CALLBACK IF THE AUTOPUSH REGISTRATION IS TRUE - } - if (_setUserSuccessCallbackHandler != null) { // todo: why is there an else if it can be both true - _setUserSuccessCallbackHandler.onSuccess(new JSONObject()); // passing blank json object here as onSuccess is @Nonnull + registerForPush(); + } else if (_setUserSuccessCallbackHandler != null) { + invokeSuccessHandlerAndClear(); } getInAppManager().syncInApp(); getEmbeddedManager().syncMessages(); } + private void invokeSuccessHandlerAndClear() { + _setUserSuccessCallbackHandler.onSuccess(IterableResponseObject.LocalSuccessResponse); + _setUserSuccessCallbackHandler = null; + _setUserFailureCallbackHandler = null; + } + private final IterableActivityMonitor.AppStateCallback activityMonitorListener = new IterableActivityMonitor.AppStateCallback() { @Override public void onSwitchToForeground() { @@ -701,7 +707,7 @@ protected void disableToken(@Nullable String email, @Nullable String userId, @No * @param authToken * @param deviceToken The device token */ - protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { if (deviceToken == null) { IterableLogger.d(TAG, "device token not available"); return; @@ -730,16 +736,16 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user IterableLogger.e(TAG, "registerDeviceToken: applicationName is null, check that pushIntegrationName is set in IterableConfig"); } - IterableHelper.SuccessHandler wrappedSuccessHandler = getSuccessHandler(); - IterableHelper.FailureHandler wrappedFailureHandler = getFailureHandler(); + IterableHelper.IterableSuccessCallback wrappedSuccessHandler = wrapSetUserCallbackForRemoteCall(); + IterableHelper.FailureHandler wrappedFailureHandler = wrapSetUserFailureHandlerForRemoteCall(); apiClient.registerDeviceToken(email, userId, authToken, applicationName, deviceToken, dataFields, deviceAttributes, wrappedSuccessHandler, wrappedFailureHandler); } - private IterableHelper.SuccessHandler getSuccessHandler() { - IterableHelper.SuccessHandler wrappedSuccessHandler = null; + private IterableHelper.IterableSuccessCallback wrapSetUserCallbackForRemoteCall() { + IterableHelper.RemoteSuccessCallback wrappedSuccessHandler = null; if (_setUserSuccessCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { - final IterableHelper.SuccessHandler originalSuccessHandler = null; //todo: MAYBE THIS HAS TO DO WITH THE ERROR, IF THE PERSON IS SETTING EMAIL AND REGISTERING DEVICE TOKEN THEY WILL GET THE WRONG CALLBACK CALLED + final IterableHelper.IterableSuccessCallback originalSuccessHandler = _setUserSuccessCallbackHandler; wrappedSuccessHandler = data -> { trackConsentOnDeviceRegistration(); @@ -751,7 +757,7 @@ private IterableHelper.SuccessHandler getSuccessHandler() { return wrappedSuccessHandler; } - private IterableHelper.FailureHandler getFailureHandler() { + private IterableHelper.FailureHandler wrapSetUserFailureHandlerForRemoteCall() { IterableHelper.FailureHandler wrappedFailureHandler = null; if (_setUserFailureCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { final IterableHelper.FailureHandler originalFailureHandler = _setUserFailureCallbackHandler; @@ -986,7 +992,11 @@ public IterableAttributionInfo getAttributionInfo() { public void pauseAuthRetries(boolean pauseRetry) { getAuthManager().pauseAuthRetries(pauseRetry); if (!pauseRetry) { // request new auth token as soon as unpause - getAuthManager().requestNewAuthToken(false, null); + IterableHelper.AuthTokenCallback callback = null; + getAuthManager().requestNewAuthToken( + false, + callback + ); } } @@ -998,11 +1008,11 @@ public void setEmail(@Nullable String email, IterableIdentityResolution identity queueOrExecute(() -> setEmail(email, null, identityResolution, null, null), "setEmail(" + maskPII(email) + ", identityResolution)"); } - public void setEmail(@Nullable String email, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, null, null, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", callbacks)"); } - public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, null, identityResolution, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", identityResolution, callbacks)"); } @@ -1014,19 +1024,16 @@ public void setEmail(@Nullable String email, @Nullable String authToken, Iterabl queueOrExecute(() -> setEmail(email, authToken, identityResolution, null, null), "setEmail(" + maskPII(email) + ", " + maskPII(authToken) + ", identityResolution)"); } - public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, authToken, null, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", " + maskPII(authToken) + ", callbacks)"); } - public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { boolean replay = isReplay(iterableIdentityResolution); boolean merge = isMerge(iterableIdentityResolution); if (_email != null && _email.equals(email)) { checkAndUpdateAuthToken(authToken); - if (successHandler != null) { - successHandler.onSuccess(new JSONObject()); - } return; } @@ -1070,11 +1077,11 @@ public void setUserId(@Nullable String userId, IterableIdentityResolution identi queueOrExecute(() -> setUserId(userId, null, identityResolution, null, null, false), "setUserId(" + maskPII(userId) + ", identityResolution)"); } - public void setUserId(@Nullable String userId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, null, null, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", callbacks)"); } - public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, null, identityResolution, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", identityResolution, callbacks)"); } @@ -1087,11 +1094,11 @@ public void setUserId(@Nullable String userId, @Nullable String authToken, Itera } - public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, authToken, null, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", " + maskPII(authToken) + ", callbacks)"); } - public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler, boolean isUnknown) { + public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler, boolean isUnknown) { boolean replay = isReplay(iterableIdentityResolution); boolean merge = isMerge(iterableIdentityResolution); @@ -1256,7 +1263,7 @@ public void inAppConsume(@NonNull String messageId) { * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public void inAppConsume(@NonNull String messageId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull String messageId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { IterableInAppMessage message = getInAppManager().getMessageById(messageId); if (checkIfMessageIsNull(message, failureHandler)) { return; @@ -1295,7 +1302,7 @@ public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable Iterab * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { if (!checkSDKInitialization()) { return; } @@ -1502,7 +1509,7 @@ public void updateEmail(final @NonNull String newEmail, final @NonNull String au queueOrExecute(() -> updateEmail(newEmail, authToken, null, null), "updateEmail(" + maskPII(newEmail) + ", " + maskPII(authToken) + ")"); } - public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> updateEmail(newEmail, null, successHandler, failureHandler), "updateEmail(" + maskPII(newEmail) + ", callbacks)"); } @@ -1513,7 +1520,7 @@ public void updateEmail(final @NonNull String newEmail, final @Nullable Iterable * @param successHandler Success handler. Called when the server returns a success code. * @param failureHandler Failure handler. Called when the server call failed. */ - public void updateEmail(final @NonNull String newEmail, final @Nullable String authToken, final @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable String authToken, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { if (!checkSDKInitialization()) { IterableLogger.e(TAG, "The Iterable SDK must be initialized with email or userId before " + "calling updateEmail"); @@ -1525,20 +1532,17 @@ public void updateEmail(final @NonNull String newEmail, final @Nullable String a return; } - apiClient.updateEmail(newEmail, new IterableHelper.SuccessHandler() { - @Override - public void onSuccess(@NonNull JSONObject data) { - if (_email != null) { - _email = newEmail; - _authToken = authToken; - } + apiClient.updateEmail(newEmail, data -> { + if (_email != null) { + _email = newEmail; + _authToken = authToken; + } - storeAuthData(); - getAuthManager().requestNewAuthToken(false, null); + storeAuthData(); + getAuthManager().requestNewAuthToken(false, null); - if (successHandler != null) { - successHandler.onSuccess(data); - } + if (successHandler != null) { + successHandler.onSuccess(data); } }, failureHandler); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java index bdb3a2578..af2ca3373 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java @@ -222,7 +222,7 @@ public void trackPurchase(double total, @NonNull List items, @Null } } - public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { JSONObject requestJSON = new JSONObject(); try { @@ -319,7 +319,7 @@ void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper. } } - void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { JSONObject requestJSON = new JSONObject(); try { @@ -488,7 +488,7 @@ void trackEmbeddedMessageReceived(@NonNull IterableEmbeddedMessage message) { } - public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable String inboxSessionId, @Nullable final IterableHelper.SuccessHandler successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable String inboxSessionId, @Nullable final IterableHelper.IterableSuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { JSONObject requestJSON = new JSONObject(); try { @@ -604,7 +604,7 @@ protected void trackPushOpen(int campaignId, int templateId, @NonNull String mes } } - protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { JSONObject requestJSON = new JSONObject(); try { requestJSON.put(IterableConstants.KEY_TOKEN, deviceToken); @@ -620,7 +620,7 @@ protected void disableToken(@Nullable String email, @Nullable String userId, @Nu } } - protected void registerDeviceToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String applicationName, @NonNull String deviceToken, @Nullable JSONObject dataFields, HashMap deviceAttributes, @Nullable final IterableHelper.SuccessHandler successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { + protected void registerDeviceToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String applicationName, @NonNull String deviceToken, @Nullable JSONObject dataFields, HashMap deviceAttributes, @Nullable final IterableHelper.IterableSuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { Context context = authProvider.getContext(); JSONObject requestJSON = new JSONObject(); try { @@ -756,11 +756,11 @@ void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nu sendPostRequest(resourcePath, json, authToken, null, null); } - void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { sendPostRequest(resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure); } - void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { getRequestProcessor().processPostRequest(authProvider.getApiKey(), resourcePath, json, authToken, onSuccess, onFailure); } @@ -774,7 +774,7 @@ void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nul getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onCallback); } - void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure); } @@ -783,7 +783,7 @@ void onLogout() { authProvider.resetAuth(); } - void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { JSONObject requestJson = new JSONObject(); try { if (sourceEmail != null && !sourceEmail.isEmpty()) { @@ -808,7 +808,7 @@ void getCriteriaList(@Nullable IterableHelper.IterableActionHandler actionHandle sendGetRequest(IterableConstants.ENDPOINT_CRITERIA_LIST, new JSONObject(), actionHandler); } - void trackUnknownUserSession(long createdAt, String userId, @NonNull JSONObject requestJson, JSONObject updateUserTrack, @NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void trackUnknownUserSession(long createdAt, String userId, @NonNull JSONObject requestJson, JSONObject updateUserTrack, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { try { JSONObject requestObject = new JSONObject(); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java index 915dbbb2a..9a7a40e71 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java @@ -2,12 +2,14 @@ import android.util.Base64; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Timer; import java.util.TimerTask; @@ -45,7 +47,7 @@ public class IterableAuthManager implements IterableActivityMonitor.AppStateCall this.activityMonitor.addCallback(this); } - public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, IterableHelper.SuccessHandler successCallback) { + public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, IterableHelper.IterableSuccessCallback successCallback) { requestNewAuthToken(hasFailedPriorAuth, successCallback, true); } @@ -67,19 +69,19 @@ void resetRetryCount() { retryCount = 0; } - private void handleSuccessForAuthToken(String authToken, IterableHelper.SuccessHandler successCallback) { + private void handleSuccessForAuthToken(String authToken, IterableHelper.IterableSuccessCallback successCallback) { try { - JSONObject object = new JSONObject(); - object.put("newAuthToken", authToken); - successCallback.onSuccess(object); - } catch (JSONException e) { + if(authToken == null) throw new RuntimeException("Auth Token is null"); + IterableResponseObject.AuthTokenSuccess remoteSuccess = new IterableResponseObject.AuthTokenSuccess(authToken); + successCallback.onSuccess(remoteSuccess); + } catch (RuntimeException e) { e.printStackTrace(); } } public synchronized void requestNewAuthToken( boolean hasFailedPriorAuth, - final IterableHelper.SuccessHandler successCallback, + final IterableHelper.IterableSuccessCallback successCallback, boolean shouldIgnoreRetryPolicy) { if (!shouldIgnoreRetryPolicy && (pauseAuthRetry || (retryCount >= authRetryPolicy.maxRetry))) { return; @@ -130,7 +132,7 @@ public void run() { } } - private void handleAuthTokenSuccess(String authToken, IterableHelper.SuccessHandler successCallback) { + private void handleAuthTokenSuccess(String authToken, IterableHelper.IterableSuccessCallback successCallback) { if (authToken != null) { IterableApi.getInstance().setAuthToken(authToken); queueExpirationRefresh(authToken); @@ -210,7 +212,7 @@ long getNextRetryInterval() { return nextRetryInterval; } - void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableHelper.SuccessHandler successCallback) { + void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableHelper.IterableSuccessCallback successCallback) { if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) { // we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work return; diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt index 8b53171bc..de66ea72e 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt @@ -1,7 +1,6 @@ package com.iterable.iterableapi import android.content.Context -import com.iterable.iterableapi.IterableHelper.SuccessHandler import org.json.JSONException import org.json.JSONObject @@ -92,11 +91,13 @@ public class IterableEmbeddedManager : IterableActivityMonitor.AppStateCallback IterableApi.sharedInstance.getEmbeddedMessages(placementIds, { data -> IterableLogger.v(TAG, "Got response from network call to get embedded messages") try { + val remoteData = data as (IterableResponseObject.RemoteSuccess) val previousPlacementIds = getPlacementIds() val currentPlacementIds: MutableList = mutableListOf() + val placementsArray = - data.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) + remoteData.responseJson.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) if (placementsArray != null) { //if there are no placements in the payload //reset the local message storage and trigger a UI update diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java index a949a0727..678014ced 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -1,9 +1,11 @@ package com.iterable.iterableapi; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.json.JSONException; import org.json.JSONObject; /** @@ -22,14 +24,206 @@ public interface IterableUrlCallback { void execute(@Nullable Uri url); } - public interface SuccessHandler { + /** + * @deprecated Use {@link IterableSuccessCallback} instead. + *

+ * This interface is deprecated and will be removed in a future version. + * Replace all usages with {@link IterableSuccessCallback} for general success handling, + * or {@link RemoteSuccessCallback} if you specifically need JSON response data from the API. + *

+ * Quick Migration (Recommended): Replace {@code SuccessHandler} with {@code IterableSuccessCallback} + *

+     * // REPLACE THIS:
+     * IterableHelper.SuccessHandler successHandler = data -> {
+     *     // Your code here
+     * };
+     *
+     * // WITH THIS (see JavaDoc on IterableSuccessCallback for more details):
+     * IterableHelper.IterableSuccessCallback successHandler = data -> {
+     *     // Your code here - see JavaDoc for accessing response data
+     * };
+     * 
+ * + * @see IterableSuccessCallback + * @see RemoteSuccessCallback + * @see LocalSuccessCallback + */ + @Deprecated + public interface SuccessHandler extends IterableSuccessCallback { void onSuccess(@NonNull JSONObject data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + IterableLogger.w("IterableHelper", "SuccessHandler is deprecated. Please migrate to IterableSuccessCallback or RemoteSuccessCallback. " + + "See JavaDoc for migration guide. Current success type: " + data.getClass().getSimpleName()); + + JSONObject jsonObject = new JSONObject(); + try { + if (data instanceof IterableResponseObject.RemoteSuccess) { + JSONObject originalJson = ((IterableResponseObject.RemoteSuccess) data).getResponseJson(); + jsonObject = new JSONObject(originalJson.toString()); + } else if (data instanceof IterableResponseObject.AuthTokenSuccess) { + jsonObject.put("newAuthToken", ((IterableResponseObject.AuthTokenSuccess) data).getAuthToken()); + } else if (data instanceof IterableResponseObject.LocalSuccess) { + jsonObject.put("message", data.getMessage()); + } else { + jsonObject.put("message", data.getMessage()); + } + } catch (JSONException e) { + IterableLogger.e("IterableHelper", "Error creating JSON for deprecated SuccessHandler callback", e); + } + + onSuccess(jsonObject); + } + } + + /** + * Generic callback interface for successful SDK operations. + *

+ * When to use this callback: + *

    + *
  • When you want to proceed on any type of success (remote, local, or auth token)
  • + *
  • When you don't need to access specific response data (like JSON from API or auth tokens)
  • + *
  • When you just need to know the operation completed successfully, regardless of how
  • + *
+ *

+ * Example use cases: + *

    + *
  • {@code setEmail()} - Can complete locally (if autoPushRegistration is false) or remotely (via registerForPush)
  • + *
  • {@code setUserId()} - May complete locally or trigger remote operations
  • + *
  • Any operation where you just need confirmation of success without caring about the response details
  • + *
+ *

+ * When to use specialized callbacks instead: + *

    + *
  • Use {@link RemoteSuccessCallback} if you need to access JSON response data from the API
  • + *
  • Use {@link LocalSuccessCallback} if you only want to proceed when no remote call was made
  • + *
  • Use {@link AuthTokenCallback} if you need to access the authentication token
  • + *
+ * + * @see RemoteSuccessCallback + * @see LocalSuccessCallback + * @see AuthTokenCallback + */ + public interface IterableSuccessCallback { + void onSuccess(@NonNull IterableResponseObject.Success data); + } + + /** + * Callback specifically for operations that make remote API calls and return JSON response data. + *

+ * When to use this callback: + *

    + *
  • When you need to access the JSON response data from the Iterable API
  • + *
  • When you want your callback to trigger only if a remote API call was made
  • + *
  • When the operation you're calling always makes a remote API request
  • + *
+ *

+ * Example use cases: + *

    + *
  • {@code trackEvent()} - Always makes a remote API call with JSON response
  • + *
  • {@code updateUser()} - Makes a remote call to update user profile
  • + *
  • Operations where you need to inspect the server's response data
  • + *
+ *

+ * Important: If the operation completes locally (e.g., {@code setEmail} with autoPushRegistration disabled), + * your callback will NOT be triggered. Use {@link IterableSuccessCallback} instead if you want to handle both + * remote and local success cases. + */ + public interface RemoteSuccessCallback extends IterableSuccessCallback { + void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableResponseObject.RemoteSuccess) { + onSuccess((IterableResponseObject.RemoteSuccess) data); + } else { + IterableLogger.w("IterableHelper", "RemoteSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback only triggers for remote API responses. Consider using IterableSuccessCallback if you want to handle all success types."); + } + } + } + + /** + * Callback specifically for authentication token operations. + *

+ * When to use this callback: + *

    + *
  • When you need to access the JWT authentication token from the response
  • + *
  • For operations that request or refresh auth tokens
  • + *
  • When implementing custom auth token handling or caching
  • + *
+ *

+ * Example use cases: + *

    + *
  • Auth token refresh operations triggered by {@link IterableAuthHandler}
  • + *
  • Operations that return a new JWT token for the current user
  • + *
  • Custom auth token validation or storage logic
  • + *
+ *

+ * Note: This is primarily used internally by the SDK's auth system. Most applications + * won't need to use this callback directly. + */ + public interface AuthTokenCallback extends IterableSuccessCallback { + void onSuccess(@NonNull IterableResponseObject.AuthTokenSuccess data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableResponseObject.AuthTokenSuccess) { + onSuccess((IterableResponseObject.AuthTokenSuccess) data); + } else { + IterableLogger.w("IterableHelper", "AuthTokenCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback is for auth token operations only."); + } + } + } + + /** + * Callback specifically for operations that complete locally without making a remote API call. + *

+ * When to use this callback: + *

    + *
  • When you want your callback to trigger only if the operation completed locally
  • + *
  • When you want to distinguish between operations that hit the server vs. those that don't
  • + *
  • For testing or debugging purposes to verify no remote call was made
  • + *
+ *

+ * Example use cases: + *

    + *
  • {@code setEmail()} with {@code autoPushRegistration = false} - Updates locally without calling the API
  • + *
  • Operations that only update local state or keychain
  • + *
  • Scenarios where you want different behavior based on whether a network call occurred
  • + *
+ *

+ * Important: If the operation makes a remote call (e.g., {@code setEmail} with autoPushRegistration enabled), + * your callback will NOT be triggered. Use {@link IterableSuccessCallback} instead if you want to handle both + * local and remote success cases. + */ + public interface LocalSuccessCallback extends IterableSuccessCallback { + void onSuccess(@NonNull IterableResponseObject.LocalSuccess data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableResponseObject.LocalSuccess) { + onSuccess((IterableResponseObject.LocalSuccess) data); + } else { + IterableLogger.w("IterableHelper", "LocalSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback only triggers for local-only operations. Consider using IterableSuccessCallback if you want to handle all success types."); + } + } } public interface FailureHandler { void onFailure(@NonNull String reason, @Nullable JSONObject data); } + /** + * @deprecated Use {@link AuthTokenCallback} instead for better type safety and clarity. + */ + @Deprecated public interface SuccessAuthHandler { void onSuccess(@NonNull String authToken); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index 9a8589baf..c63abfc61 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -137,10 +137,10 @@ public synchronized void setRead(@NonNull IterableInAppMessage message, boolean * @param read Read state flag. true = read, false = unread * @param successHandler The callback which returns `success`. */ - public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableHelper.IterableSuccessCallback 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(IterableResponseObject.LocalSuccessResponse); // passing blank json object here as onSuccess is @Nonnull } notifyOnChange(); } @@ -279,7 +279,7 @@ public synchronized void removeMessage(@NonNull IterableInAppMessage message, @N * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public synchronized void removeMessage(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public synchronized void removeMessage(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { IterableLogger.printInfo(); if (message != null) { message.setConsumed(true); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java index f052da780..07b999399 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java @@ -7,11 +7,14 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.google.gson.JsonIOException; + import org.json.JSONException; import org.json.JSONObject; @@ -375,7 +378,8 @@ private void handleSuccessResponse(IterableApiResponse response) { } if (iterableApiRequest.successCallback != null) { - iterableApiRequest.successCallback.onSuccess(response.responseJson); + IterableResponseObject.Success iterableResponse = new IterableResponseObject.RemoteSuccess(response.responseJson); + iterableApiRequest.successCallback.onSuccess(iterableResponse); } } @@ -395,12 +399,9 @@ private static void requestNewAuthTokenAndRetry(IterableApiRequest iterableApiRe IterableApi.getInstance().getAuthManager().setIsLastAuthTokenValid(false); long retryInterval = IterableApi.getInstance().getAuthManager().getNextRetryInterval(); IterableApi.getInstance().getAuthManager().scheduleAuthTokenRefresh(retryInterval, false, data -> { - try { - String newAuthToken = data.getString("newAuthToken"); - retryRequestWithNewAuthToken(newAuthToken, iterableApiRequest); - } catch (JSONException e) { - e.printStackTrace(); - } + IterableResponseObject.AuthTokenSuccess authTokenResponse = (IterableResponseObject.AuthTokenSuccess) data; + String newAuthToken = authTokenResponse.getAuthToken(); + retryRequestWithNewAuthToken(newAuthToken, iterableApiRequest); }); } @@ -427,7 +428,7 @@ class IterableApiRequest { private ProcessorType processorType = ProcessorType.ONLINE; IterableHelper.IterableActionHandler legacyCallback; - IterableHelper.SuccessHandler successCallback; + IterableHelper.IterableSuccessCallback successCallback; IterableHelper.FailureHandler failureCallback; enum ProcessorType { @@ -455,7 +456,7 @@ void setProcessorType(ProcessorType processorType) { this.processorType = processorType; } - IterableApiRequest(String apiKey, String baseUrl, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.SuccessHandler onSuccess, IterableHelper.FailureHandler onFailure) { + IterableApiRequest(String apiKey, String baseUrl, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.IterableSuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { this.apiKey = apiKey; this.baseUrl = baseUrl; this.resourcePath = resourcePath; @@ -466,7 +467,7 @@ void setProcessorType(ProcessorType processorType) { this.failureCallback = onFailure; } - IterableApiRequest(String apiKey, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.SuccessHandler onSuccess, IterableHelper.FailureHandler onFailure) { + IterableApiRequest(String apiKey, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.IterableSuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { this.apiKey = apiKey; this.baseUrl = null; this.resourcePath = resourcePath; @@ -497,7 +498,7 @@ public JSONObject toJSONObject() throws JSONException { return jsonObject; } - static IterableApiRequest fromJSON(JSONObject jsonData, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + static IterableApiRequest fromJSON(JSONObject jsonData, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { try { String apikey = jsonData.getString("apiKey"); String resourcePath = jsonData.getString("resourcePath"); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt new file mode 100644 index 000000000..94343de53 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt @@ -0,0 +1,51 @@ +package com.iterable.iterableapi + +import org.json.JSONObject + +sealed class IterableResponseObject( + val message: String, + val code: IterableResponseCode +) { + sealed class Success( + message: String, + ): IterableResponseObject(message, IterableResponseCode.SUCCESS) + + class GenericSuccess( + message: String, + ): Success(message) + + class RemoteSuccess(val responseJson: JSONObject): Success( + message = SuccessMessages.REMOTE_SUCCESS + ) + + class AuthTokenSuccess( + val authToken: String + ): Success( + message = SuccessMessages.AUTH_TOKEN_SUCCESS, + ) + + object LocalSuccess: Success( + message = SuccessMessages.LOCAL_SUCCESS, + ) + + + class Failure(remoteMessage: String): IterableResponseObject( + message = remoteMessage, + code = IterableResponseCode.FAILURE + ) + + companion object { + @JvmField + val LocalSuccessResponse = LocalSuccess + } + + private object SuccessMessages { + const val REMOTE_SUCCESS = "Successfully received response from remote API" + const val AUTH_TOKEN_SUCCESS = "Successfully obtained authentication token" + const val LOCAL_SUCCESS = "Operation completed locally without remote API call" + } +} + +enum class IterableResponseCode { + SUCCESS, FAILURE +} \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java index e60b08293..684e610c2 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java @@ -60,13 +60,13 @@ public void processGetRequest(@Nullable String apiKey, @NonNull String resourceP } @Override - public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, json, IterableApiRequest.GET, authToken, onSuccess, onFailure); new IterableRequestTask().execute(request); } @Override - public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, json, IterableApiRequest.POST, authToken, onSuccess, onFailure); if (isRequestOfflineCompatible(request.resourcePath) && healthMonitor.canSchedule()) { request.setProcessorType(IterableApiRequest.ProcessorType.OFFLINE); @@ -87,7 +87,7 @@ boolean isRequestOfflineCompatible(String baseUrl) { } class TaskScheduler implements IterableTaskRunner.TaskCompletedListener { - static HashMap successCallbackMap = new HashMap<>(); + static HashMap successCallbackMap = new HashMap<>(); static HashMap failureCallbackMap = new HashMap<>(); private final IterableTaskStorage taskStorage; private final IterableTaskRunner taskRunner; @@ -98,7 +98,7 @@ class TaskScheduler implements IterableTaskRunner.TaskCompletedListener { taskRunner.addTaskCompletedListener(this); } - void scheduleTask(IterableApiRequest request, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void scheduleTask(IterableApiRequest request, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { JSONObject serializedRequest = null; try { serializedRequest = request.toJSONObject(); @@ -120,13 +120,14 @@ void scheduleTask(IterableApiRequest request, @Nullable IterableHelper.SuccessHa @MainThread @Override public void onTaskCompleted(String taskId, IterableTaskRunner.TaskResult result, IterableApiResponse response) { - IterableHelper.SuccessHandler onSuccess = successCallbackMap.get(taskId); + IterableHelper.IterableSuccessCallback onSuccess = successCallbackMap.get(taskId); IterableHelper.FailureHandler onFailure = failureCallbackMap.get(taskId); successCallbackMap.remove(taskId); failureCallbackMap.remove(taskId); if (response.success) { if (onSuccess != null) { - onSuccess.onSuccess(response.responseJson); + IterableResponseObject.RemoteSuccess successResponse = new IterableResponseObject.RemoteSuccess(response.responseJson); + onSuccess.onSuccess(successResponse); } } else { if (onFailure != null) { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java index 013f7f0ad..a806b7b84 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java @@ -22,13 +22,13 @@ public void processGetRequest(@Nullable String apiKey, @NonNull String resourceP } @Override - public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, addCreatedAtToJson(json), IterableApiRequest.GET, authToken, onSuccess, onFailure); new IterableRequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, request); } @Override - public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, addCreatedAtToJson(json), IterableApiRequest.POST, authToken, onSuccess, onFailure); new IterableRequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, request); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java index 4de818fd6..497f94112 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java @@ -10,8 +10,8 @@ public interface RequestProcessor { void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableActionHandler onCallback); - void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure); + void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); - void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.SuccessHandler onSuccess, @Nullable IterableHelper.FailureHandler onFailure); + void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); void onLogout(Context context); } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java index 2fa168b59..379f2c3b4 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java @@ -201,9 +201,9 @@ public void testStoreAuthData_CompletionHandler_ReceivesStoredCredentials() thro final CountDownLatch latch = new CountDownLatch(1); // Capture what the completion handler receives - spyApi.setEmail(originalEmail, new IterableHelper.SuccessHandler() { + spyApi.setEmail(originalEmail, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(JSONObject data) { + public void onSuccess(IterableResponseObject.Success data) { // This callback happens after completeUserLogin latch.countDown(); } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java index bb781b9ad..1c443618d 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java @@ -59,9 +59,9 @@ public void testRequestSerialization() throws Exception { String requestType = "api"; String authToken = "authToken123##"; - IterableHelper.SuccessHandler successHandler = new IterableHelper.SuccessHandler() { + IterableHelper.IterableSuccessCallback successHandler = new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { IterableLogger.v("RequestSerializationTest", "Passed"); } }; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java index 463cf2b7c..635883563 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java @@ -161,7 +161,7 @@ public void testUpdateEmailPersistence() throws Exception { IterableApi.getInstance().updateEmail(newEmail); shadowOf(getMainLooper()).idle(); - verify(mockApiClient).updateEmail(eq(newEmail), nullable(IterableHelper.SuccessHandler.class), nullable(IterableHelper.FailureHandler.class)); + verify(mockApiClient).updateEmail(eq(newEmail), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); server.takeRequest(1, TimeUnit.SECONDS); assertEquals("new@email.com", IterableApi.getInstance().getEmail()); @@ -175,9 +175,9 @@ public void testSetEmailWithCallback() { IterableApi.initialize(getContext(), "apiKey"); String email = "test@example.com"; - IterableApi.getInstance().setEmail(email, new IterableHelper.SuccessHandler() { + IterableApi.getInstance().setEmail(email, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { assertTrue(true); // callback should be called with success } }, new IterableHelper.FailureHandler() { @@ -193,9 +193,9 @@ public void testSetUserIdWithCallback() { IterableApi.initialize(getContext(), "apiKey"); String userId = "test_user_id"; - IterableApi.getInstance().setUserId(userId, new IterableHelper.SuccessHandler() { + IterableApi.getInstance().setUserId(userId, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { assertTrue(true); // callback should be called with success } }, new IterableHelper.FailureHandler() { @@ -1015,7 +1015,7 @@ public void testRegisterDeviceTokenSuccessCallback_CreatesWrappedHandler() throw IterableApi.getInstance().setVisitorUsageTracked(true); // Create a mock success handler - IterableHelper.SuccessHandler originalHandler = mock(IterableHelper.SuccessHandler.class); + IterableHelper.IterableSuccessCallback originalHandler = mock(IterableHelper.IterableSuccessCallback.class); // Set up user with success handler IterableApi.getInstance().setEmail("test@example.com", originalHandler, null); @@ -1032,7 +1032,7 @@ public void testRegisterDeviceTokenSuccessCallback_CreatesWrappedHandler() throw shadowOf(getMainLooper()).idle(); // Verify: registerDeviceToken was called with a success handler - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.SuccessHandler.class); + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.IterableSuccessCallback.class); verify(mockClient, timeout(1000)).registerDeviceToken( eq("test@example.com"), nullable(String.class), @@ -1075,7 +1075,7 @@ public void testRegisterDeviceTokenSuccessCallback_WithoutOriginalHandler() thro shadowOf(getMainLooper()).idle(); // Verify: registerDeviceToken was called with a success handler (the wrapper) - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.SuccessHandler.class); + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.IterableSuccessCallback.class); verify(mockClient, timeout(1000)).registerDeviceToken( nullable(String.class), eq("test_user_123"), @@ -1112,7 +1112,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingTriggered() throws IterableApi.getInstance().setVisitorUsageTracked(true); // Create a success handler and set user - IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); IterableApi.getInstance().setEmail("test@example.com", successHandler, null); // Execute: Register device token @@ -1144,7 +1144,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingTriggered() throws assertTrue("Should have made consent request", foundConsentRequest); // Verify: Original success handler was called at least once - verify(successHandler, atLeastOnce()).onSuccess(any(JSONObject.class)); + verify(successHandler, atLeastOnce()).onSuccess(any(IterableResponseObject.Success.class)); } @Test @@ -1162,7 +1162,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingNotTriggeredWhenDis // Set up other conditions IterableApi.getInstance().setVisitorUsageTracked(true); - IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); IterableApi.getInstance().setEmail("test@example.com", successHandler, null); // Execute: Register device token @@ -1194,7 +1194,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingNotTriggeredWhenDis assertFalse("Should NOT have made consent request", foundConsentRequest); // Verify: Original success handler was called at least once - verify(successHandler, atLeastOnce()).onSuccess(any(JSONObject.class)); + verify(successHandler, atLeastOnce()).onSuccess(any(IterableResponseObject.Success.class)); } //endregion diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java index bd387d64c..f7b9b3546 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java @@ -1317,9 +1317,9 @@ public void onSDKInitialized() { // Call multiple overloaded method chains during initialization // Each overload internally delegates to the full signature IterableApi.getInstance().setEmail("user1@test.com"); - IterableApi.getInstance().setEmail("user2@test.com", (IterableHelper.SuccessHandler) null, null); + IterableApi.getInstance().setEmail("user2@test.com", (IterableHelper.IterableSuccessCallback) null, null); IterableApi.getInstance().setUserId("user123"); - IterableApi.getInstance().setUserId("user456", (IterableHelper.SuccessHandler) null, null); + IterableApi.getInstance().setUserId("user456", (IterableHelper.IterableSuccessCallback) null, null); IterableApi.getInstance().updateEmail("newemail@test.com"); // Verify operations are queued diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java new file mode 100644 index 000000000..7de8a7738 --- /dev/null +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java @@ -0,0 +1,343 @@ +package com.iterable.iterableapi; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests the functionality of IterableHelper callback interfaces + */ +public class IterableHelperUnitTest { + @Test + public void actionHandlerCallback() throws Exception { + final String resultString = "testString"; + + IterableHelper.IterableActionHandler clickCallback = new IterableHelper.IterableActionHandler() { + @Override + public void execute(String result) { + assertEquals(result, resultString); + } + }; + clickCallback.execute(resultString); + } + + // ========== IterableSuccessCallback Tests ========== + + @Test + public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { + JSONObject testJson = new JSONObject().put("key", "value"); + IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + + IterableHelper.IterableSuccessCallback callback = data -> { + callbackInvoked.set(true); + assertTrue(data instanceof IterableResponseObject.RemoteSuccess); + try { + assertEquals("value", ((IterableResponseObject.RemoteSuccess) data).getResponseJson().getString("key")); + } catch (JSONException e) { + throw new RuntimeException(e); + } + }; + + callback.onSuccess(remoteSuccess); + assertTrue("Callback should be invoked", callbackInvoked.get()); + } + + @Test + public void testIterableSuccessCallback_WithLocalSuccess() { + IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + + IterableHelper.IterableSuccessCallback callback = data -> { + callbackInvoked.set(true); + assertTrue(data instanceof IterableResponseObject.LocalSuccess); + assertNotNull(data.getMessage()); + }; + + callback.onSuccess(localSuccess); + assertTrue("Callback should be invoked", callbackInvoked.get()); + } + + @Test + public void testIterableSuccessCallback_WithAuthTokenSuccess() { + String testToken = "test-jwt-token-123"; + IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + + IterableHelper.IterableSuccessCallback callback = data -> { + callbackInvoked.set(true); + assertTrue(data instanceof IterableResponseObject.AuthTokenSuccess); + assertEquals(testToken, ((IterableResponseObject.AuthTokenSuccess) data).getAuthToken()); + }; + + callback.onSuccess(authSuccess); + assertTrue("Callback should be invoked", callbackInvoked.get()); + } + + // ========== RemoteSuccessCallback Tests ========== + + @Test + public void testRemoteSuccessCallback_WithCorrectType() throws Exception { + JSONObject testJson = new JSONObject().put("status", "success"); + IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { + @Override + public void onSuccess(IterableResponseObject.RemoteSuccess data) { + typedCallbackInvoked.set(true); + assertEquals(testJson, data.getResponseJson()); + } + }; + + callback.onSuccess((IterableResponseObject.Success) remoteSuccess); + assertTrue("Typed callback should be invoked for RemoteSuccess", typedCallbackInvoked.get()); + } + + @Test + public void testRemoteSuccessCallback_WithLocalSuccess_LogsWarning() { + IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { + @Override + public void onSuccess(IterableResponseObject.RemoteSuccess data) { + typedCallbackInvoked.set(true); + } + }; + + // Should not invoke typed callback, should log warning instead + callback.onSuccess(localSuccess); + assertFalse("Typed callback should NOT be invoked for LocalSuccess", typedCallbackInvoked.get()); + } + + // ========== LocalSuccessCallback Tests ========== + + @Test + public void testLocalSuccessCallback_WithCorrectType() { + IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { + @Override + public void onSuccess(IterableResponseObject.LocalSuccess data) { + typedCallbackInvoked.set(true); + assertNotNull(data.getMessage()); + } + }; + + callback.onSuccess(localSuccess); + assertTrue("Typed callback should be invoked for LocalSuccess", typedCallbackInvoked.get()); + } + + @Test + public void testLocalSuccessCallback_WithRemoteSuccess_LogsWarning() throws Exception { + JSONObject testJson = new JSONObject().put("key", "value"); + IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { + @Override + public void onSuccess(IterableResponseObject.LocalSuccess data) { + typedCallbackInvoked.set(true); + } + }; + + // Should not invoke typed callback, should log warning instead + callback.onSuccess((IterableResponseObject.Success) remoteSuccess); + assertFalse("Typed callback should NOT be invoked for RemoteSuccess", typedCallbackInvoked.get()); + } + + // ========== AuthTokenCallback Tests ========== + + @Test + public void testAuthTokenCallback_WithCorrectType() { + String testToken = "jwt-token-xyz"; + IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { + @Override + public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { + typedCallbackInvoked.set(true); + assertEquals(testToken, data.getAuthToken()); + } + }; + + callback.onSuccess((IterableResponseObject.Success) authSuccess); + assertTrue("Typed callback should be invoked for AuthTokenSuccess", typedCallbackInvoked.get()); + } + + @Test + public void testAuthTokenCallback_WithWrongType_LogsWarning() { + IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); + + IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { + @Override + public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { + typedCallbackInvoked.set(true); + } + }; + + // Should not invoke typed callback, should log warning instead + callback.onSuccess(localSuccess); + assertFalse("Typed callback should NOT be invoked for LocalSuccess", typedCallbackInvoked.get()); + } + + // ========== SuccessHandler (Deprecated) Backward Compatibility Tests ========== + + @Test + public void testSuccessHandler_WithRemoteSuccess_PassesCorrectJSON() throws Exception { + JSONObject testJson = new JSONObject() + .put("key1", "value1") + .put("key2", 123); + IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + AtomicReference receivedJson = new AtomicReference<>(); + + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { + @Override + public void onSuccess(JSONObject data) { + callbackInvoked.set(true); + receivedJson.set(data); + } + }; + + handler.onSuccess((IterableResponseObject.Success) remoteSuccess); + + assertTrue("Callback should be invoked", callbackInvoked.get()); + assertNotNull("JSON should not be null", receivedJson.get()); + assertEquals("value1", receivedJson.get().getString("key1")); + assertEquals(123, receivedJson.get().getInt("key2")); + } + + @Test + public void testSuccessHandler_WithLocalSuccess_PassesMessageJSON() throws Exception { + IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + AtomicReference receivedJson = new AtomicReference<>(); + + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { + @Override + public void onSuccess(JSONObject data) { + callbackInvoked.set(true); + receivedJson.set(data); + } + }; + + handler.onSuccess(localSuccess); + + assertTrue("Callback should be invoked", callbackInvoked.get()); + assertNotNull("JSON should not be null", receivedJson.get()); + assertTrue("JSON should contain message", receivedJson.get().has("message")); + assertNotNull(receivedJson.get().getString("message")); + } + + @Test + public void testSuccessHandler_WithAuthTokenSuccess_PassesTokenJSON() throws Exception { + String testToken = "test-auth-token"; + IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + AtomicReference receivedJson = new AtomicReference<>(); + + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { + @Override + public void onSuccess(JSONObject data) { + callbackInvoked.set(true); + receivedJson.set(data); + } + }; + + handler.onSuccess((IterableResponseObject.Success) authSuccess); + + assertTrue("Callback should be invoked", callbackInvoked.get()); + assertNotNull("JSON should not be null", receivedJson.get()); + assertTrue("JSON should contain newAuthToken", receivedJson.get().has("newAuthToken")); + assertEquals(testToken, receivedJson.get().getString("newAuthToken")); + } + + @Test + public void testSuccessHandler_DoesNotMutateOriginalJSON() throws Exception { + JSONObject originalJson = new JSONObject().put("original", "value"); + IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(originalJson); + + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { + @Override + public void onSuccess(JSONObject data) { + // Received JSON is a copy, mutations here shouldn't affect original + try { + data.put("modified", "newValue"); + } catch (JSONException e) { + // Ignore + } + } + }; + + handler.onSuccess((IterableResponseObject.Success) remoteSuccess); + + // Original JSON should not have the "modified" field + assertFalse("Original JSON should not be mutated", originalJson.has("modified")); + assertTrue("Original JSON should still have original field", originalJson.has("original")); + } + + // ========== FailureHandler Tests ========== + + @Test + public void testFailureHandler_ReceivesReasonAndData() throws Exception { + String expectedReason = "Network error"; + JSONObject expectedData = new JSONObject().put("errorCode", 500); + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + + IterableHelper.FailureHandler handler = (reason, data) -> { + callbackInvoked.set(true); + assertEquals(expectedReason, reason); + assertNotNull(data); + try { + assertEquals(500, data.getInt("errorCode")); + } catch (JSONException e) { + throw new RuntimeException(e); + } + }; + + handler.onFailure(expectedReason, expectedData); + assertTrue("Failure handler should be invoked", callbackInvoked.get()); + } + + @Test + public void testFailureHandler_HandlesNullData() { + String expectedReason = "Unknown error"; + + AtomicBoolean callbackInvoked = new AtomicBoolean(false); + + IterableHelper.FailureHandler handler = (reason, data) -> { + callbackInvoked.set(true); + assertEquals(expectedReason, reason); + // Data can be null, should not throw + }; + + handler.onFailure(expectedReason, null); + assertTrue("Failure handler should be invoked with null data", callbackInvoked.get()); + } +} \ No newline at end of file diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java index ceaf7c586..68f3472f2 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; + public class IterableInAppHTMLNotificationTest extends BaseTest { private ActivityController controller; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java index 3dd8a191c..a0b248b4d 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; + public class IterableInAppManagerSyncTest extends BaseTest { @Mock diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java index e18614061..f030e85c3 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java @@ -8,7 +8,6 @@ import androidx.fragment.app.FragmentActivity; import java.util.List; - import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.json.JSONArray; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java index 868cb12b6..25a5fc3be 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java @@ -98,9 +98,9 @@ public void testRemoveMessageSuccessCallbackOnSuccessfulResponse() throws Except final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); dispatcher.enqueueResponse("/events/inAppConsume", new MockResponse().setResponseCode(200).setBody(responseData.toString())); - inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.SuccessHandler() { + inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { signal.countDown(); } }, new IterableHelper.FailureHandler() { @@ -127,9 +127,9 @@ public void testRemoveMessageFailureCallbackOnFailedResponse() throws Exception final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); dispatcher.enqueueResponse("/events/inAppConsume", new MockResponse().setResponseCode(500).setBody(responseData.toString())); - inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.SuccessHandler() { + inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { assertFalse(true); } }, new IterableHelper.FailureHandler() { @@ -163,9 +163,9 @@ public void testSetRead() throws Exception { // Set first message as read with a callback final boolean[] callbackCalled = { false }; - inAppManager.setRead(inboxMessages.get(0), true, new IterableHelper.SuccessHandler() { + inAppManager.setRead(inboxMessages.get(0), true, new IterableHelper.IterableSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { + public void onSuccess(@NonNull IterableResponseObject.Success data) { callbackCalled[0] = true; assertTrue(callbackCalled[0]); } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java index 214b667ae..c3104a19e 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java @@ -75,7 +75,7 @@ public void testEnableDevice() throws Exception { verify(apiMock, timeout(100)).registerDeviceToken(eq(IterableTestUtils.userEmail), nullable(String.class), isNull(), eq(INTEGRATION_NAME), eq(TEST_TOKEN), eq(deviceAttributes)); - verify(apiMock, never()).disableToken(eq(IterableTestUtils.userEmail), nullable(String.class), nullable(String.class), any(String.class), nullable(IterableHelper.SuccessHandler.class), nullable(IterableHelper.FailureHandler.class)); + verify(apiMock, never()).disableToken(eq(IterableTestUtils.userEmail), nullable(String.class), nullable(String.class), any(String.class), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); } @Test @@ -87,6 +87,6 @@ public void testDisableDevice() throws Exception { new IterablePushRegistrationTask().execute(data); shadowOf(getMainLooper()).idle(); - verify(apiMock, timeout(100)).disableToken(eq(IterableTestUtils.userEmail), isNull(), isNull(), eq(TEST_TOKEN), nullable(IterableHelper.SuccessHandler.class), nullable(IterableHelper.FailureHandler.class)); + verify(apiMock, timeout(100)).disableToken(eq(IterableTestUtils.userEmail), isNull(), isNull(), eq(TEST_TOKEN), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); } } \ No newline at end of file diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java index c79956909..cb003a8b6 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java @@ -35,12 +35,12 @@ public void testScheduleTaskCreatesTaskInStorage() throws Exception { @Test public void testSuccessCallbackIsCalledOnCompletion() throws Exception { - IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); IterableApiRequest request = new IterableApiRequest("apiKey", "api/test", new JSONObject(), "POST", null, null, null); when(mockTaskStorage.createTask(any(String.class), any(IterableTaskType.class), any(String.class))).thenReturn("testTaskId"); taskScheduler.scheduleTask(request, successHandler, null); taskScheduler.onTaskCompleted("testTaskId", IterableTaskRunner.TaskResult.SUCCESS, IterableApiResponse.success(200, "", new JSONObject())); - verify(successHandler).onSuccess(any(JSONObject.class)); + verify(successHandler).onSuccess(any(IterableResponseObject.Success.class)); } @Test diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/unit/IterableHelperUnitTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/unit/IterableHelperUnitTest.java deleted file mode 100644 index 14ff7eabb..000000000 --- a/iterableapi/src/test/java/com/iterable/iterableapi/unit/IterableHelperUnitTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.iterable.iterableapi.unit; - -import com.iterable.iterableapi.IterableHelper; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * IterableConstants tests the functionality in IterableHelper - */ -public class IterableHelperUnitTest { - - @Test - public void actionHandlerCallback() throws Exception { - final String resultString = "testString"; - - IterableHelper.IterableActionHandler clickCallback = new IterableHelper.IterableActionHandler() { - @Override - public void execute(String result) { - assertEquals(result, resultString); - } - }; - clickCallback.execute(resultString); - } -} \ No newline at end of file From e7807fb78e6416515aa237d5e480c001a6e7f89f Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Fri, 23 Jan 2026 14:21:21 +0000 Subject: [PATCH 3/5] Fixed checkstyle fails and indentation fixes --- .../tests/utils/IntegrationTestUtils.kt | 32 +++---- .../iterableapi/IterableApiResponseTest.java | 12 +-- .../com/iterable/iterableapi/IterableApi.java | 37 ++++--- .../iterableapi/IterableAuthManager.java | 14 +-- .../iterableapi/IterableEmbeddedManager.kt | 7 +- .../iterable/iterableapi/IterableHelper.java | 4 +- .../iterableapi/IterableRequestTask.java | 3 - .../IterableApiAuthSecurityTests.java | 1 - .../iterableapi/IterableHelperUnitTest.java | 96 +++++++++---------- 9 files changed, 97 insertions(+), 109 deletions(-) diff --git a/integration-tests/src/main/java/com/iterable/integration/tests/utils/IntegrationTestUtils.kt b/integration-tests/src/main/java/com/iterable/integration/tests/utils/IntegrationTestUtils.kt index 6d3d21dc7..6bf77a431 100644 --- a/integration-tests/src/main/java/com/iterable/integration/tests/utils/IntegrationTestUtils.kt +++ b/integration-tests/src/main/java/com/iterable/integration/tests/utils/IntegrationTestUtils.kt @@ -116,14 +116,14 @@ class IntegrationTestUtils(private val context: Context) { val response = httpClient.newCall(request).execute() val success = response.isSuccessful - if (success) { - Log.d(TAG, "Campaign triggered successfully via API: campaignId=$campaignId, recipientEmail=$recipientEmail") - } else { - val errorBody = response.body?.string() ?: "No error body" - Log.e(TAG, "Failed to trigger campaign via API: ${response.code} - $errorBody") - // Store error message for UI display - lastErrorMessage = "HTTP ${response.code}: $errorBody" - } + if (success) { + Log.d(TAG, "Campaign triggered successfully via API: campaignId=$campaignId, recipientEmail=$recipientEmail") + } else { + val errorBody = response.body?.string() ?: "No error body" + Log.e(TAG, "Failed to trigger campaign via API: ${response.code} - $errorBody") + // Store error message for UI display + lastErrorMessage = "HTTP ${response.code}: $errorBody" + } callback?.invoke(success) } catch (e: Exception) { @@ -149,14 +149,14 @@ class IntegrationTestUtils(private val context: Context) { val response = httpClient.newCall(request).execute() val success = response.isSuccessful - if (success) { - Log.d(TAG, "Push campaign triggered successfully via API: campaignId=$campaignId, recipientEmail=$recipientEmail") - } else { - val errorBody = response.body?.string() ?: "No error body" - Log.e(TAG, "Failed to trigger push campaign via API: ${response.code} - $errorBody") - // Store error message for UI display - lastErrorMessage = "HTTP ${response.code}: $errorBody" - } + if (success) { + Log.d(TAG, "Push campaign triggered successfully via API: campaignId=$campaignId, recipientEmail=$recipientEmail") + } else { + val errorBody = response.body?.string() ?: "No error body" + Log.e(TAG, "Failed to trigger push campaign via API: ${response.code} - $errorBody") + // Store error message for UI display + lastErrorMessage = "HTTP ${response.code}: $errorBody" + } //TODO: Move callback success inside if(success) callback?.invoke(success) } catch (e: Exception) { diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java index bcda39d86..2b5b4856a 100644 --- a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java +++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java @@ -77,10 +77,10 @@ public void testResponseCode200() throws Exception { final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); stubAnyRequestReturningStatusCode(200, responseData); - IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.IterableSuccessCallback() { + IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.RemoteSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject data) { - assertEquals(responseData.toString(), data.toString()); + public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data) { + assertEquals(responseData.toString(), data.getResponseJson().toString()); signal.countDown(); } }, null); @@ -222,11 +222,11 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) { "}"); stubAnyRequestReturningStatusCode(200, responseData); - new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.IterableSuccessCallback() { + new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.RemoteSuccessCallback() { @Override - public void onSuccess(@NonNull JSONObject successData) { + public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess successData) { try { - assertEquals(responseData.toString(), successData.toString()); + assertEquals(responseData.toString(), successData.getResponseJson().toString()); } catch (AssertionError e) { e.printStackTrace(); } finally { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 248be3f28..bfbcf3f09 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -405,7 +405,7 @@ private void onLogin( boolean isUnknown, @Nullable IterableHelper.FailureHandler failureHandler ) { - if (!isInitialized()) { //todo: If we get here and it is not initialized, isn't it possible to have leftover data that was already set by setEmail? + if (!isInitialized()) { setAuthToken(null); return; } @@ -413,7 +413,7 @@ private void onLogin( getAuthManager().pauseAuthRetries(false); if (authToken != null) { setAuthToken(authToken); - attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); //todo: Why do we need to do this again if we did this on setEmail? + attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); } else { IterableHelper.AuthTokenCallback callback = data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); getAuthManager().requestNewAuthToken(false, callback); @@ -452,17 +452,15 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, if (config.autoPushRegistration) { registerForPush(); } else if (_setUserSuccessCallbackHandler != null) { - invokeSuccessHandlerAndClear(); + invokeSetUserSuccessHandler(); } getInAppManager().syncInApp(); getEmbeddedManager().syncMessages(); } - private void invokeSuccessHandlerAndClear() { + private void invokeSetUserSuccessHandler() { _setUserSuccessCallbackHandler.onSuccess(IterableResponseObject.LocalSuccessResponse); - _setUserSuccessCallbackHandler = null; - _setUserFailureCallbackHandler = null; } private final IterableActivityMonitor.AppStateCallback activityMonitorListener = new IterableActivityMonitor.AppStateCallback() { @@ -992,11 +990,7 @@ public IterableAttributionInfo getAttributionInfo() { public void pauseAuthRetries(boolean pauseRetry) { getAuthManager().pauseAuthRetries(pauseRetry); if (!pauseRetry) { // request new auth token as soon as unpause - IterableHelper.AuthTokenCallback callback = null; - getAuthManager().requestNewAuthToken( - false, - callback - ); + getAuthManager().requestNewAuthToken(false, null); } } @@ -1532,17 +1526,20 @@ public void updateEmail(final @NonNull String newEmail, final @Nullable String a return; } - apiClient.updateEmail(newEmail, data -> { - if (_email != null) { - _email = newEmail; - _authToken = authToken; - } + apiClient.updateEmail(newEmail, new IterableHelper.RemoteSuccessCallback() { + @Override + public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data) { + if (_email != null) { + _email = newEmail; + _authToken = authToken; + } - storeAuthData(); - getAuthManager().requestNewAuthToken(false, null); + storeAuthData(); + getAuthManager().requestNewAuthToken(false, null); - if (successHandler != null) { - successHandler.onSuccess(data); + if (successHandler != null) { + successHandler.onSuccess(data); + } } }, failureHandler); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java index 9a7a40e71..fc51ec6ae 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java @@ -6,10 +6,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import org.json.JSONException; import org.json.JSONObject; -import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Timer; import java.util.TimerTask; @@ -69,14 +67,10 @@ void resetRetryCount() { retryCount = 0; } - private void handleSuccessForAuthToken(String authToken, IterableHelper.IterableSuccessCallback successCallback) { - try { - if(authToken == null) throw new RuntimeException("Auth Token is null"); - IterableResponseObject.AuthTokenSuccess remoteSuccess = new IterableResponseObject.AuthTokenSuccess(authToken); - successCallback.onSuccess(remoteSuccess); - } catch (RuntimeException e) { - e.printStackTrace(); - } + private void handleSuccessForAuthToken(@NonNull String authToken, IterableHelper.IterableSuccessCallback successCallback) { + IterableResponseObject.AuthTokenSuccess remoteSuccess = new IterableResponseObject.AuthTokenSuccess(authToken); + successCallback.onSuccess(remoteSuccess); + } public synchronized void requestNewAuthToken( diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt index de66ea72e..acb4408a4 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt @@ -88,16 +88,17 @@ public class IterableEmbeddedManager : IterableActivityMonitor.AppStateCallback if (iterableApi.config.enableEmbeddedMessaging) { IterableLogger.v(TAG, "Syncing messages...") - IterableApi.sharedInstance.getEmbeddedMessages(placementIds, { data -> + + + IterableApi.sharedInstance.getEmbeddedMessages(placementIds, IterableHelper.RemoteSuccessCallback { data: IterableResponseObject.RemoteSuccess -> IterableLogger.v(TAG, "Got response from network call to get embedded messages") try { - val remoteData = data as (IterableResponseObject.RemoteSuccess) val previousPlacementIds = getPlacementIds() val currentPlacementIds: MutableList = mutableListOf() val placementsArray = - remoteData.responseJson.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) + data.responseJson.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) if (placementsArray != null) { //if there are no placements in the payload //reset the local message storage and trigger a UI update diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java index 678014ced..ab40c7c43 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -56,7 +56,7 @@ public interface SuccessHandler extends IterableSuccessCallback { default void onSuccess(@NonNull IterableResponseObject.Success data) { IterableLogger.w("IterableHelper", "SuccessHandler is deprecated. Please migrate to IterableSuccessCallback or RemoteSuccessCallback. " + "See JavaDoc for migration guide. Current success type: " + data.getClass().getSimpleName()); - + JSONObject jsonObject = new JSONObject(); try { if (data instanceof IterableResponseObject.RemoteSuccess) { @@ -72,7 +72,7 @@ default void onSuccess(@NonNull IterableResponseObject.Success data) { } catch (JSONException e) { IterableLogger.e("IterableHelper", "Error creating JSON for deprecated SuccessHandler callback", e); } - + onSuccess(jsonObject); } } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java index 07b999399..92815c122 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java @@ -7,14 +7,11 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import com.google.gson.JsonIOException; - import org.json.JSONException; import org.json.JSONObject; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java index 379f2c3b4..dd441f568 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java @@ -18,7 +18,6 @@ import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; -import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java index 7de8a7738..1407bc6f8 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java @@ -35,9 +35,9 @@ public void execute(String result) { public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { JSONObject testJson = new JSONObject().put("key", "value"); IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); - + IterableHelper.IterableSuccessCallback callback = data -> { callbackInvoked.set(true); assertTrue(data instanceof IterableResponseObject.RemoteSuccess); @@ -47,7 +47,7 @@ public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { throw new RuntimeException(e); } }; - + callback.onSuccess(remoteSuccess); assertTrue("Callback should be invoked", callbackInvoked.get()); } @@ -55,15 +55,15 @@ public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { @Test public void testIterableSuccessCallback_WithLocalSuccess() { IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); - + IterableHelper.IterableSuccessCallback callback = data -> { callbackInvoked.set(true); assertTrue(data instanceof IterableResponseObject.LocalSuccess); assertNotNull(data.getMessage()); }; - + callback.onSuccess(localSuccess); assertTrue("Callback should be invoked", callbackInvoked.get()); } @@ -72,15 +72,15 @@ public void testIterableSuccessCallback_WithLocalSuccess() { public void testIterableSuccessCallback_WithAuthTokenSuccess() { String testToken = "test-jwt-token-123"; IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); - + IterableHelper.IterableSuccessCallback callback = data -> { callbackInvoked.set(true); assertTrue(data instanceof IterableResponseObject.AuthTokenSuccess); assertEquals(testToken, ((IterableResponseObject.AuthTokenSuccess) data).getAuthToken()); }; - + callback.onSuccess(authSuccess); assertTrue("Callback should be invoked", callbackInvoked.get()); } @@ -91,9 +91,9 @@ public void testIterableSuccessCallback_WithAuthTokenSuccess() { public void testRemoteSuccessCallback_WithCorrectType() throws Exception { JSONObject testJson = new JSONObject().put("status", "success"); IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { @Override public void onSuccess(IterableResponseObject.RemoteSuccess data) { @@ -101,7 +101,7 @@ public void onSuccess(IterableResponseObject.RemoteSuccess data) { assertEquals(testJson, data.getResponseJson()); } }; - + callback.onSuccess((IterableResponseObject.Success) remoteSuccess); assertTrue("Typed callback should be invoked for RemoteSuccess", typedCallbackInvoked.get()); } @@ -109,16 +109,16 @@ public void onSuccess(IterableResponseObject.RemoteSuccess data) { @Test public void testRemoteSuccessCallback_WithLocalSuccess_LogsWarning() { IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { @Override public void onSuccess(IterableResponseObject.RemoteSuccess data) { typedCallbackInvoked.set(true); } }; - + // Should not invoke typed callback, should log warning instead callback.onSuccess(localSuccess); assertFalse("Typed callback should NOT be invoked for LocalSuccess", typedCallbackInvoked.get()); @@ -129,9 +129,9 @@ public void onSuccess(IterableResponseObject.RemoteSuccess data) { @Test public void testLocalSuccessCallback_WithCorrectType() { IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { @Override public void onSuccess(IterableResponseObject.LocalSuccess data) { @@ -139,7 +139,7 @@ public void onSuccess(IterableResponseObject.LocalSuccess data) { assertNotNull(data.getMessage()); } }; - + callback.onSuccess(localSuccess); assertTrue("Typed callback should be invoked for LocalSuccess", typedCallbackInvoked.get()); } @@ -148,16 +148,16 @@ public void onSuccess(IterableResponseObject.LocalSuccess data) { public void testLocalSuccessCallback_WithRemoteSuccess_LogsWarning() throws Exception { JSONObject testJson = new JSONObject().put("key", "value"); IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { @Override public void onSuccess(IterableResponseObject.LocalSuccess data) { typedCallbackInvoked.set(true); } }; - + // Should not invoke typed callback, should log warning instead callback.onSuccess((IterableResponseObject.Success) remoteSuccess); assertFalse("Typed callback should NOT be invoked for RemoteSuccess", typedCallbackInvoked.get()); @@ -169,9 +169,9 @@ public void onSuccess(IterableResponseObject.LocalSuccess data) { public void testAuthTokenCallback_WithCorrectType() { String testToken = "jwt-token-xyz"; IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { @Override public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { @@ -179,7 +179,7 @@ public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { assertEquals(testToken, data.getAuthToken()); } }; - + callback.onSuccess((IterableResponseObject.Success) authSuccess); assertTrue("Typed callback should be invoked for AuthTokenSuccess", typedCallbackInvoked.get()); } @@ -187,16 +187,16 @@ public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { @Test public void testAuthTokenCallback_WithWrongType_LogsWarning() { IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; - + AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - + IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { @Override public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { typedCallbackInvoked.set(true); } }; - + // Should not invoke typed callback, should log warning instead callback.onSuccess(localSuccess); assertFalse("Typed callback should NOT be invoked for LocalSuccess", typedCallbackInvoked.get()); @@ -210,10 +210,10 @@ public void testSuccessHandler_WithRemoteSuccess_PassesCorrectJSON() throws Exce .put("key1", "value1") .put("key2", 123); IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(testJson); - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); AtomicReference receivedJson = new AtomicReference<>(); - + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { @Override public void onSuccess(JSONObject data) { @@ -221,9 +221,9 @@ public void onSuccess(JSONObject data) { receivedJson.set(data); } }; - + handler.onSuccess((IterableResponseObject.Success) remoteSuccess); - + assertTrue("Callback should be invoked", callbackInvoked.get()); assertNotNull("JSON should not be null", receivedJson.get()); assertEquals("value1", receivedJson.get().getString("key1")); @@ -233,10 +233,10 @@ public void onSuccess(JSONObject data) { @Test public void testSuccessHandler_WithLocalSuccess_PassesMessageJSON() throws Exception { IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); AtomicReference receivedJson = new AtomicReference<>(); - + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { @Override public void onSuccess(JSONObject data) { @@ -244,9 +244,9 @@ public void onSuccess(JSONObject data) { receivedJson.set(data); } }; - + handler.onSuccess(localSuccess); - + assertTrue("Callback should be invoked", callbackInvoked.get()); assertNotNull("JSON should not be null", receivedJson.get()); assertTrue("JSON should contain message", receivedJson.get().has("message")); @@ -257,10 +257,10 @@ public void onSuccess(JSONObject data) { public void testSuccessHandler_WithAuthTokenSuccess_PassesTokenJSON() throws Exception { String testToken = "test-auth-token"; IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); AtomicReference receivedJson = new AtomicReference<>(); - + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { @Override public void onSuccess(JSONObject data) { @@ -268,9 +268,9 @@ public void onSuccess(JSONObject data) { receivedJson.set(data); } }; - + handler.onSuccess((IterableResponseObject.Success) authSuccess); - + assertTrue("Callback should be invoked", callbackInvoked.get()); assertNotNull("JSON should not be null", receivedJson.get()); assertTrue("JSON should contain newAuthToken", receivedJson.get().has("newAuthToken")); @@ -281,7 +281,7 @@ public void onSuccess(JSONObject data) { public void testSuccessHandler_DoesNotMutateOriginalJSON() throws Exception { JSONObject originalJson = new JSONObject().put("original", "value"); IterableResponseObject.RemoteSuccess remoteSuccess = new IterableResponseObject.RemoteSuccess(originalJson); - + IterableHelper.SuccessHandler handler = new IterableHelper.SuccessHandler() { @Override public void onSuccess(JSONObject data) { @@ -293,9 +293,9 @@ public void onSuccess(JSONObject data) { } } }; - + handler.onSuccess((IterableResponseObject.Success) remoteSuccess); - + // Original JSON should not have the "modified" field assertFalse("Original JSON should not be mutated", originalJson.has("modified")); assertTrue("Original JSON should still have original field", originalJson.has("original")); @@ -307,9 +307,9 @@ public void onSuccess(JSONObject data) { public void testFailureHandler_ReceivesReasonAndData() throws Exception { String expectedReason = "Network error"; JSONObject expectedData = new JSONObject().put("errorCode", 500); - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); - + IterableHelper.FailureHandler handler = (reason, data) -> { callbackInvoked.set(true); assertEquals(expectedReason, reason); @@ -320,7 +320,7 @@ public void testFailureHandler_ReceivesReasonAndData() throws Exception { throw new RuntimeException(e); } }; - + handler.onFailure(expectedReason, expectedData); assertTrue("Failure handler should be invoked", callbackInvoked.get()); } @@ -328,15 +328,15 @@ public void testFailureHandler_ReceivesReasonAndData() throws Exception { @Test public void testFailureHandler_HandlesNullData() { String expectedReason = "Unknown error"; - + AtomicBoolean callbackInvoked = new AtomicBoolean(false); - + IterableHelper.FailureHandler handler = (reason, data) -> { callbackInvoked.set(true); assertEquals(expectedReason, reason); // Data can be null, should not throw }; - + handler.onFailure(expectedReason, null); assertTrue("Failure handler should be invoked with null data", callbackInvoked.get()); } From 7b437758d09c42f827b0f20521183ce4c78c5e80 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Fri, 23 Jan 2026 17:52:13 +0000 Subject: [PATCH 4/5] Modularization of the callback handlers into packages --- .../iterableapi/IterableApiResponseTest.java | 7 +- .../com/iterable/iterableapi/IterableApi.java | 49 +++--- .../iterableapi/IterableApiClient.java | 21 +-- .../iterableapi/IterableAuthManager.java | 16 +- .../iterableapi/IterableEmbeddedManager.kt | 98 ++++++----- .../iterable/iterableapi/IterableHelper.java | 164 ++---------------- .../iterableapi/IterableInAppManager.java | 6 +- .../iterableapi/IterableRequestTask.java | 14 +- .../iterableapi/OfflineRequestProcessor.java | 13 +- .../iterableapi/OnlineRequestProcessor.java | 6 +- .../iterableapi/RequestProcessor.java | 6 +- .../response/IterableAuthResponseObject.kt | 15 ++ .../{ => response}/IterableResponseObject.kt | 22 +-- .../handlers/IterableCallbackHandlers.java | 124 +++++++++++++ .../auth/IterableAuthCallbackHandlers.java | 52 ++++++ .../IterableApiAuthSecurityTests.java | 4 +- .../iterableapi/IterableApiRequestTest.java | 4 +- .../iterable/iterableapi/IterableApiTest.java | 18 +- .../IterableAsyncInitializationTest.java | 6 +- .../iterableapi/IterableHelperUnitTest.java | 37 ++-- .../iterableapi/IterableInAppManagerTest.java | 1 + .../iterableapi/IterableInboxTest.java | 8 +- .../IterablePushRegistrationTaskTest.java | 6 +- .../iterableapi/TaskSchedulerTest.java | 4 +- 24 files changed, 402 insertions(+), 299 deletions(-) create mode 100644 iterableapi/src/main/java/com/iterable/iterableapi/response/IterableAuthResponseObject.kt rename iterableapi/src/main/java/com/iterable/iterableapi/{ => response}/IterableResponseObject.kt (71%) create mode 100644 iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/IterableCallbackHandlers.java create mode 100644 iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/auth/IterableAuthCallbackHandlers.java diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java index 2b5b4856a..83479c723 100644 --- a/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java +++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/IterableApiResponseTest.java @@ -30,6 +30,9 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertThat; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + @RunWith(AndroidJUnit4.class) @MediumTest public class IterableApiResponseTest { @@ -77,7 +80,7 @@ public void testResponseCode200() throws Exception { final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); stubAnyRequestReturningStatusCode(200, responseData); - IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.RemoteSuccessCallback() { + IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableCallbackHandlers.RemoteSuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data) { assertEquals(responseData.toString(), data.getResponseJson().toString()); @@ -222,7 +225,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) { "}"); stubAnyRequestReturningStatusCode(200, responseData); - new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.RemoteSuccessCallback() { + new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableCallbackHandlers.RemoteSuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess successData) { try { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index bfbcf3f09..c0386d98b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -12,8 +12,13 @@ import androidx.annotation.VisibleForTesting; import androidx.core.app.NotificationManagerCompat; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; +import com.iterable.iterableapi.response.handlers.auth.IterableAuthCallbackHandlers; import com.iterable.iterableapi.util.DeviceInfoUtils; +import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; @@ -43,7 +48,7 @@ public class IterableApi { private IterableNotificationData _notificationData; private String _deviceId; private boolean _firstForegroundHandled; - private IterableHelper.IterableSuccessCallback _setUserSuccessCallbackHandler; + private IterableCallbackHandlers.SuccessCallback _setUserSuccessCallbackHandler; private IterableHelper.FailureHandler _setUserFailureCallbackHandler; IterableApiClient apiClient = new IterableApiClient(new IterableApiAuthProvider()); @@ -310,7 +315,7 @@ public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull Iterable * @param onFailure */ - public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableCallbackHandlers.SuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { if (!checkSDKInitialization()) { return; } @@ -328,7 +333,7 @@ public void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull Iterable * @param onSuccess * @param onFailure */ - void getEmbeddedMessages(@NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void getEmbeddedMessages(@NonNull IterableCallbackHandlers.SuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { if (!checkSDKInitialization()) { return; } @@ -415,7 +420,7 @@ private void onLogin( setAuthToken(authToken); attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); } else { - IterableHelper.AuthTokenCallback callback = data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); + IterableAuthCallbackHandlers.AuthTokenCallback callback = data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); getAuthManager().requestNewAuthToken(false, callback); } } @@ -705,7 +710,7 @@ protected void disableToken(@Nullable String email, @Nullable String userId, @No * @param authToken * @param deviceToken The device token */ - protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { if (deviceToken == null) { IterableLogger.d(TAG, "device token not available"); return; @@ -734,16 +739,16 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user IterableLogger.e(TAG, "registerDeviceToken: applicationName is null, check that pushIntegrationName is set in IterableConfig"); } - IterableHelper.IterableSuccessCallback wrappedSuccessHandler = wrapSetUserCallbackForRemoteCall(); + IterableCallbackHandlers.SuccessCallback wrappedSuccessHandler = wrapSetUserCallbackForRemoteCall(); IterableHelper.FailureHandler wrappedFailureHandler = wrapSetUserFailureHandlerForRemoteCall(); apiClient.registerDeviceToken(email, userId, authToken, applicationName, deviceToken, dataFields, deviceAttributes, wrappedSuccessHandler, wrappedFailureHandler); } - private IterableHelper.IterableSuccessCallback wrapSetUserCallbackForRemoteCall() { - IterableHelper.RemoteSuccessCallback wrappedSuccessHandler = null; + private IterableCallbackHandlers.SuccessCallback wrapSetUserCallbackForRemoteCall() { + IterableCallbackHandlers.SuccessCallback wrappedSuccessHandler = null; if (_setUserSuccessCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { - final IterableHelper.IterableSuccessCallback originalSuccessHandler = _setUserSuccessCallbackHandler; + final IterableCallbackHandlers.SuccessCallback originalSuccessHandler = _setUserSuccessCallbackHandler; wrappedSuccessHandler = data -> { trackConsentOnDeviceRegistration(); @@ -1002,11 +1007,11 @@ public void setEmail(@Nullable String email, IterableIdentityResolution identity queueOrExecute(() -> setEmail(email, null, identityResolution, null, null), "setEmail(" + maskPII(email) + ", identityResolution)"); } - public void setEmail(@Nullable String email, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, null, null, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", callbacks)"); } - public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, null, identityResolution, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", identityResolution, callbacks)"); } @@ -1018,11 +1023,11 @@ public void setEmail(@Nullable String email, @Nullable String authToken, Iterabl queueOrExecute(() -> setEmail(email, authToken, identityResolution, null, null), "setEmail(" + maskPII(email) + ", " + maskPII(authToken) + ", identityResolution)"); } - public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setEmail(email, authToken, null, successHandler, failureHandler), "setEmail(" + maskPII(email) + ", " + maskPII(authToken) + ", callbacks)"); } - public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { boolean replay = isReplay(iterableIdentityResolution); boolean merge = isMerge(iterableIdentityResolution); @@ -1071,11 +1076,11 @@ public void setUserId(@Nullable String userId, IterableIdentityResolution identi queueOrExecute(() -> setUserId(userId, null, identityResolution, null, null, false), "setUserId(" + maskPII(userId) + ", identityResolution)"); } - public void setUserId(@Nullable String userId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, null, null, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", callbacks)"); } - public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, null, identityResolution, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", identityResolution, callbacks)"); } @@ -1088,11 +1093,11 @@ public void setUserId(@Nullable String userId, @Nullable String authToken, Itera } - public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> setUserId(userId, authToken, null, successHandler, failureHandler, false), "setUserId(" + maskPII(userId) + ", " + maskPII(authToken) + ", callbacks)"); } - public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler, boolean isUnknown) { + public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler, boolean isUnknown) { boolean replay = isReplay(iterableIdentityResolution); boolean merge = isMerge(iterableIdentityResolution); @@ -1257,7 +1262,7 @@ public void inAppConsume(@NonNull String messageId) { * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public void inAppConsume(@NonNull String messageId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull String messageId, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { IterableInAppMessage message = getInAppManager().getMessageById(messageId); if (checkIfMessageIsNull(message, failureHandler)) { return; @@ -1296,7 +1301,7 @@ public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable Iterab * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { if (!checkSDKInitialization()) { return; } @@ -1503,7 +1508,7 @@ public void updateEmail(final @NonNull String newEmail, final @NonNull String au queueOrExecute(() -> updateEmail(newEmail, authToken, null, null), "updateEmail(" + maskPII(newEmail) + ", " + maskPII(authToken) + ")"); } - public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { queueOrExecute(() -> updateEmail(newEmail, null, successHandler, failureHandler), "updateEmail(" + maskPII(newEmail) + ", callbacks)"); } @@ -1514,7 +1519,7 @@ public void updateEmail(final @NonNull String newEmail, final @Nullable Iterable * @param successHandler Success handler. Called when the server returns a success code. * @param failureHandler Failure handler. Called when the server call failed. */ - public void updateEmail(final @NonNull String newEmail, final @Nullable String authToken, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable String authToken, final @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { if (!checkSDKInitialization()) { IterableLogger.e(TAG, "The Iterable SDK must be initialized with email or userId before " + "calling updateEmail"); @@ -1526,7 +1531,7 @@ public void updateEmail(final @NonNull String newEmail, final @Nullable String a return; } - apiClient.updateEmail(newEmail, new IterableHelper.RemoteSuccessCallback() { + apiClient.updateEmail(newEmail, new IterableCallbackHandlers.RemoteSuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data) { if (_email != null) { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java index af2ca3373..f90a04779 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java @@ -7,6 +7,7 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.util.DeviceInfoUtils; import org.json.JSONArray; @@ -222,7 +223,7 @@ public void trackPurchase(double total, @NonNull List items, @Null } } - public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public void updateEmail(final @NonNull String newEmail, final @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { JSONObject requestJSON = new JSONObject(); try { @@ -319,7 +320,7 @@ void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper. } } - void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void getEmbeddedMessages(@Nullable Long[] placementIds, @NonNull IterableCallbackHandlers.SuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { JSONObject requestJSON = new JSONObject(); try { @@ -488,7 +489,7 @@ void trackEmbeddedMessageReceived(@NonNull IterableEmbeddedMessage message) { } - public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable String inboxSessionId, @Nullable final IterableHelper.IterableSuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { + public void inAppConsume(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable String inboxSessionId, @Nullable final IterableCallbackHandlers.SuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { JSONObject requestJSON = new JSONObject(); try { @@ -604,7 +605,7 @@ protected void trackPushOpen(int campaignId, int templateId, @NonNull String mes } } - protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + protected void disableToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String deviceToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { JSONObject requestJSON = new JSONObject(); try { requestJSON.put(IterableConstants.KEY_TOKEN, deviceToken); @@ -620,7 +621,7 @@ protected void disableToken(@Nullable String email, @Nullable String userId, @Nu } } - protected void registerDeviceToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String applicationName, @NonNull String deviceToken, @Nullable JSONObject dataFields, HashMap deviceAttributes, @Nullable final IterableHelper.IterableSuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { + protected void registerDeviceToken(@Nullable String email, @Nullable String userId, @Nullable String authToken, @NonNull String applicationName, @NonNull String deviceToken, @Nullable JSONObject dataFields, HashMap deviceAttributes, @Nullable final IterableCallbackHandlers.SuccessCallback successHandler, @Nullable final IterableHelper.FailureHandler failureHandler) { Context context = authProvider.getContext(); JSONObject requestJSON = new JSONObject(); try { @@ -756,11 +757,11 @@ void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nu sendPostRequest(resourcePath, json, authToken, null, null); } - void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { sendPostRequest(resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure); } - void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nullable String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { getRequestProcessor().processPostRequest(authProvider.getApiKey(), resourcePath, json, authToken, onSuccess, onFailure); } @@ -774,7 +775,7 @@ void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nul getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onCallback); } - void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @NonNull IterableCallbackHandlers.SuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure); } @@ -783,7 +784,7 @@ void onLogout() { authProvider.resetAuth(); } - void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { JSONObject requestJson = new JSONObject(); try { if (sourceEmail != null && !sourceEmail.isEmpty()) { @@ -808,7 +809,7 @@ void getCriteriaList(@Nullable IterableHelper.IterableActionHandler actionHandle sendGetRequest(IterableConstants.ENDPOINT_CRITERIA_LIST, new JSONObject(), actionHandler); } - void trackUnknownUserSession(long createdAt, String userId, @NonNull JSONObject requestJson, JSONObject updateUserTrack, @NonNull IterableHelper.IterableSuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { + void trackUnknownUserSession(long createdAt, String userId, @NonNull JSONObject requestJson, JSONObject updateUserTrack, @NonNull IterableCallbackHandlers.SuccessCallback onSuccess, @NonNull IterableHelper.FailureHandler onFailure) { try { JSONObject requestObject = new JSONObject(); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java index fc51ec6ae..132685a3f 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java @@ -6,6 +6,10 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + import org.json.JSONObject; import java.io.UnsupportedEncodingException; @@ -45,7 +49,7 @@ public class IterableAuthManager implements IterableActivityMonitor.AppStateCall this.activityMonitor.addCallback(this); } - public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, IterableHelper.IterableSuccessCallback successCallback) { + public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, IterableCallbackHandlers.SuccessCallback successCallback) { requestNewAuthToken(hasFailedPriorAuth, successCallback, true); } @@ -67,15 +71,15 @@ void resetRetryCount() { retryCount = 0; } - private void handleSuccessForAuthToken(@NonNull String authToken, IterableHelper.IterableSuccessCallback successCallback) { - IterableResponseObject.AuthTokenSuccess remoteSuccess = new IterableResponseObject.AuthTokenSuccess(authToken); + private void handleSuccessForAuthToken(@NonNull String authToken, IterableCallbackHandlers.SuccessCallback successCallback) { + IterableAuthResponseObject.Success remoteSuccess = new IterableAuthResponseObject.Success(authToken); successCallback.onSuccess(remoteSuccess); } public synchronized void requestNewAuthToken( boolean hasFailedPriorAuth, - final IterableHelper.IterableSuccessCallback successCallback, + final IterableCallbackHandlers.SuccessCallback successCallback, boolean shouldIgnoreRetryPolicy) { if (!shouldIgnoreRetryPolicy && (pauseAuthRetry || (retryCount >= authRetryPolicy.maxRetry))) { return; @@ -126,7 +130,7 @@ public void run() { } } - private void handleAuthTokenSuccess(String authToken, IterableHelper.IterableSuccessCallback successCallback) { + private void handleAuthTokenSuccess(String authToken, IterableCallbackHandlers.SuccessCallback successCallback) { if (authToken != null) { IterableApi.getInstance().setAuthToken(authToken); queueExpirationRefresh(authToken); @@ -206,7 +210,7 @@ long getNextRetryInterval() { return nextRetryInterval; } - void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableHelper.IterableSuccessCallback successCallback) { + void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableCallbackHandlers.SuccessCallback successCallback) { if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) { // we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work return; diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt index acb4408a4..90dac7aea 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt @@ -1,6 +1,8 @@ package com.iterable.iterableapi import android.content.Context +import com.iterable.iterableapi.response.IterableResponseObject +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers import org.json.JSONException import org.json.JSONObject @@ -89,63 +91,65 @@ public class IterableEmbeddedManager : IterableActivityMonitor.AppStateCallback IterableLogger.v(TAG, "Syncing messages...") - - IterableApi.sharedInstance.getEmbeddedMessages(placementIds, IterableHelper.RemoteSuccessCallback { data: IterableResponseObject.RemoteSuccess -> - IterableLogger.v(TAG, "Got response from network call to get embedded messages") - try { - val previousPlacementIds = getPlacementIds() - val currentPlacementIds: MutableList = mutableListOf() - - - val placementsArray = - data.responseJson.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) - if (placementsArray != null) { - //if there are no placements in the payload - //reset the local message storage and trigger a UI update - if (placementsArray.length() == 0) { - reset() - if (previousPlacementIds.isNotEmpty()) { - updateHandleListeners.forEach { - IterableLogger.d(TAG, "Calling updateHandler") - it.onMessagesUpdated() + IterableApi.sharedInstance.getEmbeddedMessages(placementIds, object : IterableCallbackHandlers.RemoteSuccessCallback { + override fun onSuccess(data: IterableResponseObject.RemoteSuccess) { + IterableLogger.v(TAG, "Got response from network call to get embedded messages") + try { + val previousPlacementIds = getPlacementIds() + val currentPlacementIds: MutableList = mutableListOf() + + + val placementsArray = + data.responseJson.optJSONArray(IterableConstants.ITERABLE_EMBEDDED_MESSAGE_PLACEMENTS) + if (placementsArray != null) { + //if there are no placements in the payload + //reset the local message storage and trigger a UI update + if (placementsArray.length() == 0) { + reset() + if (previousPlacementIds.isNotEmpty()) { + updateHandleListeners.forEach { + IterableLogger.d(TAG, "Calling updateHandler") + it.onMessagesUpdated() + } + } + } else { + for (i in 0 until placementsArray.length()) { + val placementJson = placementsArray.optJSONObject(i) + val placement = + IterableEmbeddedPlacement.fromJSONObject(placementJson) + val placementId = placement.placementId + val messages = placement.messages + + currentPlacementIds.add(placementId) + updateLocalMessageMap(placementId, messages) } - } - } else { - for (i in 0 until placementsArray.length()) { - val placementJson = placementsArray.optJSONObject(i) - val placement = - IterableEmbeddedPlacement.fromJSONObject(placementJson) - val placementId = placement.placementId - val messages = placement.messages - - currentPlacementIds.add(placementId) - updateLocalMessageMap(placementId, messages) } } - } - // compare previous placements to the current placement payload - val removedPlacementIds = - previousPlacementIds.subtract(currentPlacementIds.toSet()) + // compare previous placements to the current placement payload + val removedPlacementIds = + previousPlacementIds.subtract(currentPlacementIds.toSet()) - //if there are placements removed, update the local storage and trigger UI update - if (removedPlacementIds.isNotEmpty()) { - removedPlacementIds.forEach { - localPlacementMessagesMap.remove(it) - } + //if there are placements removed, update the local storage and trigger UI update + if (removedPlacementIds.isNotEmpty()) { + removedPlacementIds.forEach { + localPlacementMessagesMap.remove(it) + } - updateHandleListeners.forEach { - IterableLogger.d(TAG, "Calling updateHandler") - it.onMessagesUpdated() + updateHandleListeners.forEach { + IterableLogger.d(TAG, "Calling updateHandler") + it.onMessagesUpdated() + } } - } - //store placements from payload for next comparison - localPlacementIds = currentPlacementIds + //store placements from payload for next comparison + localPlacementIds = currentPlacementIds - } catch (e: JSONException) { - IterableLogger.e(TAG, e.toString()) + } catch (e: JSONException) { + IterableLogger.e(TAG, e.toString()) + } } + }, object : IterableHelper.FailureHandler { override fun onFailure(reason: String, data: JSONObject?) { if (reason.equals( diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java index ab40c7c43..f2e53013b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -5,8 +5,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; + import org.json.JSONException; import org.json.JSONObject; +import com.iterable.iterableapi.response.handlers.auth.IterableAuthCallbackHandlers; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; /** * Created by David Truong dt@iterable.com @@ -25,11 +30,11 @@ public interface IterableUrlCallback { } /** - * @deprecated Use {@link IterableSuccessCallback} instead. + * @deprecated Use {@link } instead. *

* This interface is deprecated and will be removed in a future version. - * Replace all usages with {@link IterableSuccessCallback} for general success handling, - * or {@link RemoteSuccessCallback} if you specifically need JSON response data from the API. + * Replace all usages with {@link IterableCallbackHandlers.SuccessCallback} for general success handling, + * or {@link IterableCallbackHandlers.RemoteSuccessCallback} if you specifically need JSON response data from the API. *

* Quick Migration (Recommended): Replace {@code SuccessHandler} with {@code IterableSuccessCallback} *

@@ -39,17 +44,17 @@ public interface IterableUrlCallback {
      * };
      *
      * // WITH THIS (see JavaDoc on IterableSuccessCallback for more details):
-     * IterableHelper.IterableSuccessCallback successHandler = data -> {
+     * IterableCallbackHandlers.SuccessCallback successHandler = data -> {
      *     // Your code here - see JavaDoc for accessing response data
      * };
      * 
* - * @see IterableSuccessCallback - * @see RemoteSuccessCallback - * @see LocalSuccessCallback + * @see IterableCallbackHandlers.SuccessCallback + * @see IterableCallbackHandlers.RemoteSuccessCallback + * @see IterableCallbackHandlers.LocalSuccessCallback */ @Deprecated - public interface SuccessHandler extends IterableSuccessCallback { + public interface SuccessHandler extends IterableCallbackHandlers.SuccessCallback { void onSuccess(@NonNull JSONObject data); @Override @@ -62,8 +67,8 @@ default void onSuccess(@NonNull IterableResponseObject.Success data) { if (data instanceof IterableResponseObject.RemoteSuccess) { JSONObject originalJson = ((IterableResponseObject.RemoteSuccess) data).getResponseJson(); jsonObject = new JSONObject(originalJson.toString()); - } else if (data instanceof IterableResponseObject.AuthTokenSuccess) { - jsonObject.put("newAuthToken", ((IterableResponseObject.AuthTokenSuccess) data).getAuthToken()); + } else if (data instanceof IterableAuthResponseObject.Success) { + jsonObject.put("newAuthToken", ((IterableAuthResponseObject.Success) data).getAuthToken()); } else if (data instanceof IterableResponseObject.LocalSuccess) { jsonObject.put("message", data.getMessage()); } else { @@ -77,151 +82,14 @@ default void onSuccess(@NonNull IterableResponseObject.Success data) { } } - /** - * Generic callback interface for successful SDK operations. - *

- * When to use this callback: - *

    - *
  • When you want to proceed on any type of success (remote, local, or auth token)
  • - *
  • When you don't need to access specific response data (like JSON from API or auth tokens)
  • - *
  • When you just need to know the operation completed successfully, regardless of how
  • - *
- *

- * Example use cases: - *

    - *
  • {@code setEmail()} - Can complete locally (if autoPushRegistration is false) or remotely (via registerForPush)
  • - *
  • {@code setUserId()} - May complete locally or trigger remote operations
  • - *
  • Any operation where you just need confirmation of success without caring about the response details
  • - *
- *

- * When to use specialized callbacks instead: - *

    - *
  • Use {@link RemoteSuccessCallback} if you need to access JSON response data from the API
  • - *
  • Use {@link LocalSuccessCallback} if you only want to proceed when no remote call was made
  • - *
  • Use {@link AuthTokenCallback} if you need to access the authentication token
  • - *
- * - * @see RemoteSuccessCallback - * @see LocalSuccessCallback - * @see AuthTokenCallback - */ - public interface IterableSuccessCallback { - void onSuccess(@NonNull IterableResponseObject.Success data); - } - - /** - * Callback specifically for operations that make remote API calls and return JSON response data. - *

- * When to use this callback: - *

    - *
  • When you need to access the JSON response data from the Iterable API
  • - *
  • When you want your callback to trigger only if a remote API call was made
  • - *
  • When the operation you're calling always makes a remote API request
  • - *
- *

- * Example use cases: - *

    - *
  • {@code trackEvent()} - Always makes a remote API call with JSON response
  • - *
  • {@code updateUser()} - Makes a remote call to update user profile
  • - *
  • Operations where you need to inspect the server's response data
  • - *
- *

- * Important: If the operation completes locally (e.g., {@code setEmail} with autoPushRegistration disabled), - * your callback will NOT be triggered. Use {@link IterableSuccessCallback} instead if you want to handle both - * remote and local success cases. - */ - public interface RemoteSuccessCallback extends IterableSuccessCallback { - void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data); - - @Override - default void onSuccess(@NonNull IterableResponseObject.Success data) { - // Dispatch to the specific method if it's the correct type - if (data instanceof IterableResponseObject.RemoteSuccess) { - onSuccess((IterableResponseObject.RemoteSuccess) data); - } else { - IterableLogger.w("IterableHelper", "RemoteSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + - ". This callback only triggers for remote API responses. Consider using IterableSuccessCallback if you want to handle all success types."); - } - } - } - /** - * Callback specifically for authentication token operations. - *

- * When to use this callback: - *

    - *
  • When you need to access the JWT authentication token from the response
  • - *
  • For operations that request or refresh auth tokens
  • - *
  • When implementing custom auth token handling or caching
  • - *
- *

- * Example use cases: - *

    - *
  • Auth token refresh operations triggered by {@link IterableAuthHandler}
  • - *
  • Operations that return a new JWT token for the current user
  • - *
  • Custom auth token validation or storage logic
  • - *
- *

- * Note: This is primarily used internally by the SDK's auth system. Most applications - * won't need to use this callback directly. - */ - public interface AuthTokenCallback extends IterableSuccessCallback { - void onSuccess(@NonNull IterableResponseObject.AuthTokenSuccess data); - - @Override - default void onSuccess(@NonNull IterableResponseObject.Success data) { - // Dispatch to the specific method if it's the correct type - if (data instanceof IterableResponseObject.AuthTokenSuccess) { - onSuccess((IterableResponseObject.AuthTokenSuccess) data); - } else { - IterableLogger.w("IterableHelper", "AuthTokenCallback received unexpected success type: " + data.getClass().getSimpleName() + - ". This callback is for auth token operations only."); - } - } - } - - /** - * Callback specifically for operations that complete locally without making a remote API call. - *

- * When to use this callback: - *

    - *
  • When you want your callback to trigger only if the operation completed locally
  • - *
  • When you want to distinguish between operations that hit the server vs. those that don't
  • - *
  • For testing or debugging purposes to verify no remote call was made
  • - *
- *

- * Example use cases: - *

    - *
  • {@code setEmail()} with {@code autoPushRegistration = false} - Updates locally without calling the API
  • - *
  • Operations that only update local state or keychain
  • - *
  • Scenarios where you want different behavior based on whether a network call occurred
  • - *
- *

- * Important: If the operation makes a remote call (e.g., {@code setEmail} with autoPushRegistration enabled), - * your callback will NOT be triggered. Use {@link IterableSuccessCallback} instead if you want to handle both - * local and remote success cases. - */ - public interface LocalSuccessCallback extends IterableSuccessCallback { - void onSuccess(@NonNull IterableResponseObject.LocalSuccess data); - - @Override - default void onSuccess(@NonNull IterableResponseObject.Success data) { - // Dispatch to the specific method if it's the correct type - if (data instanceof IterableResponseObject.LocalSuccess) { - onSuccess((IterableResponseObject.LocalSuccess) data); - } else { - IterableLogger.w("IterableHelper", "LocalSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + - ". This callback only triggers for local-only operations. Consider using IterableSuccessCallback if you want to handle all success types."); - } - } - } public interface FailureHandler { void onFailure(@NonNull String reason, @Nullable JSONObject data); } /** - * @deprecated Use {@link AuthTokenCallback} instead for better type safety and clarity. + * @deprecated Use {@link IterableAuthCallbackHandlers.AuthTokenCallback} instead for better type safety and clarity. */ @Deprecated public interface SuccessAuthHandler { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index c63abfc61..083825e1c 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -12,6 +12,8 @@ import com.iterable.iterableapi.IterableInAppHandler.InAppResponse; import com.iterable.iterableapi.IterableInAppMessage.Trigger.TriggerType; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import org.json.JSONArray; import org.json.JSONException; @@ -137,7 +139,7 @@ public synchronized void setRead(@NonNull IterableInAppMessage message, boolean * @param read Read state flag. true = read, false = unread * @param successHandler The callback which returns `success`. */ - public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { message.setRead(read); if (successHandler != null) { successHandler.onSuccess(IterableResponseObject.LocalSuccessResponse); // passing blank json object here as onSuccess is @Nonnull @@ -279,7 +281,7 @@ public synchronized void removeMessage(@NonNull IterableInAppMessage message, @N * @param successHandler The callback which returns `success`. * @param failureHandler The callback which returns `failure`. */ - public synchronized void removeMessage(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableHelper.IterableSuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { + public synchronized void removeMessage(@NonNull IterableInAppMessage message, @Nullable IterableInAppDeleteActionType source, @Nullable IterableInAppLocation clickLocation, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { IterableLogger.printInfo(); if (message != null) { message.setConsumed(true); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java index 92815c122..b1eca58da 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java @@ -12,6 +12,10 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + import org.json.JSONException; import org.json.JSONObject; @@ -396,7 +400,7 @@ private static void requestNewAuthTokenAndRetry(IterableApiRequest iterableApiRe IterableApi.getInstance().getAuthManager().setIsLastAuthTokenValid(false); long retryInterval = IterableApi.getInstance().getAuthManager().getNextRetryInterval(); IterableApi.getInstance().getAuthManager().scheduleAuthTokenRefresh(retryInterval, false, data -> { - IterableResponseObject.AuthTokenSuccess authTokenResponse = (IterableResponseObject.AuthTokenSuccess) data; + IterableAuthResponseObject.Success authTokenResponse = (IterableAuthResponseObject.Success) data; String newAuthToken = authTokenResponse.getAuthToken(); retryRequestWithNewAuthToken(newAuthToken, iterableApiRequest); }); @@ -425,7 +429,7 @@ class IterableApiRequest { private ProcessorType processorType = ProcessorType.ONLINE; IterableHelper.IterableActionHandler legacyCallback; - IterableHelper.IterableSuccessCallback successCallback; + IterableCallbackHandlers.SuccessCallback successCallback; IterableHelper.FailureHandler failureCallback; enum ProcessorType { @@ -453,7 +457,7 @@ void setProcessorType(ProcessorType processorType) { this.processorType = processorType; } - IterableApiRequest(String apiKey, String baseUrl, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.IterableSuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { + IterableApiRequest(String apiKey, String baseUrl, String resourcePath, JSONObject json, String requestType, String authToken, IterableCallbackHandlers.SuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { this.apiKey = apiKey; this.baseUrl = baseUrl; this.resourcePath = resourcePath; @@ -464,7 +468,7 @@ void setProcessorType(ProcessorType processorType) { this.failureCallback = onFailure; } - IterableApiRequest(String apiKey, String resourcePath, JSONObject json, String requestType, String authToken, IterableHelper.IterableSuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { + IterableApiRequest(String apiKey, String resourcePath, JSONObject json, String requestType, String authToken, IterableCallbackHandlers.SuccessCallback onSuccess, IterableHelper.FailureHandler onFailure) { this.apiKey = apiKey; this.baseUrl = null; this.resourcePath = resourcePath; @@ -495,7 +499,7 @@ public JSONObject toJSONObject() throws JSONException { return jsonObject; } - static IterableApiRequest fromJSON(JSONObject jsonData, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + static IterableApiRequest fromJSON(JSONObject jsonData, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { try { String apikey = jsonData.getString("apiKey"); String resourcePath = jsonData.getString("resourcePath"); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java index 684e610c2..c648c584f 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java @@ -7,6 +7,9 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + import org.json.JSONException; import org.json.JSONObject; @@ -60,13 +63,13 @@ public void processGetRequest(@Nullable String apiKey, @NonNull String resourceP } @Override - public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, json, IterableApiRequest.GET, authToken, onSuccess, onFailure); new IterableRequestTask().execute(request); } @Override - public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, json, IterableApiRequest.POST, authToken, onSuccess, onFailure); if (isRequestOfflineCompatible(request.resourcePath) && healthMonitor.canSchedule()) { request.setProcessorType(IterableApiRequest.ProcessorType.OFFLINE); @@ -87,7 +90,7 @@ boolean isRequestOfflineCompatible(String baseUrl) { } class TaskScheduler implements IterableTaskRunner.TaskCompletedListener { - static HashMap successCallbackMap = new HashMap<>(); + static HashMap successCallbackMap = new HashMap<>(); static HashMap failureCallbackMap = new HashMap<>(); private final IterableTaskStorage taskStorage; private final IterableTaskRunner taskRunner; @@ -98,7 +101,7 @@ class TaskScheduler implements IterableTaskRunner.TaskCompletedListener { taskRunner.addTaskCompletedListener(this); } - void scheduleTask(IterableApiRequest request, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + void scheduleTask(IterableApiRequest request, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { JSONObject serializedRequest = null; try { serializedRequest = request.toJSONObject(); @@ -120,7 +123,7 @@ void scheduleTask(IterableApiRequest request, @Nullable IterableHelper.IterableS @MainThread @Override public void onTaskCompleted(String taskId, IterableTaskRunner.TaskResult result, IterableApiResponse response) { - IterableHelper.IterableSuccessCallback onSuccess = successCallbackMap.get(taskId); + IterableCallbackHandlers.SuccessCallback onSuccess = successCallbackMap.get(taskId); IterableHelper.FailureHandler onFailure = failureCallbackMap.get(taskId); successCallbackMap.remove(taskId); failureCallbackMap.remove(taskId); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java index a806b7b84..52d7091e7 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + import org.json.JSONException; import org.json.JSONObject; @@ -22,13 +24,13 @@ public void processGetRequest(@Nullable String apiKey, @NonNull String resourceP } @Override - public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, addCreatedAtToJson(json), IterableApiRequest.GET, authToken, onSuccess, onFailure); new IterableRequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, request); } @Override - public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { + public void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure) { IterableApiRequest request = new IterableApiRequest(apiKey, resourcePath, addCreatedAtToJson(json), IterableApiRequest.POST, authToken, onSuccess, onFailure); new IterableRequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, request); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java index 497f94112..1c4e99e00 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/RequestProcessor.java @@ -5,13 +5,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + import org.json.JSONObject; public interface RequestProcessor { void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableActionHandler onCallback); - void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); + void processGetRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); - void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableHelper.IterableSuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); + void processPostRequest(@Nullable String apiKey, @NonNull String resourcePath, @NonNull JSONObject json, String authToken, @Nullable IterableCallbackHandlers.SuccessCallback onSuccess, @Nullable IterableHelper.FailureHandler onFailure); void onLogout(Context context); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableAuthResponseObject.kt b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableAuthResponseObject.kt new file mode 100644 index 000000000..061b152b4 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableAuthResponseObject.kt @@ -0,0 +1,15 @@ +package com.iterable.iterableapi.response + +sealed class IterableAuthResponseObject( + message: String, + code: IterableResponseCode +): IterableResponseObject(message, code) { + + class Success( + val authToken: String + ): IterableResponseObject.Success( + message = SuccessMessages.AUTH_TOKEN_SUCCESS, + ) + + +} \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt similarity index 71% rename from iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt rename to iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt index 94343de53..aa18c982b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableResponseObject.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt @@ -1,4 +1,4 @@ -package com.iterable.iterableapi +package com.iterable.iterableapi.response import org.json.JSONObject @@ -10,36 +10,30 @@ sealed class IterableResponseObject( message: String, ): IterableResponseObject(message, IterableResponseCode.SUCCESS) - class GenericSuccess( - message: String, - ): Success(message) - class RemoteSuccess(val responseJson: JSONObject): Success( message = SuccessMessages.REMOTE_SUCCESS ) - class AuthTokenSuccess( - val authToken: String - ): Success( - message = SuccessMessages.AUTH_TOKEN_SUCCESS, - ) - object LocalSuccess: Success( message = SuccessMessages.LOCAL_SUCCESS, ) - class Failure(remoteMessage: String): IterableResponseObject( - message = remoteMessage, + sealed class Failure(message: String): IterableResponseObject( + message = message, code = IterableResponseCode.FAILURE ) + class RemoteFailure(remoteMessage: String, val errorCode: Int): Failure( + message = remoteMessage + ) + companion object { @JvmField val LocalSuccessResponse = LocalSuccess } - private object SuccessMessages { + object SuccessMessages { const val REMOTE_SUCCESS = "Successfully received response from remote API" const val AUTH_TOKEN_SUCCESS = "Successfully obtained authentication token" const val LOCAL_SUCCESS = "Operation completed locally without remote API call" diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/IterableCallbackHandlers.java b/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/IterableCallbackHandlers.java new file mode 100644 index 000000000..576cd7cfe --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/IterableCallbackHandlers.java @@ -0,0 +1,124 @@ +package com.iterable.iterableapi.response.handlers; + +import androidx.annotation.NonNull; + +import com.iterable.iterableapi.IterableLogger; +import com.iterable.iterableapi.response.IterableResponseObject; + +/** + * Callback handlers for Iterable SDK operations. + */ +public class IterableCallbackHandlers { + + /** + * Generic callback interface for successful SDK operations. + * + *

When to use this callback:

+ *
    + *
  • When you want to proceed on any type of success (remote, local, or auth token)
  • + *
  • When you don't need to access specific response data (like JSON from API or auth tokens)
  • + *
  • When you just need to know the operation completed successfully, regardless of how
  • + *
+ * + *

Example use cases:

+ *
    + *
  • {@code setEmail()} - Can complete locally (if autoPushRegistration is false) or remotely (via registerForPush)
  • + *
  • {@code setUserId()} - May complete locally or trigger remote operations
  • + *
  • Any operation where you just need confirmation of success without caring about the response details
  • + *
+ * + *

When to use specialized callbacks instead:

+ *
    + *
  • Use {@link RemoteSuccessCallback} if you need to access JSON response data from the API
  • + *
  • Use {@link LocalSuccessCallback} if you only want to proceed when no remote call was made
  • + *
  • Use {@link com.iterable.iterableapi.response.handlers.auth.IterableAuthCallbackHandlers.AuthTokenCallback} + * if you need to access the authentication token
  • + *
+ * + * @see RemoteSuccessCallback + * @see LocalSuccessCallback + */ + public interface SuccessCallback { + void onSuccess(@NonNull IterableResponseObject.Success data); + } + + /** + * Callback specifically for operations that make remote API calls and return JSON response data. + * + *

When to use this callback:

+ *
    + *
  • When you need to access the JSON response data from the Iterable API
  • + *
  • When you want your callback to trigger only if a remote API call was made
  • + *
  • When the operation you're calling always makes a remote API request
  • + *
+ * + *

Example use cases:

+ *
    + *
  • {@code trackEvent()} - Always makes a remote API call with JSON response
  • + *
  • {@code updateUser()} - Makes a remote call to update user profile
  • + *
  • Operations where you need to inspect the server's response data
  • + *
+ * + *

Important: If the operation completes locally (e.g., {@code setEmail} with autoPushRegistration disabled), + * your callback will NOT be triggered. Use {@link SuccessCallback} instead if you want to handle both + * remote and local success cases.

+ */ + public interface RemoteSuccessCallback extends SuccessCallback { + void onSuccess(@NonNull IterableResponseObject.RemoteSuccess data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableResponseObject.RemoteSuccess) { + onSuccess((IterableResponseObject.RemoteSuccess) data); + } else { + IterableLogger.w( + "IterableHelper", + "RemoteSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback only triggers for remote API responses. Consider using SuccessCallback if you want to handle all success types." + ); + } + } + } + + /** + * Callback specifically for operations that complete locally without making a remote API call. + * + *

When to use this callback:

+ *
    + *
  • When you want your callback to trigger only if the operation completed locally
  • + *
  • When you want to distinguish between operations that hit the server vs. those that don't
  • + *
  • For testing or debugging purposes to verify no remote call was made
  • + *
+ * + *

Example use cases:

+ *
    + *
  • {@code setEmail()} with {@code autoPushRegistration = false} - Updates locally without calling the API
  • + *
  • Operations that only update local state or keychain
  • + *
  • Scenarios where you want different behavior based on whether a network call occurred
  • + *
+ * + *

Important: If the operation makes a remote call (e.g., {@code setEmail} with autoPushRegistration enabled), + * your callback will NOT be triggered. Use {@link SuccessCallback} instead if you want to handle both + * local and remote success cases.

+ */ + public interface LocalSuccessCallback extends SuccessCallback { + void onSuccess(@NonNull IterableResponseObject.LocalSuccess data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableResponseObject.LocalSuccess) { + onSuccess((IterableResponseObject.LocalSuccess) data); + } else { + IterableLogger.w( + "IterableHelper", + "LocalSuccessCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback only triggers for local-only operations. Consider using SuccessCallback if you want to handle all success types." + ); + } + } + } + + +} diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/auth/IterableAuthCallbackHandlers.java b/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/auth/IterableAuthCallbackHandlers.java new file mode 100644 index 000000000..f3c5b2879 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/response/handlers/auth/IterableAuthCallbackHandlers.java @@ -0,0 +1,52 @@ +package com.iterable.iterableapi.response.handlers.auth; + +import androidx.annotation.NonNull; + +import com.iterable.iterableapi.IterableLogger; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + +/** + * Callback handlers for authentication-related operations. + */ +public class IterableAuthCallbackHandlers { + + /** + * Callback specifically for authentication token operations. + * + *

When to use this callback:

+ *
    + *
  • When you need to access the JWT authentication token from the response
  • + *
  • For operations that request or refresh auth tokens
  • + *
  • When implementing custom auth token handling or caching
  • + *
+ * + *

Example use cases:

+ *
    + *
  • Auth token refresh operations triggered by {@link com.iterable.iterableapi.IterableAuthHandler}
  • + *
  • Operations that return a new JWT token for the current user
  • + *
  • Custom auth token validation or storage logic
  • + *
+ * + *

Note: This is primarily used internally by the SDK's auth system. Most applications + * won't need to use this callback directly.

+ */ + public interface AuthTokenCallback extends IterableCallbackHandlers.SuccessCallback { + void onSuccess(@NonNull IterableAuthResponseObject.Success data); + + @Override + default void onSuccess(@NonNull IterableResponseObject.Success data) { + // Dispatch to the specific method if it's the correct type + if (data instanceof IterableAuthResponseObject.Success) { + onSuccess((IterableAuthResponseObject.Success) data); + } else { + IterableLogger.w( + "IterableHelper", + "AuthTokenCallback received unexpected success type: " + data.getClass().getSimpleName() + + ". This callback is for auth token operations only." + ); + } + } + } +} diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java index dd441f568..5d0e080ce 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java @@ -16,6 +16,8 @@ import android.content.Context; import android.content.SharedPreferences; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.junit.After; @@ -200,7 +202,7 @@ public void testStoreAuthData_CompletionHandler_ReceivesStoredCredentials() thro final CountDownLatch latch = new CountDownLatch(1); // Capture what the completion handler receives - spyApi.setEmail(originalEmail, new IterableHelper.IterableSuccessCallback() { + spyApi.setEmail(originalEmail, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(IterableResponseObject.Success data) { // This callback happens after completeUserLogin diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java index 1c443618d..886fd081b 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiRequestTest.java @@ -8,6 +8,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.unit.TestRunner; import org.json.JSONObject; @@ -59,7 +61,7 @@ public void testRequestSerialization() throws Exception { String requestType = "api"; String authToken = "authToken123##"; - IterableHelper.IterableSuccessCallback successHandler = new IterableHelper.IterableSuccessCallback() { + IterableCallbackHandlers.SuccessCallback successHandler = new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { IterableLogger.v("RequestSerializationTest", "Passed"); diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java index 635883563..6a958b8da 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java @@ -1,5 +1,7 @@ package com.iterable.iterableapi; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.util.DeviceInfoUtils; import android.app.Activity; import android.content.Context; @@ -161,7 +163,7 @@ public void testUpdateEmailPersistence() throws Exception { IterableApi.getInstance().updateEmail(newEmail); shadowOf(getMainLooper()).idle(); - verify(mockApiClient).updateEmail(eq(newEmail), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); + verify(mockApiClient).updateEmail(eq(newEmail), nullable(IterableCallbackHandlers.SuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); server.takeRequest(1, TimeUnit.SECONDS); assertEquals("new@email.com", IterableApi.getInstance().getEmail()); @@ -175,7 +177,7 @@ public void testSetEmailWithCallback() { IterableApi.initialize(getContext(), "apiKey"); String email = "test@example.com"; - IterableApi.getInstance().setEmail(email, new IterableHelper.IterableSuccessCallback() { + IterableApi.getInstance().setEmail(email, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { assertTrue(true); // callback should be called with success @@ -193,7 +195,7 @@ public void testSetUserIdWithCallback() { IterableApi.initialize(getContext(), "apiKey"); String userId = "test_user_id"; - IterableApi.getInstance().setUserId(userId, new IterableHelper.IterableSuccessCallback() { + IterableApi.getInstance().setUserId(userId, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { assertTrue(true); // callback should be called with success @@ -1015,7 +1017,7 @@ public void testRegisterDeviceTokenSuccessCallback_CreatesWrappedHandler() throw IterableApi.getInstance().setVisitorUsageTracked(true); // Create a mock success handler - IterableHelper.IterableSuccessCallback originalHandler = mock(IterableHelper.IterableSuccessCallback.class); + IterableCallbackHandlers.SuccessCallback originalHandler = mock(IterableCallbackHandlers.SuccessCallback.class); // Set up user with success handler IterableApi.getInstance().setEmail("test@example.com", originalHandler, null); @@ -1032,7 +1034,7 @@ public void testRegisterDeviceTokenSuccessCallback_CreatesWrappedHandler() throw shadowOf(getMainLooper()).idle(); // Verify: registerDeviceToken was called with a success handler - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.IterableSuccessCallback.class); + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableCallbackHandlers.SuccessCallback.class); verify(mockClient, timeout(1000)).registerDeviceToken( eq("test@example.com"), nullable(String.class), @@ -1075,7 +1077,7 @@ public void testRegisterDeviceTokenSuccessCallback_WithoutOriginalHandler() thro shadowOf(getMainLooper()).idle(); // Verify: registerDeviceToken was called with a success handler (the wrapper) - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.IterableSuccessCallback.class); + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableCallbackHandlers.SuccessCallback.class); verify(mockClient, timeout(1000)).registerDeviceToken( nullable(String.class), eq("test_user_123"), @@ -1112,7 +1114,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingTriggered() throws IterableApi.getInstance().setVisitorUsageTracked(true); // Create a success handler and set user - IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); + IterableCallbackHandlers.SuccessCallback successHandler = mock(IterableCallbackHandlers.SuccessCallback.class); IterableApi.getInstance().setEmail("test@example.com", successHandler, null); // Execute: Register device token @@ -1162,7 +1164,7 @@ public void testRegisterDeviceTokenIntegration_ConsentLoggingNotTriggeredWhenDis // Set up other conditions IterableApi.getInstance().setVisitorUsageTracked(true); - IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); + IterableCallbackHandlers.SuccessCallback successHandler = mock(IterableCallbackHandlers.SuccessCallback.class); IterableApi.getInstance().setEmail("test@example.com", successHandler, null); // Execute: Register device token diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java index f7b9b3546..4e06663ce 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableAsyncInitializationTest.java @@ -27,6 +27,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + /** * Comprehensive test suite for async initialization functionality. * Tests ANR elimination, operation queuing, callback execution, and edge cases. @@ -1317,9 +1319,9 @@ public void onSDKInitialized() { // Call multiple overloaded method chains during initialization // Each overload internally delegates to the full signature IterableApi.getInstance().setEmail("user1@test.com"); - IterableApi.getInstance().setEmail("user2@test.com", (IterableHelper.IterableSuccessCallback) null, null); + IterableApi.getInstance().setEmail("user2@test.com", (IterableCallbackHandlers.SuccessCallback) null, null); IterableApi.getInstance().setUserId("user123"); - IterableApi.getInstance().setUserId("user456", (IterableHelper.IterableSuccessCallback) null, null); + IterableApi.getInstance().setUserId("user456", (IterableCallbackHandlers.SuccessCallback) null, null); IterableApi.getInstance().updateEmail("newemail@test.com"); // Verify operations are queued diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java index 1407bc6f8..97ec4ecb0 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java @@ -12,6 +12,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.iterable.iterableapi.response.IterableAuthResponseObject; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; +import com.iterable.iterableapi.response.handlers.auth.IterableAuthCallbackHandlers; + /** * Tests the functionality of IterableHelper callback interfaces */ @@ -38,7 +43,7 @@ public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { AtomicBoolean callbackInvoked = new AtomicBoolean(false); - IterableHelper.IterableSuccessCallback callback = data -> { + IterableCallbackHandlers.SuccessCallback callback = data -> { callbackInvoked.set(true); assertTrue(data instanceof IterableResponseObject.RemoteSuccess); try { @@ -58,7 +63,7 @@ public void testIterableSuccessCallback_WithLocalSuccess() { AtomicBoolean callbackInvoked = new AtomicBoolean(false); - IterableHelper.IterableSuccessCallback callback = data -> { + IterableCallbackHandlers.SuccessCallback callback = data -> { callbackInvoked.set(true); assertTrue(data instanceof IterableResponseObject.LocalSuccess); assertNotNull(data.getMessage()); @@ -71,14 +76,14 @@ public void testIterableSuccessCallback_WithLocalSuccess() { @Test public void testIterableSuccessCallback_WithAuthTokenSuccess() { String testToken = "test-jwt-token-123"; - IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + IterableAuthResponseObject.Success authSuccess = new IterableAuthResponseObject.Success(testToken); AtomicBoolean callbackInvoked = new AtomicBoolean(false); - IterableHelper.IterableSuccessCallback callback = data -> { + IterableCallbackHandlers.SuccessCallback callback = data -> { callbackInvoked.set(true); - assertTrue(data instanceof IterableResponseObject.AuthTokenSuccess); - assertEquals(testToken, ((IterableResponseObject.AuthTokenSuccess) data).getAuthToken()); + assertTrue(data instanceof IterableAuthResponseObject.Success); + assertEquals(testToken, ((IterableAuthResponseObject.Success) data).getAuthToken()); }; callback.onSuccess(authSuccess); @@ -94,7 +99,7 @@ public void testRemoteSuccessCallback_WithCorrectType() throws Exception { AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { + IterableCallbackHandlers.RemoteSuccessCallback callback = new IterableCallbackHandlers.RemoteSuccessCallback() { @Override public void onSuccess(IterableResponseObject.RemoteSuccess data) { typedCallbackInvoked.set(true); @@ -112,7 +117,7 @@ public void testRemoteSuccessCallback_WithLocalSuccess_LogsWarning() { AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.RemoteSuccessCallback callback = new IterableHelper.RemoteSuccessCallback() { + IterableCallbackHandlers.RemoteSuccessCallback callback = new IterableCallbackHandlers.RemoteSuccessCallback() { @Override public void onSuccess(IterableResponseObject.RemoteSuccess data) { typedCallbackInvoked.set(true); @@ -132,7 +137,7 @@ public void testLocalSuccessCallback_WithCorrectType() { AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { + IterableCallbackHandlers.LocalSuccessCallback callback = new IterableCallbackHandlers.LocalSuccessCallback() { @Override public void onSuccess(IterableResponseObject.LocalSuccess data) { typedCallbackInvoked.set(true); @@ -151,7 +156,7 @@ public void testLocalSuccessCallback_WithRemoteSuccess_LogsWarning() throws Exce AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.LocalSuccessCallback callback = new IterableHelper.LocalSuccessCallback() { + IterableCallbackHandlers.LocalSuccessCallback callback = new IterableCallbackHandlers.LocalSuccessCallback() { @Override public void onSuccess(IterableResponseObject.LocalSuccess data) { typedCallbackInvoked.set(true); @@ -168,13 +173,13 @@ public void onSuccess(IterableResponseObject.LocalSuccess data) { @Test public void testAuthTokenCallback_WithCorrectType() { String testToken = "jwt-token-xyz"; - IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + IterableResponseObject.Success authSuccess = new IterableAuthResponseObject.Success(testToken); AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { + IterableAuthCallbackHandlers.AuthTokenCallback callback = new IterableAuthCallbackHandlers.AuthTokenCallback() { @Override - public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { + public void onSuccess(IterableAuthResponseObject.Success data) { typedCallbackInvoked.set(true); assertEquals(testToken, data.getAuthToken()); } @@ -190,9 +195,9 @@ public void testAuthTokenCallback_WithWrongType_LogsWarning() { AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); - IterableHelper.AuthTokenCallback callback = new IterableHelper.AuthTokenCallback() { + IterableAuthCallbackHandlers.AuthTokenCallback callback = new IterableAuthCallbackHandlers.AuthTokenCallback() { @Override - public void onSuccess(IterableResponseObject.AuthTokenSuccess data) { + public void onSuccess(IterableAuthResponseObject.Success data) { typedCallbackInvoked.set(true); } }; @@ -256,7 +261,7 @@ public void onSuccess(JSONObject data) { @Test public void testSuccessHandler_WithAuthTokenSuccess_PassesTokenJSON() throws Exception { String testToken = "test-auth-token"; - IterableResponseObject.AuthTokenSuccess authSuccess = new IterableResponseObject.AuthTokenSuccess(testToken); + IterableAuthResponseObject.Success authSuccess = new IterableAuthResponseObject.Success(testToken); AtomicBoolean callbackInvoked = new AtomicBoolean(false); AtomicReference receivedJson = new AtomicReference<>(); diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java index f030e85c3..e18614061 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerTest.java @@ -8,6 +8,7 @@ import androidx.fragment.app.FragmentActivity; import java.util.List; + import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.json.JSONArray; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java index 25a5fc3be..4da4c56ef 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInboxTest.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.json.JSONObject; @@ -98,7 +100,7 @@ public void testRemoveMessageSuccessCallbackOnSuccessfulResponse() throws Except final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); dispatcher.enqueueResponse("/events/inAppConsume", new MockResponse().setResponseCode(200).setBody(responseData.toString())); - inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.IterableSuccessCallback() { + inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { signal.countDown(); @@ -127,7 +129,7 @@ public void testRemoveMessageFailureCallbackOnFailedResponse() throws Exception final JSONObject responseData = new JSONObject("{\"key\":\"value\"}"); dispatcher.enqueueResponse("/events/inAppConsume", new MockResponse().setResponseCode(500).setBody(responseData.toString())); - inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableHelper.IterableSuccessCallback() { + inAppManager.removeMessage(inboxMessages.get(0), null, null, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { assertFalse(true); @@ -163,7 +165,7 @@ public void testSetRead() throws Exception { // Set first message as read with a callback final boolean[] callbackCalled = { false }; - inAppManager.setRead(inboxMessages.get(0), true, new IterableHelper.IterableSuccessCallback() { + inAppManager.setRead(inboxMessages.get(0), true, new IterableCallbackHandlers.SuccessCallback() { @Override public void onSuccess(@NonNull IterableResponseObject.Success data) { callbackCalled[0] = true; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java index c3104a19e..5deeb3abf 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushRegistrationTaskTest.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; + public class IterablePushRegistrationTaskTest extends BaseTest { private static final String TEST_TOKEN = "testToken"; @@ -75,7 +77,7 @@ public void testEnableDevice() throws Exception { verify(apiMock, timeout(100)).registerDeviceToken(eq(IterableTestUtils.userEmail), nullable(String.class), isNull(), eq(INTEGRATION_NAME), eq(TEST_TOKEN), eq(deviceAttributes)); - verify(apiMock, never()).disableToken(eq(IterableTestUtils.userEmail), nullable(String.class), nullable(String.class), any(String.class), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); + verify(apiMock, never()).disableToken(eq(IterableTestUtils.userEmail), nullable(String.class), nullable(String.class), any(String.class), nullable(IterableCallbackHandlers.SuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); } @Test @@ -87,6 +89,6 @@ public void testDisableDevice() throws Exception { new IterablePushRegistrationTask().execute(data); shadowOf(getMainLooper()).idle(); - verify(apiMock, timeout(100)).disableToken(eq(IterableTestUtils.userEmail), isNull(), isNull(), eq(TEST_TOKEN), nullable(IterableHelper.IterableSuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); + verify(apiMock, timeout(100)).disableToken(eq(IterableTestUtils.userEmail), isNull(), isNull(), eq(TEST_TOKEN), nullable(IterableCallbackHandlers.SuccessCallback.class), nullable(IterableHelper.FailureHandler.class)); } } \ No newline at end of file diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java index cb003a8b6..3f99dd2c5 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/TaskSchedulerTest.java @@ -1,5 +1,7 @@ package com.iterable.iterableapi; +import com.iterable.iterableapi.response.IterableResponseObject; +import com.iterable.iterableapi.response.handlers.IterableCallbackHandlers; import com.iterable.iterableapi.unit.TestRunner; import org.json.JSONObject; @@ -35,7 +37,7 @@ public void testScheduleTaskCreatesTaskInStorage() throws Exception { @Test public void testSuccessCallbackIsCalledOnCompletion() throws Exception { - IterableHelper.IterableSuccessCallback successHandler = mock(IterableHelper.IterableSuccessCallback.class); + IterableCallbackHandlers.SuccessCallback successHandler = mock(IterableCallbackHandlers.SuccessCallback.class); IterableApiRequest request = new IterableApiRequest("apiKey", "api/test", new JSONObject(), "POST", null, null, null); when(mockTaskStorage.createTask(any(String.class), any(IterableTaskType.class), any(String.class))).thenReturn("testTaskId"); taskScheduler.scheduleTask(request, successHandler, null); From 760edb6eaa509e835683d3f8f9fa77f02808e188 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Mon, 26 Jan 2026 15:54:29 +0000 Subject: [PATCH 5/5] Clearing setEmailCallback after invoking it --- .../com/iterable/iterableapi/IterableApi.java | 21 ++++++++++++++----- .../iterableapi/IterableInAppManager.java | 2 +- .../response/IterableResponseObject.kt | 9 ++------ .../iterableapi/IterableHelperUnitTest.java | 10 ++++----- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index c0386d98b..697fefa8b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -457,15 +457,20 @@ private void completeUserLogin(@Nullable String email, @Nullable String userId, if (config.autoPushRegistration) { registerForPush(); } else if (_setUserSuccessCallbackHandler != null) { - invokeSetUserSuccessHandler(); + invokeSetUserSuccessHandler(new IterableResponseObject.LocalSuccess("As autoPushRegistration is false, setEmail is completed locally")); } getInAppManager().syncInApp(); getEmbeddedManager().syncMessages(); } - private void invokeSetUserSuccessHandler() { - _setUserSuccessCallbackHandler.onSuccess(IterableResponseObject.LocalSuccessResponse); + private void invokeSetUserSuccessHandler(IterableResponseObject.Success responseObject) { + if (_setUserSuccessCallbackHandler != null) { + _setUserSuccessCallbackHandler.onSuccess(responseObject); + // Clear the callback after invoking to prevent double-calls + _setUserSuccessCallbackHandler = null; + _setUserFailureCallbackHandler = null; + } } private final IterableActivityMonitor.AppStateCallback activityMonitorListener = new IterableActivityMonitor.AppStateCallback() { @@ -748,12 +753,15 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user private IterableCallbackHandlers.SuccessCallback wrapSetUserCallbackForRemoteCall() { IterableCallbackHandlers.SuccessCallback wrappedSuccessHandler = null; if (_setUserSuccessCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { - final IterableCallbackHandlers.SuccessCallback originalSuccessHandler = _setUserSuccessCallbackHandler; + final IterableCallbackHandlers.SuccessCallback originalSuccessHandler = _setUserSuccessCallbackHandler; // todo: not sure if we need to store it before instead of just calling the callback on success wrappedSuccessHandler = data -> { trackConsentOnDeviceRegistration(); if (originalSuccessHandler != null) { originalSuccessHandler.onSuccess(data); + + _setUserSuccessCallbackHandler = null; + _setUserFailureCallbackHandler = null; } }; } @@ -763,12 +771,15 @@ private IterableCallbackHandlers.SuccessCallback wrapSetUserCallbackForRemoteCal private IterableHelper.FailureHandler wrapSetUserFailureHandlerForRemoteCall() { IterableHelper.FailureHandler wrappedFailureHandler = null; if (_setUserFailureCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { - final IterableHelper.FailureHandler originalFailureHandler = _setUserFailureCallbackHandler; + final IterableHelper.FailureHandler originalFailureHandler = _setUserFailureCallbackHandler; // todo: not sure if we need to store it before instead of just calling the callback on success wrappedFailureHandler = (reason, data) -> { trackConsentOnDeviceRegistration(); if (originalFailureHandler != null) { originalFailureHandler.onFailure(reason, data); + + _setUserSuccessCallbackHandler = null; + _setUserFailureCallbackHandler = null; } }; } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index 083825e1c..5cb466632 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -142,7 +142,7 @@ public synchronized void setRead(@NonNull IterableInAppMessage message, boolean public synchronized void setRead(@NonNull IterableInAppMessage message, boolean read, @Nullable IterableCallbackHandlers.SuccessCallback successHandler, @Nullable IterableHelper.FailureHandler failureHandler) { message.setRead(read); if (successHandler != null) { - successHandler.onSuccess(IterableResponseObject.LocalSuccessResponse); // passing blank json object here as onSuccess is @Nonnull + successHandler.onSuccess(new IterableResponseObject.LocalSuccess()); } notifyOnChange(); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt index aa18c982b..436149640 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/response/IterableResponseObject.kt @@ -14,8 +14,8 @@ sealed class IterableResponseObject( message = SuccessMessages.REMOTE_SUCCESS ) - object LocalSuccess: Success( - message = SuccessMessages.LOCAL_SUCCESS, + class LocalSuccess(localMessage: String = SuccessMessages.LOCAL_SUCCESS): Success( + message = localMessage, ) @@ -28,11 +28,6 @@ sealed class IterableResponseObject( message = remoteMessage ) - companion object { - @JvmField - val LocalSuccessResponse = LocalSuccess - } - object SuccessMessages { const val REMOTE_SUCCESS = "Successfully received response from remote API" const val AUTH_TOKEN_SUCCESS = "Successfully obtained authentication token" diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java index 97ec4ecb0..3f2bfd406 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableHelperUnitTest.java @@ -59,7 +59,7 @@ public void testIterableSuccessCallback_WithRemoteSuccess() throws Exception { @Test public void testIterableSuccessCallback_WithLocalSuccess() { - IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + IterableResponseObject.Success localSuccess = new IterableResponseObject.LocalSuccess(); AtomicBoolean callbackInvoked = new AtomicBoolean(false); @@ -113,7 +113,7 @@ public void onSuccess(IterableResponseObject.RemoteSuccess data) { @Test public void testRemoteSuccessCallback_WithLocalSuccess_LogsWarning() { - IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + IterableResponseObject.Success localSuccess = new IterableResponseObject.LocalSuccess(); AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); @@ -133,7 +133,7 @@ public void onSuccess(IterableResponseObject.RemoteSuccess data) { @Test public void testLocalSuccessCallback_WithCorrectType() { - IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + IterableResponseObject.Success localSuccess = new IterableResponseObject.LocalSuccess(); AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); @@ -191,7 +191,7 @@ public void onSuccess(IterableAuthResponseObject.Success data) { @Test public void testAuthTokenCallback_WithWrongType_LogsWarning() { - IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + IterableResponseObject.Success localSuccess = new IterableResponseObject.LocalSuccess(); AtomicBoolean typedCallbackInvoked = new AtomicBoolean(false); @@ -237,7 +237,7 @@ public void onSuccess(JSONObject data) { @Test public void testSuccessHandler_WithLocalSuccess_PassesMessageJSON() throws Exception { - IterableResponseObject.Success localSuccess = IterableResponseObject.LocalSuccessResponse; + IterableResponseObject.Success localSuccess = new IterableResponseObject.LocalSuccess(); AtomicBoolean callbackInvoked = new AtomicBoolean(false); AtomicReference receivedJson = new AtomicReference<>();