Skip to content

Conversation

@lposen
Copy link
Contributor

@lposen lposen commented Oct 13, 2025

🔹 JIRA Ticket(s) if any

✏️ Description

Adds the ability to sync and get messages to android

Testing

  1. cd into the example dir and run:
    yarn install
    watchman watch-del-all
    yarn start --reset-cache
  2. In a separate terminal, cd into example then either run yarn android
  3. Click on the "Embedded" tab.
  4. Click "Get messages"
  5. Params of your embedded messages should display in the UI
Screenshot_1763537044

@lposen lposen changed the base branch from loren/embedded/MOB-12263-android-add-ability-to-sync-embedded-messages to loren/embedded/MOB-12265-start-end-session October 13, 2025 19:36
@github-actions
Copy link

github-actions bot commented Oct 13, 2025

Lines Statements Branches Functions
Coverage: 59%
59.86% (349/583) 33.18% (76/229) 58.98% (128/217)

@qltysh
Copy link

qltysh bot commented Oct 13, 2025

Diff Coverage: The code coverage on the diff in this pull request is 100.0%.

Total Coverage: This PR will increase coverage by 0.43%.

File Coverage Changes
Path File Coverage Δ Indirect
src/embedded/types/IterableEmbeddedMessage.ts 100.0
src/embedded/types/IterableEmbeddedMessageElements.ts 100.0
src/embedded/types/IterableEmbeddedMessageElementsButton.ts 100.0
src/embedded/types/IterableEmbeddedMessageElementsText.ts 100.0
src/embedded/types/IterableEmbeddedMessageMetadata.ts 100.0
src/embedded/types/index.ts 100.0
🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

This is from Qlty Cloud, the successor to Code Climate Quality. Learn more.

@qltysh
Copy link

qltysh bot commented Oct 13, 2025

2 new issues

Tool Category Rule Count
qlty Structure High total complexity (count = 65) 1
qlty Structure Function with high complexity (count = 11): getEmbeddedMessages 1

This is from Qlty Cloud, the successor to Code Climate Quality. Learn more.

@lposen lposen changed the title feat: add session management methods to IterableEmbeddedManager [MOB-12264] Add sync messages and get messages to android Oct 13, 2025
@lposen lposen added the embedded Issues/PRs related to Embedded Messages label Oct 14, 2025
…embedded/MOB-12264-android-sync-and-get-messages
@lposen lposen marked this pull request as ready for review November 19, 2025 07:35
@lposen lposen requested a review from Copilot November 19, 2025 07:36
Copilot finished reviewing on behalf of lposen November 19, 2025 07:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for syncing and retrieving embedded messages in the Android SDK, bringing feature parity between iOS and Android platforms for embedded messaging functionality.

Key Changes:

  • Adds syncMessages() method to manually refresh the local cache of embedded messages
  • Adds getMessages(placementIds) method to retrieve embedded messages for specific placements
  • Exports TypeScript type definitions for embedded message structures

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/index.tsx Exports IterableEmbeddedMessage type for public API
src/embedded/types/*.ts New TypeScript interfaces defining the structure of embedded messages, metadata, elements, buttons, and text
src/embedded/index.ts Exports embedded message types
src/embedded/classes/IterableEmbeddedManager.ts Adds syncMessages() and getMessages() methods to the manager class
src/core/classes/IterableApi.ts Adds wrapper methods for native embedded message operations
src/api/NativeRNIterableAPI.ts Defines native bridge interface including EmbeddedMessage type and method signatures
android/src/oldarch/java/com/RNIterableAPIModule.java Adds React method bindings for old architecture
android/src/newarch/java/com/RNIterableAPIModule.java Adds React method bindings for new architecture
android/src/main/java/com/iterable/reactnative/Serialization.java Adds serialization logic for embedded messages using native SDK's toJSONObject()
android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java Implements sync and get messages logic, iterating through placement IDs
example/src/components/Embedded/Embedded.tsx Demo implementation showing how to sync and retrieve embedded messages in the example app

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* Syncs embedded local cache with the server.
*
* When your app first launches, and each time it comes to the foreground,
* Iterable's iOS SDK automatically refresh a local, on-device cache of
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation incorrectly refers to "iOS SDK" in a comment about syncing messages. Since this PR is adding Android support, the comment should mention both iOS and Android SDKs, or be platform-agnostic.

Suggested fix:

/**
 * Syncs embedded local cache with the server.
 *
 * When your app first launches, and each time it comes to the foreground,
 * Iterable's SDK automatically refreshes a local, on-device cache of
 * embedded messages for the signed-in user. These are the messages the
 * signed-in user is eligible to see.
 *
 * At key points during your app's lifecycle, you may want to manually refresh
 * your app's local cache of embedded messages. For example, as users navigate
 * around, on pull-to-refresh, etc.
 *
 * However, do not poll for new embedded messages at a regular interval.
 *
 * @example
 * ```typescript
 * IterableEmbeddedManager.syncMessages();
 * ```
 */
Suggested change
* Iterable's iOS SDK automatically refresh a local, on-device cache of
* Iterable's SDK automatically refreshes a local, on-device cache of

