Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
"<html><body>Test</body></html>", 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
Expand Down
Loading