From 59bf82508eb4af06b500dbcaf34219922e394df5 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Thu, 27 Mar 2025 18:04:22 +0000 Subject: [PATCH 01/11] [MOB-9340] fix: mark recalled in-app messages as consumed before removal- Ensure in-app messages are marked as consumed before being removed from local storage to prevent re-display.- Add unit test to verify that recalled messages are correctly marked as consumed and inAppConsume is called. --- .../iterableapi/IterableInAppManager.java | 4 +++ .../IterableInAppManagerSyncTest.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index 9a8589baf..d97d6e20c 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -365,6 +365,10 @@ private void syncWithRemoteQueue(List remoteQueue) { for (IterableInAppMessage localMessage : storage.getMessages()) { if (!remoteQueueMap.containsKey(localMessage.getMessageId())) { + // Mark message as consumed before removing it to prevent it from being displayed + localMessage.setConsumed(true); + api.inAppConsume(localMessage, null, null, null, null); + storage.removeMessage(localMessage); changed = true; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java index 3dd8a191c..56a1a63e4 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java @@ -18,6 +18,8 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class IterableInAppManagerSyncTest extends BaseTest { @@ -81,4 +83,37 @@ public void testSyncOnLogin() throws Exception { verify(inAppManagerMock).syncInApp(); } + @Test + public void testRecalledMessagesAreConsumed() throws Exception { + // Create a test message in local storage + IterableInAppMessage testMessage = InAppTestUtils.getTestInboxInAppWithId("test-message-1"); + doReturn(testMessage).when(storageMock).getMessage("test-message-1"); + + // Create a storage with only this message + doReturn(new IterableInAppMessage[] { testMessage }).when(storageMock).getMessages(); + + // Setup the API to return empty message list (simulating recall) + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + IterableHelper.IterableActionHandler handler = invocation.getArgument(1); + // Return an empty message list + handler.execute("{\"" + IterableConstants.ITERABLE_IN_APP_MESSAGE + "\": []}"); + return null; + } + }).when(iterableApiMock).getInAppMessages(any(Integer.class), any(IterableHelper.IterableActionHandler.class)); + + // Verify message is not consumed initially + assertFalse(testMessage.isConsumed()); + + // Sync with remote queue + inAppManager.syncInApp(); + + // Verify that the message was marked as consumed + assertTrue(testMessage.isConsumed()); + + // Verify that inAppConsume was called + verify(iterableApiMock).inAppConsume(testMessage, null, null, null, null); + } + } From 52f775e2717dc4d43502dafc96da05dfc3c8aa2d Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Thu, 27 Mar 2025 18:16:57 +0000 Subject: [PATCH 02/11] [MOB-9340] Remove redundant in-app message consumption call before deletion --- .../java/com/iterable/iterableapi/IterableInAppManager.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index d97d6e20c..a6f990cae 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -367,8 +367,6 @@ private void syncWithRemoteQueue(List remoteQueue) { if (!remoteQueueMap.containsKey(localMessage.getMessageId())) { // Mark message as consumed before removing it to prevent it from being displayed localMessage.setConsumed(true); - api.inAppConsume(localMessage, null, null, null, null); - storage.removeMessage(localMessage); changed = true; From 4fda5df4d66892f196c1a2ff13e37be5251853c2 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Thu, 27 Mar 2025 18:21:16 +0000 Subject: [PATCH 03/11] [MOB-9340] Enhance In-App Message Handling- Add `syncInApp()` call after message removal in `IterableFirebaseMessagingService`.- Mark messages as consumed before removal in `IterableInAppManager` to prevent re-display. --- .../iterable/iterableapi/IterableFirebaseMessagingService.java | 1 + .../java/com/iterable/iterableapi/IterableInAppManager.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableFirebaseMessagingService.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableFirebaseMessagingService.java index 72df83464..54ed5aa9b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableFirebaseMessagingService.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableFirebaseMessagingService.java @@ -78,6 +78,7 @@ public static boolean handleMessageReceived(@NonNull Context context, @NonNull R String messageId = extras.getString("messageId"); if (messageId != null) { IterableApi.getInstance().getInAppManager().removeMessage(messageId); + IterableApi.getInstance().getInAppManager().syncInApp(); } break; case "UpdateEmbedded": diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index a6f990cae..a7bb34918 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -321,6 +321,8 @@ public void handleInAppClick(@NonNull IterableInAppMessage message, @Nullable Ur synchronized void removeMessage(String messageId) { IterableInAppMessage message = storage.getMessage(messageId); if (message != null) { + // Mark message as consumed before removing it to prevent it from being displayed + message.setConsumed(true); storage.removeMessage(message); } notifyOnChange(); From 1bb9810730261496dd3639479154cff0c033adc4 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 15:38:48 +0100 Subject: [PATCH 04/11] [MOB-9340] Update Gradle build scripts: migrate from jcenter to mavenCentral, upgrade Kotlin to 1.9.22, and configure JVMtoolchain to 17. Refactor javadoc task for compatibility with new Android Gradle Plugin. --- app/build.gradle | 4 ++++ build.gradle | 6 +++--- iterableapi-ui/build.gradle | 10 ++++++---- iterableapi/build.gradle | 15 +++++++++++---- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8c74f9670..e31077eb5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,10 @@ android { multiDexEnabled true } + buildFeatures { + buildConfig true + } + buildTypes { release { minifyEnabled true diff --git a/build.gradle b/build.gradle index 8af279fbb..3d1e16f3c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.9.0' + ext.kotlin_version = '1.9.22' ext.mavenPublishEnabled = true repositories { google() - jcenter() + mavenCentral() maven { url "https://plugins.gradle.org/m2/" } @@ -26,7 +26,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/iterableapi-ui/build.gradle b/iterableapi-ui/build.gradle index 28014424e..069d0348d 100644 --- a/iterableapi-ui/build.gradle +++ b/iterableapi-ui/build.gradle @@ -76,8 +76,10 @@ task javadoc(type: Javadoc) { } // A hack to import the classpath and BuildConfig into the javadoc task -afterEvaluate { - javadoc.classpath += files(android.libraryVariants.collect { variant -> variant.javaCompile.classpath.files }) - javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/r/${variant.flavorName}/${variant.buildType.name}" }) - javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}" }) +android.libraryVariants.configureEach { variant -> + javadoc.doFirst { + javadoc.classpath += files(variant.javaCompileProvider.get().classpath.files) + javadoc.classpath += files("build/generated/source/r/${variant.flavorName}/${variant.buildType.name}") + javadoc.classpath += files("build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}") + } } diff --git a/iterableapi/build.gradle b/iterableapi/build.gradle index b6a6e9d97..1720e4461 100644 --- a/iterableapi/build.gradle +++ b/iterableapi/build.gradle @@ -114,8 +114,15 @@ task checkstyle(type: Checkstyle) { } // A hack to import the classpath and BuildConfig into the javadoc task -afterEvaluate { - javadoc.classpath += files(android.libraryVariants.collect { variant -> variant.javaCompile.classpath.files }) - javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/r/${variant.flavorName}/${variant.buildType.name}" }) - javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}" }) +android.libraryVariants.configureEach { variant -> + javadoc.doFirst { + javadoc.classpath += files(variant.javaCompileProvider.get().classpath.files) + javadoc.classpath += files("build/generated/source/r/${variant.flavorName}/${variant.buildType.name}") + javadoc.classpath += files("build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}") + } +} + +// Add this kotlin block to match Java's JVM target +kotlin { + jvmToolchain(17) } \ No newline at end of file From 3d7a03afae6db3fa00cf1dde1294c5f0f9f29a62 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 15:44:17 +0100 Subject: [PATCH 05/11] [MOB-9340] Set Kotlin JVM target to 17 in build.gradle --- iterableapi/build.gradle | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/iterableapi/build.gradle b/iterableapi/build.gradle index 1720e4461..ff6dea89c 100644 --- a/iterableapi/build.gradle +++ b/iterableapi/build.gradle @@ -17,6 +17,10 @@ android { targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + defaultConfig { minSdkVersion 21 targetSdkVersion 34 @@ -120,9 +124,4 @@ android.libraryVariants.configureEach { variant -> javadoc.classpath += files("build/generated/source/r/${variant.flavorName}/${variant.buildType.name}") javadoc.classpath += files("build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}") } -} - -// Add this kotlin block to match Java's JVM target -kotlin { - jvmToolchain(17) } \ No newline at end of file From adfd8e664c785d7c4c2bbe543551482d837ca19d Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 15:52:34 +0100 Subject: [PATCH 06/11] [MOB-9340] Set Kotlin JVM target to 17 in build.gradle --- iterableapi-ui/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iterableapi-ui/build.gradle b/iterableapi-ui/build.gradle index 069d0348d..4b1cefd4f 100644 --- a/iterableapi-ui/build.gradle +++ b/iterableapi-ui/build.gradle @@ -23,6 +23,10 @@ android { targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + publishing { multipleVariants { allVariants() From d55ce263da4e67618ac0a45e9911544f58935004 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 16:04:35 +0100 Subject: [PATCH 07/11] [MOB-9340] Refactor IterableInAppManagerSyncTest: Use Arrays.asList for message retrieval- Replaced array with Arrays.asList for better readability and flexibility.- Added missing import for Arrays. --- .../iterable/iterableapi/IterableInAppManagerSyncTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java index 56a1a63e4..517030187 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java @@ -21,6 +21,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.Arrays; + public class IterableInAppManagerSyncTest extends BaseTest { @Mock @@ -90,7 +92,7 @@ public void testRecalledMessagesAreConsumed() throws Exception { doReturn(testMessage).when(storageMock).getMessage("test-message-1"); // Create a storage with only this message - doReturn(new IterableInAppMessage[] { testMessage }).when(storageMock).getMessages(); + doReturn(Arrays.asList(testMessage)).when(storageMock).getMessages(); // Setup the API to return empty message list (simulating recall) doAnswer(new Answer() { From 9615c83ed6b7948724b2173cf785a5d0d7ed4377 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 17:03:58 +0100 Subject: [PATCH 08/11] [MOB-9340] fix: consume in-app message via API before removalEnsure in-app messages are marked as consumed through the API before being removed from storage to prevent re-display. --- .../main/java/com/iterable/iterableapi/IterableInAppManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java index a7bb34918..ab18828b6 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java @@ -370,6 +370,7 @@ private void syncWithRemoteQueue(List remoteQueue) { // Mark message as consumed before removing it to prevent it from being displayed localMessage.setConsumed(true); storage.removeMessage(localMessage); + api.inAppConsume(localMessage, null, null, null, null); changed = true; } From 5eb2cb12a3fdcfc12621f1c416c9f8f16f4dcb83 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 17:20:19 +0100 Subject: [PATCH 09/11] [MOB-9340] Make classes and functions public for broader accessibility --- .../iterable/iterableapi/IterableActionRunner.java | 2 +- .../iterableapi/IterableEmbeddedManager.kt | 2 +- .../iterableapi/IterableEmbeddedPlacement.kt | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableActionRunner.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableActionRunner.java index 4476709d0..bf1f341a1 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableActionRunner.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableActionRunner.java @@ -13,7 +13,7 @@ import java.util.List; -class IterableActionRunner { +public class IterableActionRunner { @VisibleForTesting static IterableActionRunnerImpl instance = new IterableActionRunnerImpl(); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt index 8b53171bc..1d9edf97f 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedManager.kt @@ -163,7 +163,7 @@ public class IterableEmbeddedManager : IterableActivityMonitor.AppStateCallback } } - fun handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String?) { + public fun handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String?) { if ((clickedUrl != null) && clickedUrl.toString().isNotEmpty()) { if (clickedUrl.startsWith(IterableConstants.URL_SCHEME_ACTION)) { // This is an action:// URL, pass that to the custom action handler diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedPlacement.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedPlacement.kt index 4d71ff55b..a27018de5 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedPlacement.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableEmbeddedPlacement.kt @@ -46,7 +46,7 @@ data class IterableEmbeddedPlacement( } } -data class IterableEmbeddedMessage ( +public data class IterableEmbeddedMessage ( val metadata: EmbeddedMessageMetadata, val elements: EmbeddedMessageElements? = null, val payload: JSONObject? = null @@ -82,7 +82,7 @@ data class IterableEmbeddedMessage ( } } -class EmbeddedMessageMetadata( +public class EmbeddedMessageMetadata( var messageId: String, val placementId: Long, val campaignId: Int? = null, @@ -117,7 +117,7 @@ class EmbeddedMessageMetadata( } } -class EmbeddedMessageElements ( +public class EmbeddedMessageElements ( val title: String? = null, val body: String? = null, val mediaURL: String? = null, @@ -213,7 +213,7 @@ class EmbeddedMessageElements ( } } -class EmbeddedMessageElementsButton ( +public class EmbeddedMessageElementsButton ( val id: String, val title: String? = null, val action: EmbeddedMessageElementsButtonAction? = null @@ -258,7 +258,7 @@ class EmbeddedMessageElementsButton ( } } -class EmbeddedMessageElementsDefaultAction ( +public class EmbeddedMessageElementsDefaultAction ( val type: String, val data: String ) { @@ -287,7 +287,7 @@ class EmbeddedMessageElementsDefaultAction ( } } -class EmbeddedMessageElementsButtonAction ( +public class EmbeddedMessageElementsButtonAction ( val type: String, val data: String ) { @@ -315,7 +315,7 @@ class EmbeddedMessageElementsButtonAction ( } } } -class EmbeddedMessageElementsText ( +public class EmbeddedMessageElementsText ( val id: String, val text: String? = null, val label: String? = null From ecaba46d5b54a2935239853f31714d64695247cc Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Mon, 31 Mar 2025 17:23:48 +0100 Subject: [PATCH 10/11] [MOB-9340] Remove unused testNamespace from build.gradle --- iterableapi/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/iterableapi/build.gradle b/iterableapi/build.gradle index ff6dea89c..64965cc5b 100644 --- a/iterableapi/build.gradle +++ b/iterableapi/build.gradle @@ -6,7 +6,6 @@ android { compileSdk 34 namespace 'com.iterable.iterableapi' - testNamespace 'iterable.com.iterableapi' buildFeatures { buildConfig = true From 51a21cb303ef984bbb7503a41f797548c673ca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAkshay?= <“ayyanchira.akshay@gmail.com”> Date: Fri, 4 Apr 2025 08:49:47 -0700 Subject: [PATCH 11/11] Cosmetic corrections suggested by checkstyle --- .../iterableapi/IterableInAppManagerSyncTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java index 517030187..54127c041 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppManagerSyncTest.java @@ -90,10 +90,10 @@ public void testRecalledMessagesAreConsumed() throws Exception { // Create a test message in local storage IterableInAppMessage testMessage = InAppTestUtils.getTestInboxInAppWithId("test-message-1"); doReturn(testMessage).when(storageMock).getMessage("test-message-1"); - + // Create a storage with only this message doReturn(Arrays.asList(testMessage)).when(storageMock).getMessages(); - + // Setup the API to return empty message list (simulating recall) doAnswer(new Answer() { @Override @@ -104,18 +104,18 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return null; } }).when(iterableApiMock).getInAppMessages(any(Integer.class), any(IterableHelper.IterableActionHandler.class)); - + // Verify message is not consumed initially assertFalse(testMessage.isConsumed()); - + // Sync with remote queue inAppManager.syncInApp(); - + // Verify that the message was marked as consumed assertTrue(testMessage.isConsumed()); - + // Verify that inAppConsume was called verify(iterableApiMock).inAppConsume(testMessage, null, null, null, null); } -} +} \ No newline at end of file