Copilot uses AI. Check for mistakes.
Comment on lines +685 to +690
// If no placement IDs provided, we need to get messages for all possible placements
// Since the Android SDK requires a placement ID, we'll use 0 as a default
// This might need to be adjusted based on the actual SDK behavior
List<IterableEmbeddedMessage> messages = IterableApi.getInstance().getEmbeddedManager().getMessages(0L);
if (messages != null) {
allMessages.addAll(messages);
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 0 as a default placement ID when none are provided is questionable and may not work as expected. The comment acknowledges this with "This might need to be adjusted based on the actual SDK behavior."

This should either:

  1. Return an empty list when no placement IDs are provided (if that's the expected behavior)
  2. Fetch all messages across all known placements by iterating through getPlacementIds()
  3. Be documented clearly if 0 is a valid special value in the Iterable Android SDK

Consider implementing option 2:

if (placementIds == null || placementIds.size() == 0) {
    List<Long> allPlacementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds();
    if (allPlacementIds != null) {
        for (Long placementId : allPlacementIds) {
            List<IterableEmbeddedMessage> messages = IterableApi.getInstance().getEmbeddedManager().getMessages(placementId);
            if (messages != null) {
                allMessages.addAll(messages);
            }
        }
    }
}
Suggested change
// If no placement IDs provided, we need to get messages for all possible placements
// Since the Android SDK requires a placement ID, we'll use 0 as a default
// This might need to be adjusted based on the actual SDK behavior
List<IterableEmbeddedMessage> messages = IterableApi.getInstance().getEmbeddedManager().getMessages(0L);
if (messages != null) {
allMessages.addAll(messages);
// If no placement IDs provided, get messages for all known placements
List<Long> allPlacementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds();
if (allPlacementIds != null) {
for (Long placementId : allPlacementIds) {
List<IterableEmbeddedMessage> messages = IterableApi.getInstance().getEmbeddedManager().getMessages(placementId);
if (messages != null) {
allMessages.addAll(messages);
}
}

Copilot uses AI. Check for mistakes.
.then((ids: number[]) => Iterable.embeddedManager.getMessages(ids))
.then((messages: IterableEmbeddedMessage[]) => {
setEmbeddedMessages(messages);
console.log(messages);
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing error handling for the promise chain. If any of the promises reject (e.g., network error, SDK error), the error will be unhandled and could cause issues in the example app. Consider adding a .catch() block:

const getEmbeddedMessages = useCallback(() => {
  getPlacementIds()
    .then((ids: number[]) => Iterable.embeddedManager.getMessages(ids))
    .then((messages: IterableEmbeddedMessage[]) => {
      setEmbeddedMessages(messages);
      console.log(messages);
    })
    .catch((error) => {
      console.error('Failed to get embedded messages:', error);
    });
}, [getPlacementIds]);
Suggested change
console.log(messages);
console.log(messages);
})
.catch((error) => {
console.error('Failed to get embedded messages:', error);

Copilot uses AI. Check for mistakes.
text?: { id: string; text?: string | null; label?: string | null }[] | null;
title?: string | null;
} | null;
payload?: { [key: string]: string | number | boolean | null } | null;
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: The payload property in the EmbeddedMessage interface is defined as { [key: string]: string | number | boolean | null }, but in IterableEmbeddedMessage it's defined as Record<string, unknown>.

The native interface is more restrictive (only allows primitive types and null), while the TypeScript interface is more permissive (allows any value including nested objects/arrays). This could cause type safety issues.

Consider either:

  1. Aligning the types to match (likely the native version is correct based on what the native SDKs actually return)
  2. Adding a type guard or runtime validation when converting from native to TypeScript types

Suggested fix in IterableEmbeddedMessage.ts:

payload?: Record<string, string | number | boolean | null> | null;

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +76
* @param placementIds - The placement IDs to retrieve messages for.
* @returns A Promise that resolves to an array of embedded messages.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation for getMessages() doesn't explain the behavior when placementIds is null or an empty array. Based on the Android implementation, this is an important detail to document since the behavior is unclear (currently uses placement ID 0 as a default, which may not be correct).

Suggested documentation improvement:

/**
 * Retrieves a list of embedded messages the user is eligible to see.
 *
 * @param placementIds - The placement IDs to retrieve messages for. 
 *                       Pass null or an empty array to get messages for all placements.
 * @returns A Promise that resolves to an array of embedded messages.
 * @example
 * ```typescript
 * // Get messages for specific placements
 * const messages = await Iterable.embeddedManager.getMessages([123, 456]);
 * 
 * // Get messages for all placements
 * const allMessages = await Iterable.embeddedManager.getMessages(null);
 * ```
 */
Suggested change
* @param placementIds - The placement IDs to retrieve messages for.
* @returns A Promise that resolves to an array of embedded messages.
* @param placementIds - The placement IDs to retrieve messages for.
* Pass `null` or an empty array to get messages for all placements.
* @returns A Promise that resolves to an array of embedded messages.
* @example
* ```typescript
* // Get messages for specific placements
* const messages = await Iterable.embeddedManager.getMessages([123, 456]);
*
* // Get messages for all placements
* const allMessages = await Iterable.embeddedManager.getMessages(null);
* ```

Copilot uses AI. Check for mistakes.
} catch (JSONException e) {
IterableLogger.e(TAG, e.getLocalizedMessage());
promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 11): getEmbeddedMessages [qlty:function-complexity]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

embedded Issues/PRs related to Embedded Messages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants