Skip to content

Commit e9890d3

Browse files
Optimize: add APIs for displayed and generateDisplayInteractionXdm for multiple offers (adobe#491)
* add support for multiple display proposition in Optimize * android api call fix * update api usage, tests, docs * fix UTs * add support for generate display interaction xdm for multiple offers * preserve proposition weak reference * address PR comments * add constants in android
1 parent 805e856 commit e9890d3

File tree

10 files changed

+425
-3
lines changed

10 files changed

+425
-3
lines changed

apps/AEPSampleAppNewArchEnabled/app/OptimizeView.tsx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ export default () => {
8484

8585
const updatePropositions = () => {
8686
Optimize.updatePropositions(decisionScopes);
87-
console.log('Updated Propositions');
87+
console.log('Updated Proposition for decisionScopes:', decisionScopes);
8888
};
8989

9090
const getPropositions = async () => {
9191
const propositions: Map<string, Proposition> =
9292
await Optimize.getPropositions(decisionScopes);
93-
console.log(propositions);
9493
if (propositions) {
94+
console.log("get propositions", JSON.stringify(Object.fromEntries(propositions), null, 2));
9595
setTextProposition(propositions.get(decisionScopeText.getName()));
9696
setImageProposition(propositions.get(decisionScopeImage.getName()));
9797
setHtmlProposition(propositions.get(decisionScopeHtml.getName()));
@@ -118,6 +118,54 @@ export default () => {
118118
},
119119
});
120120

121+
const multipleOffersDisplayed = async () => {
122+
const propositionsMap: Map<string, Proposition> = await Optimize.getPropositions(decisionScopes);
123+
const offerPairs: Array<{proposition: Proposition, offerId: string}> = [];
124+
125+
propositionsMap.forEach((proposition: Proposition) => {
126+
if (proposition && proposition.items) {
127+
proposition.items.forEach((offer) => {
128+
offerPairs.push({
129+
proposition: proposition,
130+
offerId: offer.id
131+
});
132+
});
133+
}
134+
});
135+
136+
console.log('Extracted offer pairs:', offerPairs);
137+
138+
if (offerPairs.length > 0) {
139+
Optimize.displayed(offerPairs);
140+
console.log(`Called multipleOffersDisplayed with ${offerPairs.length} offers`);
141+
} else {
142+
console.log('No offers found to display');
143+
}
144+
};
145+
146+
const generateDisplayInteractionXdmForMultipleOffers = async () => {
147+
const propositionsMap: Map<string, Proposition> = await Optimize.getPropositions(decisionScopes);
148+
const offerPairs: Array<{proposition: Proposition, offerId: string}> = [];
149+
150+
propositionsMap.forEach((proposition: Proposition) => {
151+
if (proposition && proposition.items) {
152+
proposition.items.forEach((offer) => {
153+
offerPairs.push({
154+
proposition: proposition,
155+
offerId: offer.id
156+
});
157+
});
158+
}
159+
});
160+
161+
const xdm = await Optimize.generateDisplayInteractionXdm(offerPairs);
162+
if (xdm) {
163+
console.log('Generated Display Interaction XDM for Multiple Offers:', JSON.stringify(xdm, null, 2));
164+
} else {
165+
console.log('Error in generating Display interaction XDM for multiple offers.');
166+
}
167+
};
168+
121169
const renderTargetOffer = () => {
122170
if (targetProposition?.items) {
123171
if (targetProposition.items[0].format === TARGET_OFFER_TYPE_JSON) {
@@ -338,6 +386,18 @@ export default () => {
338386
onPress={clearCachedProposition}
339387
/>
340388
</View>
389+
<View style={{margin: 5}}>
390+
<Button
391+
title="Multiple Offers Displayed"
392+
onPress={multipleOffersDisplayed}
393+
/>
394+
</View>
395+
<View style={{margin: 5}}>
396+
<Button
397+
title="Generate Display Interaction XDM for Multiple Offers"
398+
onPress={generateDisplayInteractionXdmForMultipleOffers}
399+
/>
400+
</View>
341401
<View style={{margin: 5}}>
342402
<Button
343403
title="Subscribe to Proposition Update"

packages/optimize/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,68 @@ const decisionScopes = [
163163
Optimize.updatePropositions(decisionScopes, null, null);
164164
```
165165

166+
### Batching multiple display propositions track events:
167+
168+
The Optimize SDK now provides enhanced support for batching multiple display propositions track events. The following APIs are available:
169+
170+
#### displayed
171+
172+
**Syntax**
173+
174+
```typescript
175+
displayed(offerPairs: Array<{ proposition: Proposition; offerId: string }>)
176+
```
177+
178+
**Example**
179+
180+
```typescript
181+
const propositions: Map<string, Proposition> = await Optimize.getPropositions(decisionScopes);
182+
const offerPairs: Array<{ proposition: Proposition; offerId: string }> = [];
183+
184+
propositions.forEach((proposition: Proposition) => {
185+
if (proposition && proposition.items) {
186+
proposition.items.forEach((offer) => {
187+
offerPairs.push({
188+
proposition,
189+
offerId: offer.id,
190+
});
191+
});
192+
}
193+
});
194+
195+
Optimize.displayed(offerPairs);
196+
```
197+
198+
#### generateDisplayInteractionXdm
199+
200+
**Syntax**
201+
202+
```typescript
203+
generateDisplayInteractionXdm(offerPairs: Array<{ proposition: Proposition; offerId: string }>): Promise<Map<string, any>>
204+
```
205+
206+
**Example**
207+
208+
```typescript
209+
const propositions: Map<string, Proposition> = await Optimize.getPropositions(decisionScopes);
210+
const offerPairs: Array<{ proposition: Proposition; offerId: string }> = [];
211+
212+
propositions.forEach((proposition: Proposition) => {
213+
if (proposition && proposition.items) {
214+
proposition.items.forEach((offer) => {
215+
offerPairs.push({
216+
proposition,
217+
offerId: offer.id,
218+
});
219+
});
220+
}
221+
});
222+
223+
const xdmData = await Optimize.generateDisplayInteractionXdm(offerPairs);
224+
// use xdmData as needed
225+
226+
```
227+
166228
---
167229

168230
## Public classes

packages/optimize/__tests__/OptimizeTests.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,50 @@ describe('Optimize', () => {
174174
'eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEiLCJpdGVtQ291bnQiOjEwfQ=='
175175
);
176176
});
177+
178+
it('Test Optimize.displayed', async () => {
179+
const spy = jest.spyOn(NativeModules.AEPOptimize, 'multipleOffersDisplayed');
180+
const proposition = new Proposition(propositionJson as any);
181+
const offer = proposition.items[0];
182+
const offerPairs = [{
183+
proposition,
184+
offerId: offer.id
185+
}];
186+
187+
// Clean the proposition as your implementation does
188+
const entries = Object.entries(proposition).filter(
189+
([_, value]) => typeof value !== 'function'
190+
);
191+
const cleanedProposition = Object.fromEntries(entries);
192+
const cleanedPairs = [{
193+
proposition: cleanedProposition,
194+
offerId: offer.id
195+
}];
196+
197+
await Optimize.displayed(offerPairs);
198+
expect(spy).toHaveBeenCalledWith(cleanedPairs);
199+
});
200+
201+
it('Test Optimize.generateDisplayInteractionXdm', async () => {
202+
const spy = jest.spyOn(NativeModules.AEPOptimize, 'generateDisplayInteractionXdmForMultipleOffers');
203+
const proposition = new Proposition(propositionJson as any);
204+
const offer = proposition.items[0];
205+
const offerPairs = [{
206+
proposition,
207+
offerId: offer.id
208+
}];
209+
210+
// Clean the proposition as your implementation does
211+
const entries = Object.entries(proposition).filter(
212+
([_, value]) => typeof value !== 'function'
213+
);
214+
const cleanedProposition = Object.fromEntries(entries);
215+
const cleanedPairs = [{
216+
proposition: cleanedProposition,
217+
offerId: offer.id
218+
}];
219+
220+
await Optimize.generateDisplayInteractionXdm(offerPairs);
221+
expect(spy).toHaveBeenCalledWith(cleanedPairs);
222+
});
177223
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
Copyright 2025 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
package com.adobe.marketing.mobile.reactnative.optimize;
13+
14+
class RCTAEPOptimizeConstants {
15+
static final String PROPOSITION_KEY = "proposition";
16+
static final String OFFER_ID_KEY = "offerId";
17+
}

packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizeModule.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.adobe.marketing.mobile.optimize.DecisionScope;
1919
import com.adobe.marketing.mobile.optimize.Offer;
2020
import com.adobe.marketing.mobile.optimize.OfferType;
21+
import com.adobe.marketing.mobile.optimize.OfferUtils;
2122
import com.adobe.marketing.mobile.optimize.Optimize;
2223
import com.adobe.marketing.mobile.optimize.OptimizeProposition;
2324
import com.facebook.react.bridge.Promise;
@@ -30,6 +31,7 @@
3031
import com.facebook.react.bridge.WritableNativeMap;
3132
import com.facebook.react.modules.core.DeviceEventManagerModule;
3233

34+
import java.util.ArrayList;
3335
import java.util.Collections;
3436
import java.util.List;
3537
import java.util.Map;
@@ -61,6 +63,43 @@ public void offerDisplayed(final String offerId, final ReadableMap propositionMa
6163
}
6264
}
6365

66+
@ReactMethod
67+
public void multipleOffersDisplayed(final ReadableArray propositionOfferPairs) {
68+
if (propositionOfferPairs == null || propositionOfferPairs.size() == 0) {
69+
return;
70+
}
71+
72+
List<Offer> nativeOffers = new ArrayList<>();
73+
74+
for (int i = 0; i < propositionOfferPairs.size(); i++) {
75+
ReadableMap propositionOfferPairMap = propositionOfferPairs.getMap(i);
76+
if (propositionOfferPairMap == null) {
77+
continue;
78+
}
79+
80+
ReadableMap propositionMap = propositionOfferPairMap.getMap(RCTAEPOptimizeConstants.PROPOSITION_KEY);
81+
String offerId = propositionOfferPairMap.getString(RCTAEPOptimizeConstants.OFFER_ID_KEY);
82+
if (propositionMap == null || offerId == null) {
83+
continue;
84+
}
85+
86+
Map<String, Object> propositionEventData = RCTAEPOptimizeUtil.convertReadableMapToMap(propositionMap);
87+
OptimizeProposition proposition = OptimizeProposition.fromEventData(propositionEventData);
88+
if (proposition != null) {
89+
for (Offer offer : proposition.getOffers()) {
90+
if (offer.getId().equalsIgnoreCase(offerId)) {
91+
nativeOffers.add(offer);
92+
break;
93+
}
94+
}
95+
}
96+
}
97+
98+
if (!nativeOffers.isEmpty()) {
99+
OfferUtils.displayed(nativeOffers);
100+
}
101+
}
102+
64103
@ReactMethod
65104
public void extensionVersion(final Promise promise) {
66105
promise.resolve(Optimize.extensionVersion());
@@ -144,6 +183,42 @@ public void generateDisplayInteractionXdm(final String offerId, final ReadableMa
144183
}
145184
}
146185

186+
@ReactMethod
187+
public void generateDisplayInteractionXdmForMultipleOffers(final ReadableArray propositionOfferPairs, final Promise promise) {
188+
final List<Offer> nativeOffers = new ArrayList<>();
189+
190+
for (int i = 0; i < propositionOfferPairs.size(); i++) {
191+
ReadableMap propositionOfferPairMap = propositionOfferPairs.getMap(i);
192+
if (propositionOfferPairMap == null) {
193+
continue;
194+
}
195+
196+
ReadableMap propositionMap = propositionOfferPairMap.getMap(RCTAEPOptimizeConstants.PROPOSITION_KEY);
197+
String offerId = propositionOfferPairMap.getString(RCTAEPOptimizeConstants.OFFER_ID_KEY);
198+
if (propositionMap == null || offerId == null) {
199+
continue;
200+
}
201+
202+
Map<String, Object> propositionEventData = RCTAEPOptimizeUtil.convertReadableMapToMap(propositionMap);
203+
OptimizeProposition proposition = OptimizeProposition.fromEventData(propositionEventData);
204+
205+
for (Offer offer : proposition.getOffers()) {
206+
if (offer.getId().equalsIgnoreCase(offerId)) {
207+
nativeOffers.add(offer);
208+
break;
209+
}
210+
}
211+
}
212+
213+
if (nativeOffers.size() > 0) {
214+
final Map<String, Object> interactionXdm = OfferUtils.generateDisplayInteractionXdm(nativeOffers);
215+
final WritableMap writableMap = RCTAEPOptimizeUtil.convertMapToWritableMap(interactionXdm);
216+
promise.resolve(writableMap);
217+
} else {
218+
promise.reject("generateDisplayInteractionXdmForMultipleOffers", "Error in generating Display interaction XDM for multiple offers.");
219+
}
220+
}
221+
147222
@ReactMethod
148223
public void generateTapInteractionXdm(final String offerId, final ReadableMap propositionMap, final Promise promise) {
149224
final Map<String, Object> eventData = RCTAEPOptimizeUtil.convertReadableMapToMap(propositionMap);

0 commit comments

Comments
 (0)