diff --git a/CHANGELOG.md b/CHANGELOG.md index e6255f113..632930094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Configure programmatically via `IterableInboxFragment.newInstance(...)` (new 2-arg and 6-arg overloads) or via `IterableInboxActivity` intent extras (`TOOLBAR_OPTION` / `TOOLBAR_TITLE`). - Requires the host activity to use a `Theme.AppCompat` descendant when the toolbar is enabled. +### Fixed +- Fixed a `TransactionTooLargeException` crash when displaying in-app messages with oversized HTML payloads. The HTML is no longer serialized into the fragment's saved instance state; it is reloaded from storage on recreation. In-apps with missing HTML now dismiss gracefully without registering tracking events, and a warning is logged for HTML payloads exceeding the recommended size. + ## [3.8.0] ### Added - New `IterableInAppDisplayMode` enum to control how in-app messages interact with system bars. Configure via `IterableConfig.Builder.setInAppDisplayMode()`: diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppDisplayer.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppDisplayer.java index 22060f097..03064ca19 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppDisplayer.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppDisplayer.java @@ -9,6 +9,9 @@ class IterableInAppDisplayer { + // Large HTML payloads risk a TransactionTooLargeException and other display issues. + private static final int IN_APP_HTML_SIZE_WARNING_THRESHOLD = 250 * 1024; + private final IterableActivityMonitor activityMonitor; IterableInAppDisplayer(IterableActivityMonitor activityMonitor) { @@ -34,6 +37,12 @@ boolean showMessage(@NonNull IterableInAppMessage message, IterableInAppLocation return false; } + String html = message.getContent().html; + if (html != null && html.length() > IN_APP_HTML_SIZE_WARNING_THRESHOLD) { + IterableLogger.w(IterableInAppManager.TAG, "In-App message HTML payload is " + html.length() + + " bytes, exceeding the recommended size. This may cause display issues."); + } + Activity currentActivity = activityMonitor.getCurrentActivity(); if (currentActivity != null) { // Try FragmentActivity path first (backward compatibility) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java index e88e47f1a..df8aecc75 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java @@ -41,7 +41,6 @@ public class IterableInAppFragmentHTMLNotification extends DialogFragment implements IterableWebView.HTMLNotificationCallbacks { private static final String BACK_BUTTON = "itbl://backButton"; private static final String TAG = "IterableInAppFragmentHTMLNotification"; - private static final String HTML_STRING = "HTML"; private static final String BACKGROUND_ALPHA = "BackgroundAlpha"; private static final String INSET_PADDING = "InsetPadding"; private static final String CALLBACK_ON_CANCEL = "CallbackOnCancel"; @@ -87,8 +86,12 @@ public static IterableInAppFragmentHTMLNotification createInstance(@NonNull Stri public static IterableInAppFragmentHTMLNotification createInstance(@NonNull String htmlString, boolean callbackOnCancel, @NonNull IterableHelper.IterableUrlCallback clickCallback, @NonNull IterableInAppLocation location, @NonNull String messageId, @NonNull Double backgroundAlpha, @NonNull Rect padding, @NonNull boolean shouldAnimate, IterableInAppMessage.InAppBgColor inAppBgColor) { notification = new IterableInAppFragmentHTMLNotification(); + // HTML is kept in-memory (not in the Bundle) so it isn't serialized into the + // FragmentManager's saved state, which would overflow the Binder transaction limit + // (TransactionTooLargeException) for large payloads. On process-death recreation it is + // reloaded from storage by messageId in onCreate(). + notification.htmlString = htmlString; Bundle args = new Bundle(); - args.putString(HTML_STRING, htmlString); args.putBoolean(CALLBACK_ON_CANCEL, callbackOnCancel); args.putString(MESSAGE_ID, messageId); args.putDouble(BACKGROUND_ALPHA, backgroundAlpha); @@ -140,7 +143,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { - htmlString = args.getString(HTML_STRING, null); callbackOnCancel = args.getBoolean(CALLBACK_ON_CANCEL, false); messageId = args.getString(MESSAGE_ID); backgroundAlpha = args.getDouble(BACKGROUND_ALPHA); @@ -150,6 +152,14 @@ public void onCreate(@Nullable Bundle savedInstanceState) { shouldAnimate = args.getBoolean(IN_APP_SHOULD_ANIMATE); } + // On process-death recreation the HTML is gone from memory; reload it from storage. + if (htmlString == null && messageId != null) { + IterableInAppMessage message = IterableApi.sharedInstance.getInAppManager().getMessageById(messageId); + if (message != null) { + htmlString = message.getContent().html; + } + } + notification = this; displayMode = resolveDisplayMode(); } @@ -194,6 +204,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c applyWindowGravity(getDialog().getWindow(), "onCreateView"); } + if (htmlString == null || htmlString.isEmpty()) { + IterableLogger.e(TAG, "Unable to load in-app HTML for message " + messageId + "; dismissing without tracking"); + dismissAllowingStateLoss(); + return null; + } + webView = createWebViewSafely(getContext()); if (webView == null) { dismissAllowingStateLoss(); diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java index ceaf7c586..3d24d8cb4 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableInAppHTMLNotificationTest.java @@ -19,6 +19,7 @@ import static android.os.Looper.getMainLooper; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; @@ -56,6 +57,28 @@ public void testDoNotCrashOnResizeAfterDismiss() { notification.resize(500.0f); } + // ===== Oversized HTML Payload Tests (SDK-419) ===== + + @Test + public void testHtmlNotStoredInFragmentArguments() { + Rect padding = new Rect(0, -1, 0, -1); + IterableInAppFragmentHTMLNotification notification = IterableInAppFragmentHTMLNotification.createInstance( + "Test", false, uri -> { + }, IterableInAppLocation.IN_APP, "msg1", 0.0, padding, false, new IterableInAppMessage.InAppBgColor(null, 0.0f)); + + assertNull("HTML should not be persisted in fragment arguments", notification.getArguments().getString("HTML")); + } + + @Test + public void testEmptyHtmlDismissesWithoutShowing() { + IterableInAppDisplayer.showIterableFragmentNotificationHTML(activity, "", "msg1", null, 0.0, new Rect(), true, new IterableInAppMessage.InAppBgColor(null, 0.0f), false, IterableInAppLocation.IN_APP); + shadowOf(getMainLooper()).idle(); + + // onCreateView dismisses early when HTML is empty, so the fragment is torn down and + // the static instance is cleared in onDestroy — nothing is shown. + assertNull("Notification should be dismissed when HTML is empty", IterableInAppFragmentHTMLNotification.getInstance()); + } + // ===== Resize Debouncing Tests ===== @Test