Skip to content

Conversation

@joaodordio
Copy link
Member

@joaodordio joaodordio commented Dec 15, 2025

🔹 Jira Ticket(s)

✏️ Description

This PR updates the syncMessages call to pass placement IDs as a list

@joaodordio joaodordio self-assigned this Dec 15, 2025
@codecov
Copy link

codecov bot commented Dec 15, 2025

Codecov Report

❌ Patch coverage is 80.55556% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.72%. Comparing base (8ddb9b9) to head (2b3be47).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...re/Protocols/IterableEmbeddedManagerProtocol.swift 0.00% 2 Missing ⚠️
swift-sdk/Internal/api-client/ApiClient.swift 0.00% 2 Missing ⚠️
...ft-sdk/Internal/api-client/ApiClientProtocol.swift 0.00% 2 Missing ⚠️
swift-sdk/Internal/EmptyEmbeddedManager.swift 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #985      +/-   ##
==========================================
+ Coverage   69.62%   69.72%   +0.10%     
==========================================
  Files         109      111       +2     
  Lines        8954     8981      +27     
==========================================
+ Hits         6234     6262      +28     
+ Misses       2720     2719       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

placements.append(placement)
if placementIds == nil || requested.contains(placementId) {
let placement = Placement(placementId: placementId, embeddedMessages: messages)
placements.append(placement)
Copy link
Member

Choose a reason for hiding this comment

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

not quite understood this part. is it trying to add placement to list of placement once it sees it has new messages for that placement?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, pretty much. It’s not “seeing new messages per placement”—the newMessages boolean gates the whole response. Once newMessages is true, it adds a placement entry for each placementId in mockMessages that was requested (or all of them if placementIds is nil), using whatever messages are stored there.

@joaodordio joaodordio force-pushed the fix/SDK-26-add-placement-id-list-as-a-parameter-for-syncMessages branch from 81343f1 to f682deb Compare December 17, 2025 12:17
@sumeruchat
Copy link
Contributor

iOS SDK PR Review: Add Placement IDs Parameter to syncMessages

Ticket: SDK-26
Android Reference: MOB-8301
Reviewer: Code Review
Date: December 17, 2025


Summary

This PR adds an optional placementIds parameter to the syncMessages method in IterableEmbeddedManager, enabling targeted retrieval of embedded messages for specific placements. This brings iOS SDK to feature parity with Android.


What Changed

API Surface Changes

  • Added syncMessages(placementIds: [Int]?, completion:) to IterableEmbeddedManagerProtocol
  • Maintained backward compatibility via protocol extension with default nil parameter
  • Updated ApiClientProtocol.getEmbeddedMessages() to accept optional placement IDs

Implementation Details

  • Request layer passes placement IDs as comma-separated query parameter
  • Manager preserves non-requested placements during partial sync
  • Empty array and nil both fetch all placements

Test Coverage

  • Added testSyncMessagesWithPlacementIdsDoesNotClearOtherPlacements to verify placement preservation
  • Updated MockApiClient to filter placements based on request
  • Added request creator tests for both nil and populated placement ID scenarios

✅ Strengths

1. Clean Backward Compatibility

public extension IterableEmbeddedManagerProtocol {
    func syncMessages(completion: @escaping () -> Void) {
        syncMessages(placementIds: nil, completion: completion)
    }
}

Existing code continues to work without modifications—excellent API design.

2. Correct Placement Preservation Logic

if let placementIds, !placementIds.isEmpty {
    let requestedPlacementIds = Set(placementIds)
    for (placementId, currentMessages) in currentMessagesSnapshot where !requestedPlacementIds.contains(placementId) {
        fetchedMessagesDict[placementId] = currentMessages
    }
}

This ensures syncing placement 1 doesn't clear cached messages for placement 2—critical for proper behavior.

3. Thread-Safe Message Access

let currentMessagesSnapshot: [Int: [IterableEmbeddedMessage]] = self.messageProcessingQueue.sync {
    self.messages
}

Proper synchronization when reading shared state.

4. Minimal, Focused Diff

Changes are surgical—no unnecessary refactoring or scope creep. Respects rule #14.


🚨 Critical Issue: Query Parameter Format Mismatch

The Problem

iOS Implementation (this PR):

// swift-sdk/Internal/api-client/Request/RequestCreator.swift:517
args[JsonKey.Embedded.placementIds] = placementIds.map(String.init).joined(separator: ",")

Generates: ?placementIds=1,2,3

Android Implementation (MOB-8301):

// Builds multiple parameters
for (Long placementId : placementIds) {
    if (isFirst) {
        pathBuilder.append("placementIds=").append(placementId);
        isFirst = false;
    } else {
        pathBuilder.append("&placementIds=").append(placementId);
    }
}

Generates: ?placementIds=1&placementIds=2&placementIds=3

Why This Matters

These are different query string formats:

  • iOS: Single parameter with comma-separated values (custom format)
  • Android: Repeated query parameter (HTTP standard for arrays)

Most REST frameworks parse these differently. One SDK will likely fail if the backend only supports one format.

Required Action

Before merging:

  1. Verify with backend team which format the API expects
  2. Test both SDKs against the actual API endpoint
  3. Update iOS to match Android if needed (likely—repeated params are HTTP standard)

Suggested Fix (if Android format is correct)

func createGetEmbeddedMessagesRequest(placementIds: [Int]?) -> Result<IterableRequest, IterableError> {
    // ... existing auth checks ...
    
    var path = Const.Path.getEmbeddedMessages
    var args: [String: String] = [:]
    
    if let packageName = Bundle.main.appPackageName {
        args[JsonKey.Embedded.packageName] = packageName
    }
    
    setCurrentUser(inDict: &args)
    
    // Build query string manually to support repeated parameters
    if let placementIds, !placementIds.isEmpty {
        var components = URLComponents()
        components.queryItems = args.map { URLQueryItem(name: $0.key, value: $0.value) }
        placementIds.forEach { id in
            components.queryItems?.append(URLQueryItem(name: "placementIds", value: String(id)))
        }
        path += "?" + (components.query ?? "")
        return .success(.get(GetRequest(path: path, args: nil)))
    }
    
    return .success(.get(createGetRequest(forPath: path, withArgs: args)))
}

Minor Suggestions

1. Edge Case Test: Empty Array

Current test covers placementIds: [1], but consider adding:

func testSyncMessagesWithEmptyPlacementIdsArray() {
    // Verify empty array behaves same as nil (fetches all)
    manager.syncMessages(placementIds: []) { }
    // Should fetch all placements
}

2. Documentation

Consider adding inline docs to the new method:

/// Syncs embedded messages from the server
/// - Parameters:
///   - placementIds: Optional array of placement IDs to fetch. If nil or empty, fetches all placements.
///   - completion: Called when sync completes
func syncMessages(placementIds: [Int]?, completion: @escaping () -> Void)

Test Plan Verification

✅ Existing functionality preserved
✅ Placement preservation logic tested
✅ Request creation tested with and without placement IDs
⚠️ Missing: Integration test against actual API endpoint to verify query format


Recommendation

DO NOT MERGE until query parameter format is verified with backend team.

Once confirmed:

  • If iOS format is correct: APPROVE (minor suggestions optional)
  • If Android format is correct: REQUEST CHANGES to match repeated parameter format

Questions for PR Author

  1. Have you tested this against the actual Iterable API endpoint?
  2. Did backend confirm which query parameter format they expect?
  3. Is there existing iOS code that uses comma-separated query params successfully with this API?

Copy link
Contributor

@sumeruchat sumeruchat left a comment

Choose a reason for hiding this comment

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

Above is the commend by Claude. I think the critical issue is a valid concern we should match the parameter format in iOS and Android

@joaodordio
Copy link
Member Author

Above is the commend by Claude. I think the critical issue is a valid concern we should match the parameter format in iOS and Android

Good catch on the difference between the query parameters!

I'm checking with the Channels team!

@joaodordio joaodordio force-pushed the fix/SDK-26-add-placement-id-list-as-a-parameter-for-syncMessages branch from f682deb to 9ec88f8 Compare December 17, 2025 15:40
@joaodordio joaodordio force-pushed the fix/SDK-26-add-placement-id-list-as-a-parameter-for-syncMessages branch from 7508b4a to 2b3be47 Compare December 18, 2025 21:39
@joaodordio joaodordio merged commit 6ff8247 into master Dec 19, 2025
13 checks passed
@joaodordio joaodordio deleted the fix/SDK-26-add-placement-id-list-as-a-parameter-for-syncMessages branch December 19, 2025 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants