github Iterable/iterable-android-sdk 3.11.0

3 hours ago

Added

  • IterableConfig.Builder.setExpiringAuthTokenRefreshPeriod(double) accepts fractional seconds, matching the iOS, React Native and Flutter SDKs. Previously Android only accepted whole seconds, so a value like 0.5 behaved differently here than on other platforms. The existing Long overload is deprecated but still works, so no code changes are required.
  • IterableDataRegion now exposes stable cross-SDK identifiers and lookup helpers: getRegionCode() (e.g. "EU"), getCode() (0 for US, 1 for EU, matching the React Native and Flutter SDKs), and the static IterableDataRegion.from(String) / IterableDataRegion.from(int) factories. from(String) accepts a region code in any case and also accepts a full API endpoint URL, so the iOS SDK's string-based data region values resolve without translation. No action required — the existing setDataRegion(IterableDataRegion.EU) API is unchanged.
  • Added IterableConfig.Builder.setInAppColorScheme() and setInAppColorSchemeProvider() to control the color scheme reported to HTML in-app messages. The provider is evaluated for each new in-app message, allowing apps that keep their theme in Jetpack Compose state to return the current LIGHT or DARK scheme.

Fixed

  • Fixed a regression introduced in 3.6.5 (SDK-717) where IterableCustomActionHandler.handleIterableCustomAction was repeatedly fired with a stale action on every app foreground and every initialize() call after the first push. The IterableCustomActionHandler interface documents its boolean return value as "Reserved for future use", causing most clients (and the React Native SDK wrapper) to return false. The SDK incorrectly treated false as "action not yet consumed" and kept replaying the pending action. The handler is now treated as consumed once invoked, regardless of return value. Retrying only happens when the handler is null (SDK not yet initialized), which is the original SDK-307 intent. Affected versions: 3.6.5–3.10.1.
  • HTML in-app messages now follow the host activity's Android light-dark configuration instead of always rendering as if the device were in dark mode. Previously the in-app container always reported prefers-color-scheme: dark to the message HTML, so a campaign with an @media (prefers-color-scheme: dark) block rendered its dark styles even in light mode. Those campaigns now render their light styles in light mode; campaigns that don't declare dark styles, and rendering in dark mode, are unaffected.
  • Fixed the keychain treating a transient crypto timeout as a permanent decryption failure. A slow AndroidKeyStore operation that exceeded the 500 ms timeout would wipe the stored email, userId, and auth token and disable encryption, forcing the user to re-authenticate (and request a new auth token) on the next launch. Crypto timeouts are now handled as transient without wiping credentials or disabling encryption for the device: a read that times out returns no value for that call (the stored ciphertext is left intact for the next attempt), and a write that times out stores that one value unencrypted (as the non-encrypted fallback already did) rather than clearing everything. The timed-out crypto operation is also cancelled so it no longer blocks subsequent reads/writes.
  • setExpiringAuthTokenRefreshPeriod now validates its input instead of silently producing a broken refresh schedule. Previously a negative value was converted to a negative millisecond period and then subtracted when computing the refresh time, scheduling the refresh after the token had already expired; a very large value overflowed to a negative period with the same effect; and null threw a NullPointerException on unboxing. Invalid values (null, NaN, negatives) are now logged and ignored, leaving the period at whatever it was before the call — the 60 second default unless an earlier call set something else. Values above ~10 years are clamped to that ceiling rather than ignored. Zero remains valid and means the token is refreshed only once it has expired.
  • An unrecognised data region no longer resolves silently. IterableDataRegion.from(...) and setDataRegion(...) log an error naming the supported values before falling back to US, so a misconfigured region surfaces in the logs instead of quietly routing EU-destined data to the US data center. setDataRegion(null) now falls back to US with an error instead of leaving the region unset. Error level is deliberate: these run while the config is still being built, before IterableApi.initialize() applies your setLogLevel(...), so anything lower would never reach logcat.
  • Fixed a NullPointerException in EmbeddedSessionManager.updateDisplayCountAndDuration() that could crash apps calling embedded session methods off the main thread. EmbeddedSessionManager is now internally synchronized, which also fixes concurrent modification of its impression map and duplicate session tracking when endSession() raced with itself. Thanks to @Shamyyoun for the report and initial fix.
  • Ending an embedded session that has no impressions now actually ends the session, matching iOS. Previously endSession() returned early and left the session marked active, so the next foreground logged "Embedded session started twice" and kept the stale start time — inflating the reported duration of the next session that did have impressions by the time the app spent in the background. Sessions without impressions still send no tracking request, and sessions with impressions are tracked exactly as before.

Changed

  • Clarified that setExpiringAuthTokenRefreshPeriod takes seconds, with a default of 60. The unit and default are unchanged and match every other Iterable SDK.

Deprecated

  • IterableConfig.Builder.setExpiringAuthTokenRefreshPeriod(Long) — use the double overload instead, which accepts fractional seconds. The Long overload delegates to it and remains fully supported.
  • IterableConstants.BASE_URL_API and IterableConstants.BASE_URL_LINKS — both are hardcoded to the US data region and are unused by the SDK, which resolves its endpoint from the configured IterableDataRegion. Use IterableDataRegion.getEndpoint() instead. They still resolve to the same values, so no action is required in this release. They will be removed in 3.12.0 — if you reference either constant, switch to IterableDataRegion.getEndpoint() before upgrading to that version.

Removed

  • Removed the encryptionEnforced field from IterableConfig. No action required. 3.5.5 announced this option as removed and deleted its public setter, but a merge reinstated the field — without the setter — in 3.6.0, where it has sat unsettable and unread ever since. There has been no way to set it and no effect on SDK behaviour since 3.5.5, so no app can be affected. Storage behaviour is unchanged: use setKeychainEncryption(boolean) to control whether stored user data is encrypted, and setDecryptionFailureHandler(...) to be notified when the SDK cannot decrypt it.

Don't miss a new iterable-android-sdk release

NewReleases is sending notifications on new releases.