@adobe/react-native-aepmessaging 6.0.5 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,79 +33,13 @@ yarn add @adobe/react-native-aepmessaging
33
33
 
34
34
  ## Usage
35
35
 
36
- ### [Messaging](https://developer.adobe.com/client-sdks/documentation/adobe-journey-optimizer)
36
+ ### Initializing with SDK:
37
37
 
38
- ### Installing and registering the extension with the AEP Mobile Core
38
+ To initialize the SDK, use the following methods:
39
+ - [MobileCore.initializeWithAppId(appId)](https://github.com/adobe/aepsdk-react-native/tree/main/packages/core#initializewithappid)
40
+ - [MobileCore.initialize(initOptions)](https://github.com/adobe/aepsdk-react-native/tree/main/packages/core#initialize)
39
41
 
40
- ### Initialization
41
-
42
- Initializing the SDK should be done in native code, additional documentation on how to initialize the SDK can be found [here](https://github.com/adobe/aepsdk-react-native#initializing).
43
-
44
- Example:
45
-
46
- iOS
47
-
48
- ```objectivec
49
- @import AEPCore;
50
- @import AEPLifecycle;
51
- @import AEPEdge;
52
- @import AEPEdgeIdentity;
53
- @import AEPMessaging;
54
-
55
- ...
56
- @implementation AppDelegate
57
- -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
58
- [AEPMobileCore setLogLevel: AEPLogLevelDebug];
59
- [AEPMobileCore configureWithAppId:@"yourAppID"];
60
-
61
- const UIApplicationState appState = application.applicationState;
62
-
63
- [AEPMobileCore registerExtensions: @[AEPMobileEdgeIdentity.class, AEPMobileEdge.class, AEPMobileMessaging.class, AEPMobileOptimize.class] completion:^{
64
- if (appState != UIApplicationStateBackground) {
65
- [AEPMobileCore lifecycleStart:nil}];
66
- }
67
- }];
68
- return YES;
69
- }
70
-
71
- @end
72
- ```
73
-
74
- Android
75
-
76
- ```java
77
- import com.adobe.marketing.mobile.AdobeCallback;
78
- import com.adobe.marketing.mobile.InvalidInitException;
79
- import com.adobe.marketing.mobile.Lifecycle;
80
- import com.adobe.marketing.mobile.LoggingMode;
81
- import com.adobe.marketing.mobile.MobileCore;
82
- import com.adobe.marketing.mobile.Edge;
83
- import com.adobe.marketing.mobile.edge.identity.Identity;
84
- import com.adobe.marketing.mobile.Messaging;
85
-
86
- ...
87
- import android.app.Application;
88
- ...
89
- public class MainApplication extends Application implements ReactApplication {
90
- ...
91
- @Override
92
- public void on Create(){
93
- super.onCreate();
94
- ...
95
- MobileCore.setApplication(this);
96
- MobileCore.setLogLevel(LoggingMode.DEBUG);
97
- MobileCore.configureWithAppID("yourAppID");
98
- List<Class<? extends Extension>> extensions = Arrays.asList(
99
- Edge.EXTENSION,
100
- Identity.EXTENSION,
101
- Messaging.EXTENSION,
102
- Lifecycle.EXTENSION);
103
- MobileCore.registerExtensions(extensions, o -> {
104
- MobileCore.lifecycleStart(null);
105
- });
106
- }
107
- }
108
- ```
42
+ Refer to the root [Readme](https://github.com/adobe/aepsdk-react-native/blob/main/README.md) for more information about the SDK setup.
109
43
 
110
44
  ### Importing the extension:
111
45
 
@@ -372,23 +306,6 @@ var message: Message;
372
306
  message.track('sample text', MessagingEdgeEventType.IN_APP_DISMISS);
373
307
  ```
374
308
 
375
- ### handleJavascriptMessage
376
-
377
- Adds a handler for Javascript messages sent from the message's webview.
378
-
379
- **Syntax**
380
-
381
- ```javascript
382
- handleJavascriptMessage(name: string) : Promise<?any>
383
- ```
384
-
385
- **Example**
386
-
387
- ```javascript
388
- var message: Message;
389
- message.handleJavascriptMessage('test').then((data) => {});
390
- ```
391
-
392
309
  ### setAutoTrack
393
310
 
394
311
  Enables/Disables autotracking for message events.
@@ -517,3 +434,29 @@ function otherWorkflowFinished() {
517
434
  currentMessage.clearMessage();
518
435
  }
519
436
  ```
437
+
438
+
439
+ ## Tracking interactions with content cards
440
+
441
+ ### trackContentCardDisplay
442
+
443
+ Tracks a Display interaction with the given ContentCard
444
+
445
+ **Syntax**
446
+ ```javascript
447
+ Messaging.trackContentCardDisplay(proposition, contentCard);
448
+ ```
449
+
450
+ ### trackContentCardInteraction
451
+
452
+ Tracks a Click interaction with the given ContentCard
453
+
454
+ **Syntax**
455
+ ```javascript
456
+ Messaging.trackContentCardInteraction(proposition, contentCard);
457
+ ```
458
+
459
+
460
+ ## Tutorials
461
+ [Content Cards](./tutorials/ContentCards.md)
462
+
@@ -80,6 +80,7 @@ dependencies {
80
80
  //noinspection GradleDynamicVersion
81
81
  implementation "com.facebook.react:react-native:+"
82
82
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
83
- api "com.adobe.marketing.mobile:messaging:3.+"
84
- implementation "com.facebook.react:react-native:+"
83
+ implementation platform("com.adobe.marketing.mobile:sdk-bom:3.+")
84
+ api "com.adobe.marketing.mobile:messaging"
85
+
85
86
  }
@@ -27,6 +27,7 @@ import com.adobe.marketing.mobile.MessagingEdgeEventType;
27
27
  import com.adobe.marketing.mobile.MobileCore;
28
28
  import com.adobe.marketing.mobile.messaging.MessagingUtils;
29
29
  import com.adobe.marketing.mobile.messaging.Proposition;
30
+ import com.adobe.marketing.mobile.messaging.PropositionItem;
30
31
  import com.adobe.marketing.mobile.messaging.Surface;
31
32
  import com.adobe.marketing.mobile.services.ServiceProvider;
32
33
  import com.adobe.marketing.mobile.services.ui.InAppMessage;
@@ -38,6 +39,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
38
39
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
39
40
  import com.facebook.react.bridge.ReactMethod;
40
41
  import com.facebook.react.bridge.ReadableArray;
42
+ import com.facebook.react.bridge.ReadableMap;
41
43
  import com.facebook.react.bridge.WritableMap;
42
44
  import com.facebook.react.modules.core.DeviceEventManagerModule;
43
45
  import java.util.HashMap;
@@ -269,4 +271,28 @@ public final class RCTAEPMessagingModule
269
271
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
270
272
  .emit(name, eventData);
271
273
  }
272
- }
274
+
275
+ @ReactMethod
276
+ public void trackContentCardDisplay(ReadableMap propositionMap, ReadableMap contentCardMap) {
277
+ final Map<String, Object> eventData = RCTAEPMessagingUtil.convertReadableMapToMap(propositionMap);
278
+ final Proposition proposition = Proposition.fromEventData(eventData);
279
+ for (PropositionItem item : proposition.getItems()) {
280
+ if (item.getItemId().equals(contentCardMap.getString("id"))) {
281
+ item.track(MessagingEdgeEventType.DISPLAY);
282
+ break;
283
+ }
284
+ }
285
+ }
286
+
287
+ @ReactMethod
288
+ public void trackContentCardInteraction(ReadableMap propositionMap, ReadableMap contentCardMap) {
289
+ final Map<String, Object> eventData = RCTAEPMessagingUtil.convertReadableMapToMap(propositionMap);
290
+ final Proposition proposition = Proposition.fromEventData(eventData);
291
+ for (PropositionItem item : proposition.getItems()) {
292
+ if (item.getItemId().equals(contentCardMap.getString("id"))) {
293
+ item.track("click", MessagingEdgeEventType.INTERACT, null);
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ }
@@ -19,6 +19,8 @@
19
19
  import com.facebook.react.bridge.Arguments;
20
20
  import com.facebook.react.bridge.ReadableArray;
21
21
  import com.facebook.react.bridge.ReadableMap;
22
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
23
+ import com.facebook.react.bridge.ReadableType;
22
24
  import com.facebook.react.bridge.WritableArray;
23
25
  import com.facebook.react.bridge.WritableMap;
24
26
  import com.facebook.react.bridge.WritableNativeArray;
@@ -216,4 +218,66 @@
216
218
  }
217
219
  return writableMap;
218
220
  }
219
- }
221
+
222
+ /**
223
+ * Converts {@link ReadableMap} Map to {@link Map}
224
+ *
225
+ * @param readableMap instance of {@code ReadableMap}
226
+ * @return instance of {@code Map}
227
+ */
228
+ static Map<String, Object> convertReadableMapToMap(final ReadableMap readableMap) {
229
+ ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
230
+ Map<String, Object> map = new HashMap<>();
231
+ while (iterator.hasNextKey()) {
232
+ String key = iterator.nextKey();
233
+ ReadableType type = readableMap.getType(key);
234
+ switch (type) {
235
+ case Boolean:
236
+ map.put(key, readableMap.getBoolean(key));
237
+ break;
238
+ case Number:
239
+ map.put(key, readableMap.getDouble(key));
240
+ break;
241
+ case String:
242
+ map.put(key, readableMap.getString(key));
243
+ break;
244
+ case Map:
245
+ map.put(key, convertReadableMapToMap(readableMap.getMap(key)));
246
+ break;
247
+ case Array:
248
+ map.put(key, convertReadableArrayToList(readableMap.getArray(key)));
249
+ break;
250
+ default:
251
+ break;
252
+ }
253
+ }
254
+ return map;
255
+ }
256
+
257
+ static List<Object> convertReadableArrayToList(final ReadableArray readableArray) {
258
+ final List<Object> list = new ArrayList<>(readableArray.size());
259
+ for (int i = 0; i < readableArray.size(); i++) {
260
+ ReadableType indexType = readableArray.getType(i);
261
+ switch(indexType) {
262
+ case Boolean:
263
+ list.add(i, readableArray.getBoolean(i));
264
+ break;
265
+ case Number:
266
+ list.add(i, readableArray.getDouble(i));
267
+ break;
268
+ case String:
269
+ list.add(i, readableArray.getString(i));
270
+ break;
271
+ case Map:
272
+ list.add(i, convertReadableMapToMap(readableArray.getMap(i)));
273
+ break;
274
+ case Array:
275
+ list.add(i, convertReadableArrayToList(readableArray.getArray(i)));
276
+ break;
277
+ default:
278
+ break;
279
+ }
280
+ }
281
+ return list;
282
+ }
283
+ }
@@ -1,6 +1,7 @@
1
1
  import Message from './models/Message';
2
2
  import { MessagingDelegate } from './models/MessagingDelegate';
3
3
  import { MessagingProposition } from './models/MessagingProposition';
4
+ import { ContentCard } from './models/ContentCard';
4
5
  export interface NativeMessagingModule {
5
6
  extensionVersion: () => Promise<string>;
6
7
  getCachedMessages: () => Message[];
@@ -10,6 +11,8 @@ export interface NativeMessagingModule {
10
11
  setMessagingDelegate: (delegate?: MessagingDelegate) => void;
11
12
  setMessageSettings: (shouldShowMessage: boolean, shouldSaveMessage: boolean) => void;
12
13
  updatePropositionsForSurfaces: (surfaces: string[]) => void;
14
+ trackContentCardDisplay: (proposition: MessagingProposition, contentCard: ContentCard) => void;
15
+ trackContentCardInteraction: (proposition: MessagingProposition, contentCard: ContentCard) => void;
13
16
  }
14
17
  declare class Messaging {
15
18
  /**
@@ -40,6 +43,8 @@ declare class Messaging {
40
43
  * @returns A record of surface names with their corresponding propositions
41
44
  */
42
45
  static getPropositionsForSurfaces(surfaces: string[]): Promise<Record<string, MessagingProposition[]>>;
46
+ static trackContentCardDisplay(proposition: MessagingProposition, contentCard: ContentCard): void;
47
+ static trackContentCardInteraction(proposition: MessagingProposition, contentCard: ContentCard): void;
43
48
  /**
44
49
  * Function to set the UI Message delegate to listen the Message lifecycle events.
45
50
  * @returns A function to unsubscribe from all event listeners
package/dist/Messaging.js CHANGED
@@ -63,6 +63,12 @@ class Messaging {
63
63
  return yield RCTAEPMessaging.getPropositionsForSurfaces(surfaces);
64
64
  });
65
65
  }
66
+ static trackContentCardDisplay(proposition, contentCard) {
67
+ RCTAEPMessaging.trackContentCardDisplay(proposition, contentCard);
68
+ }
69
+ static trackContentCardInteraction(proposition, contentCard) {
70
+ RCTAEPMessaging.trackContentCardInteraction(proposition, contentCard);
71
+ }
66
72
  /**
67
73
  * Function to set the UI Message delegate to listen the Message lifecycle events.
68
74
  * @returns A function to unsubscribe from all event listeners
@@ -1 +1 @@
1
- {"version":3,"file":"Messaging.js","sourceRoot":"","sources":["../src/Messaging.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Messaging.js","sourceRoot":"","sources":["../src/Messaging.ts"],"names":[],"mappings":"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"}
@@ -47,4 +47,12 @@ RCT_EXTERN_METHOD(updatePropositionsForSurfaces
47
47
  : (RCTPromiseResolveBlock)resolve withRejecter
48
48
  : (RCTPromiseRejectBlock)reject);
49
49
 
50
+ RCT_EXTERN_METHOD(trackContentCardDisplay
51
+ : (NSDictionary *)propositionMap contentCardMap
52
+ : (NSDictionary *)contentCardMap);
53
+
54
+ RCT_EXTERN_METHOD(trackContentCardInteraction
55
+ : (NSDictionary *)propositionMap contentCardMap
56
+ : (NSDictionary *)contentCardMap);
57
+
50
58
  @end
@@ -201,6 +201,54 @@ public class RCTAEPMessaging: RCTEventEmitter, MessagingDelegate {
201
201
  reject(Constants.CACHE_MISS, nil, nil)
202
202
  }
203
203
 
204
+ @objc
205
+ func trackContentCardDisplay(
206
+ _ propositionMap: [String: Any],
207
+ contentCardMap: [String: Any]
208
+ ) {
209
+ guard let contentCardId = contentCardMap["id"] as? String else {
210
+ print("Error: Content card ID is missing or invalid")
211
+ return
212
+ }
213
+
214
+ do {
215
+ let jsonData = try JSONSerialization.data(withJSONObject: propositionMap)
216
+ let proposition = try JSONDecoder().decode(Proposition.self, from: jsonData)
217
+
218
+ if let matchingItem = proposition.items.first(where: { $0.itemId == contentCardId }) {
219
+ matchingItem.track(withEdgeEventType: MessagingEdgeEventType.display)
220
+ } else {
221
+ print("Error: No matching proposition item found for content card ID: \(contentCardId)")
222
+ }
223
+ } catch {
224
+ print("Error decoding proposition: \(error.localizedDescription)")
225
+ }
226
+ }
227
+
228
+ @objc
229
+ func trackContentCardInteraction(
230
+ _ propositionMap: [String: Any],
231
+ contentCardMap: [String: Any]
232
+ ) {
233
+ guard let contentCardId = contentCardMap["id"] as? String else {
234
+ print("Error: Content card ID is missing or invalid")
235
+ return
236
+ }
237
+
238
+ do {
239
+ let jsonData = try JSONSerialization.data(withJSONObject: propositionMap)
240
+ let proposition = try JSONDecoder().decode(Proposition.self, from: jsonData)
241
+
242
+ if let matchingItem = proposition.items.first(where: { $0.itemId == contentCardId }) {
243
+ matchingItem.track("click", withEdgeEventType: MessagingEdgeEventType.interact)
244
+ } else {
245
+ print("Error: No matching proposition item found for content card ID: \(contentCardId)")
246
+ }
247
+ } catch {
248
+ print("Error decoding proposition: \(error.localizedDescription)")
249
+ }
250
+ }
251
+
204
252
  // Messaging Delegate Methods
205
253
  public func onDismiss(message: Showable) {
206
254
  if let fullscreenMessage = message as? FullscreenMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/react-native-aepmessaging",
3
- "version": "6.0.5",
3
+ "version": "7.1.0",
4
4
  "description": "Adobe Experience Platform support for React Native apps.",
5
5
  "homepage": "https://developer.adobe.com/client-sdks/documentation/",
6
6
  "license": "Apache-2.0",
@@ -28,9 +28,9 @@
28
28
  "name": "Adobe Experience Platform SDK Team"
29
29
  },
30
30
  "peerDependencies": {
31
- "@adobe/react-native-aepcore": "^6.0.0",
32
- "@adobe/react-native-aepedge": "^6.0.0",
33
- "@adobe/react-native-aepedgeidentity": "^6.0.0",
31
+ "@adobe/react-native-aepcore": "^7.0.0",
32
+ "@adobe/react-native-aepedge": "^7.0.0",
33
+ "@adobe/react-native-aepedgeidentity": "^7.0.0",
34
34
  "react-native": ">=0.60.0"
35
35
  },
36
36
  "publishConfig": {
@@ -39,5 +39,5 @@
39
39
  "installConfig": {
40
40
  "hoistingLimits": "dependencies"
41
41
  },
42
- "gitHead": "d907afaf1a8d7521a95ab744bf0e7ea6890e79db"
42
+ "gitHead": "33e33a1bf4c6233e83b4bc7d10f586e03eb6345a"
43
43
  }
package/src/Messaging.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  import Message from './models/Message';
20
20
  import { MessagingDelegate } from './models/MessagingDelegate';
21
21
  import { MessagingProposition } from './models/MessagingProposition';
22
+ import { ContentCard } from './models/ContentCard';
22
23
 
23
24
  export interface NativeMessagingModule {
24
25
  extensionVersion: () => Promise<string>;
@@ -34,6 +35,8 @@ export interface NativeMessagingModule {
34
35
  shouldSaveMessage: boolean
35
36
  ) => void;
36
37
  updatePropositionsForSurfaces: (surfaces: string[]) => void;
38
+ trackContentCardDisplay: (proposition: MessagingProposition, contentCard: ContentCard) => void;
39
+ trackContentCardInteraction: (proposition: MessagingProposition, contentCard: ContentCard) => void;
37
40
  }
38
41
 
39
42
  const RCTAEPMessaging: NativeModule & NativeMessagingModule =
@@ -90,6 +93,14 @@ class Messaging {
90
93
  return await RCTAEPMessaging.getPropositionsForSurfaces(surfaces);
91
94
  }
92
95
 
96
+ static trackContentCardDisplay(proposition: MessagingProposition, contentCard: ContentCard): void {
97
+ RCTAEPMessaging.trackContentCardDisplay(proposition, contentCard);
98
+ }
99
+
100
+ static trackContentCardInteraction(proposition: MessagingProposition, contentCard: ContentCard): void {
101
+ RCTAEPMessaging.trackContentCardInteraction(proposition, contentCard);
102
+ }
103
+
93
104
  /**
94
105
  * Function to set the UI Message delegate to listen the Message lifecycle events.
95
106
  * @returns A function to unsubscribe from all event listeners
@@ -0,0 +1,747 @@
1
+ # Content Cards Tutorial
2
+
3
+ ## Overview
4
+
5
+ Content Cards are a powerful feature of Adobe Journey Optimizer that allows you to deliver personalized, contextual content directly within your mobile application. Unlike push notifications or in-app messages, content cards provide a persistent, non-intrusive way to present relevant information to users when they're actively engaged with your app. Content cards are ideal for showcasing promotions, product recommendations, onboarding tips, or any contextual information that enhances the user experience without interrupting their workflow.
6
+
7
+
8
+ ## Fetching Content Cards
9
+
10
+ ### Prerequisites
11
+
12
+ Before implementing content cards, ensure you have:
13
+
14
+ 1. Integrated and registered the AEPMessaging extension in your app
15
+ 2. Configured content card campaigns in Adobe Journey Optimizer
16
+ 3. Defined locations where content cards should appear in your app
17
+
18
+ ### Step 1: Update Propositions for Surfaces
19
+
20
+ To fetch content cards for specific surfaces configured in Adobe Journey Optimizer campaigns, call the `updatePropositionsForSurfaces` API. This method retrieves the latest content cards from the server and caches them in-memory for the application's lifecycle.
21
+
22
+ **Best Practice**: Batch requests for multiple surfaces in a single API call when possible to optimize performance.
23
+
24
+ ```typescript
25
+ import { Messaging } from '@adobe/react-native-aepmessaging';
26
+
27
+ // Define surfaces of content cards to retrieve
28
+ const surfaces: string[] = ['homepage', 'product-detail', 'checkout'];
29
+
30
+ // Fetch content cards for multiple surfaces
31
+ const updateContentCards = async (): Promise<void> => {
32
+ try {
33
+ await Messaging.updatePropositionsForSurfaces(surfaces);
34
+ console.log('Content cards updated successfully');
35
+ } catch (error) {
36
+ console.error('Failed to update content cards:', error);
37
+ }
38
+ };
39
+ ```
40
+
41
+ ### Step 2: Retrieve Content Cards
42
+
43
+ After updating propositions, retrieve the content cards for a specific surface using the `getPropositionsForSurfaces` API. This returns a record of surface names with their corresponding propositions that contain content cards for which the user is qualified.
44
+
45
+ **Important**: Only content cards for which the user has qualified are returned. User qualification is determined by the delivery rules configured in Adobe Journey Optimizer.
46
+
47
+ ```typescript
48
+ import { Messaging, MessagingProposition, ContentCard, PersonalizationSchema } from '@adobe/react-native-aepmessaging';
49
+
50
+ // Retrieve propositions for specific surfaces
51
+ const getContentCards = async (surfacePaths: string[]): Promise<ContentCard[]> => {
52
+ try {
53
+ const propositionsMap: Record<string, MessagingProposition[]> = await Messaging.getPropositionsForSurfaces(surfacePaths);
54
+
55
+ const allContentCards: ContentCard[] = [];
56
+
57
+ // Extract content cards from all surfaces
58
+ Object.values(propositionsMap).forEach(propositions => {
59
+ propositions.forEach(proposition => {
60
+ const contentCards = proposition.items.filter(
61
+ item => item.schema === PersonalizationSchema.CONTENT_CARD
62
+ ) as ContentCard[];
63
+ allContentCards.push(...contentCards);
64
+ });
65
+ });
66
+
67
+ console.log(`Found ${allContentCards.length} content cards across all surfaces`);
68
+ return allContentCards;
69
+ } catch (error) {
70
+ console.error('Error retrieving propositions:', error);
71
+ return [];
72
+ }
73
+ };
74
+ ```
75
+
76
+ ## Rendering Content Cards
77
+
78
+ Content cards can be rendered in various ways depending on your application architecture. Since React Native doesn't support the pre-built `ContentCardUI` objects, you'll need to create your own UI components based on the content card data from propositions.
79
+
80
+ ### React Native Implementation
81
+
82
+ Here's how to implement content cards in a React Native component using propositions:
83
+
84
+ ```typescript
85
+ import React, { useState, useEffect } from 'react';
86
+ import { View, ScrollView, StyleSheet, Text, Image, TouchableOpacity } from 'react-native';
87
+ import { Messaging, MessagingProposition, ContentCard, PersonalizationSchema } from '@adobe/react-native-aepmessaging';
88
+
89
+ interface ContentCardsScreenProps {
90
+ surfacePath?: string;
91
+ }
92
+
93
+ const ContentCardsScreen: React.FC<ContentCardsScreenProps> = ({ surfacePath = 'homepage' }) => {
94
+ const [contentCards, setContentCards] = useState<ContentCard[]>([]);
95
+ const [loading, setLoading] = useState<boolean>(true);
96
+
97
+ useEffect(() => {
98
+ fetchContentCards();
99
+ }, [surfacePath]);
100
+
101
+ const fetchContentCards = async (): Promise<void> => {
102
+ try {
103
+ setLoading(true);
104
+
105
+ // First, update propositions for the surface
106
+ await Messaging.updatePropositionsForSurfaces([surfacePath]);
107
+
108
+ // Then retrieve the propositions and extract content cards
109
+ const propositionsMap: Record<string, MessagingProposition[]> = await Messaging.getPropositionsForSurfaces([surfacePath]);
110
+ const propositions = propositionsMap[surfacePath] || [];
111
+
112
+ const cards: ContentCard[] = propositions.flatMap(proposition =>
113
+ proposition.items.filter(item => item.schema === PersonalizationSchema.CONTENT_CARD) as ContentCard[]
114
+ );
115
+
116
+ setContentCards(cards || []);
117
+ } catch (error) {
118
+ console.error('Failed to fetch content cards:', error);
119
+ setContentCards([]);
120
+ } finally {
121
+ setLoading(false);
122
+ }
123
+ };
124
+
125
+ const renderContentCard = (card: ContentCard, index: number): JSX.Element => {
126
+ const { data } = card;
127
+
128
+ return (
129
+ <TouchableOpacity key={index} style={styles.cardContainer}>
130
+ {data.content.image?.url && (
131
+ <Image source={{ uri: data.content.image.url }} style={styles.cardImage} />
132
+ )}
133
+ <View style={styles.cardContent}>
134
+ {data.content.title?.content && (
135
+ <Text style={styles.cardTitle}>{data.content.title.content}</Text>
136
+ )}
137
+ {data.content.body?.content && (
138
+ <Text style={styles.cardBody}>{data.content.body.content}</Text>
139
+ )}
140
+ {data.content.actionUrl && (
141
+ <TouchableOpacity style={styles.actionButton}>
142
+ <Text style={styles.actionText}>Learn More</Text>
143
+ </TouchableOpacity>
144
+ )}
145
+ </View>
146
+ </TouchableOpacity>
147
+ );
148
+ };
149
+
150
+ if (loading) {
151
+ return (
152
+ <View style={styles.loading}>
153
+ <Text>Loading content cards...</Text>
154
+ </View>
155
+ );
156
+ }
157
+
158
+ return (
159
+ <ScrollView style={styles.container}>
160
+ {contentCards.map((card, index) => renderContentCard(card, index))}
161
+ </ScrollView>
162
+ );
163
+ };
164
+
165
+ const styles = StyleSheet.create({
166
+ container: {
167
+ flex: 1,
168
+ padding: 16,
169
+ },
170
+ cardContainer: {
171
+ backgroundColor: '#fff',
172
+ borderRadius: 8,
173
+ marginBottom: 16,
174
+ shadowColor: '#000',
175
+ shadowOffset: { width: 0, height: 2 },
176
+ shadowOpacity: 0.1,
177
+ shadowRadius: 4,
178
+ elevation: 3,
179
+ },
180
+ cardImage: {
181
+ width: '100%',
182
+ height: 120,
183
+ borderTopLeftRadius: 8,
184
+ borderTopRightRadius: 8,
185
+ },
186
+ cardContent: {
187
+ padding: 16,
188
+ },
189
+ cardTitle: {
190
+ fontSize: 18,
191
+ fontWeight: 'bold',
192
+ marginBottom: 8,
193
+ },
194
+ cardBody: {
195
+ fontSize: 14,
196
+ color: '#666',
197
+ marginBottom: 12,
198
+ },
199
+ actionButton: {
200
+ backgroundColor: '#007AFF',
201
+ paddingHorizontal: 16,
202
+ paddingVertical: 8,
203
+ borderRadius: 4,
204
+ alignSelf: 'flex-start',
205
+ },
206
+ actionText: {
207
+ color: '#fff',
208
+ fontWeight: '600',
209
+ },
210
+ loading: {
211
+ flex: 1,
212
+ justifyContent: 'center',
213
+ alignItems: 'center',
214
+ },
215
+ });
216
+
217
+ export default ContentCardsScreen;
218
+ ```
219
+
220
+ ## Tracking Content Card Events
221
+
222
+ Content cards support event tracking to measure user engagement and campaign effectiveness. The AEPMessaging extension provides methods to track two key events: display and interact. These events help you understand how users engage with your content cards and optimize your campaigns accordingly.
223
+
224
+ ### Event Types
225
+
226
+ - **Display**: Triggered when a content card is shown to the user
227
+ - **Interact**: Triggered when a user taps or interacts with a content card
228
+
229
+ ### Implementing Event Tracking
230
+
231
+ Here's how to implement event tracking in your content card components:
232
+
233
+ ```typescript
234
+ import React, { useState, useEffect, useRef } from 'react';
235
+ import { View, ScrollView, StyleSheet, Text, Image, TouchableOpacity } from 'react-native';
236
+ import { Messaging, MessagingProposition, ContentCard, PersonalizationSchema } from '@adobe/react-native-aepmessaging';
237
+
238
+ interface ContentCardsWithTrackingProps {
239
+ surfacePath?: string;
240
+ }
241
+
242
+ const ContentCardsWithTracking: React.FC<ContentCardsWithTrackingProps> = ({
243
+ surfacePath = 'homepage'
244
+ }) => {
245
+ const [contentCards, setContentCards] = useState<ContentCard[]>([]);
246
+ const [propositions, setPropositions] = useState<MessagingProposition[]>([]);
247
+ const [loading, setLoading] = useState<boolean>(true);
248
+ const displayedCards = useRef<Set<string>>(new Set());
249
+
250
+ useEffect(() => {
251
+ fetchContentCards();
252
+ }, [surfacePath]);
253
+
254
+ const fetchContentCards = async (): Promise<void> => {
255
+ try {
256
+ setLoading(true);
257
+
258
+ await Messaging.updatePropositionsForSurfaces([surfacePath]);
259
+ const propositionsMap: Record<string, MessagingProposition[]> = await Messaging.getPropositionsForSurfaces([surfacePath]);
260
+ const fetchedPropositions = propositionsMap[surfacePath] || [];
261
+
262
+ const cards: ContentCard[] = fetchedPropositions.flatMap(proposition =>
263
+ proposition.items.filter(item => item.schema === PersonalizationSchema.CONTENT_CARD) as ContentCard[]
264
+ );
265
+
266
+ setContentCards(cards || []);
267
+ setPropositions(fetchedPropositions || []);
268
+ } catch (error) {
269
+ console.error('Failed to fetch content cards:', error);
270
+ setContentCards([]);
271
+ setPropositions([]);
272
+ } finally {
273
+ setLoading(false);
274
+ }
275
+ };
276
+
277
+ // Track display event when card becomes visible
278
+ const trackDisplayEvent = (proposition: MessagingProposition, contentCard: ContentCard): void => {
279
+ const propositionId = proposition.id;
280
+
281
+ // Prevent duplicate display events for the same card
282
+ if (!displayedCards.current.has(propositionId)) {
283
+ try {
284
+ Messaging.trackContentCardDisplay(proposition, contentCard);
285
+ displayedCards.current.add(propositionId);
286
+ console.log(`Display event tracked for proposition: ${propositionId}`);
287
+ } catch (error) {
288
+ console.error('Failed to track display event:', error);
289
+ }
290
+ }
291
+ };
292
+
293
+ // Track interact event when user taps on card
294
+ const trackInteractEvent = (proposition: MessagingProposition, contentCard: ContentCard): void => {
295
+ try {
296
+ Messaging.trackContentCardInteraction(proposition, contentCard);
297
+ console.log(`Interact event tracked for proposition: ${proposition.id}`);
298
+ } catch (error) {
299
+ console.error('Failed to track interact event:', error);
300
+ }
301
+ };
302
+
303
+ const handleCardPress = (card: ContentCard, proposition: MessagingProposition): void => {
304
+ // Track interaction
305
+ trackInteractEvent(proposition, card);
306
+
307
+ // Handle card action (e.g., navigate to URL)
308
+ if (card.data.content.actionUrl) {
309
+ // Navigate to action URL or handle custom action
310
+ console.log(`Navigating to: ${card.data.content.actionUrl}`);
311
+ }
312
+ };
313
+
314
+ const handleCardDismiss = (cardIndex: number): void => {
315
+ // Remove card from display
316
+ setContentCards(prevCards => prevCards.filter((_, index) => index !== cardIndex));
317
+ };
318
+
319
+ const renderContentCard = (card: ContentCard, index: number): JSX.Element => {
320
+ const { data } = card;
321
+ const proposition = propositions.find(prop =>
322
+ prop.items.some(item => item.id === card.id)
323
+ );
324
+
325
+ // Track display event when card is rendered
326
+ useEffect(() => {
327
+ if (proposition) {
328
+ trackDisplayEvent(proposition, card);
329
+ }
330
+ }, [proposition]);
331
+
332
+ return (
333
+ <View key={index} style={styles.cardContainer}>
334
+ <TouchableOpacity
335
+ style={styles.dismissButton}
336
+ onPress={() => handleCardDismiss(index)}
337
+ >
338
+ <Text style={styles.dismissText}>×</Text>
339
+ </TouchableOpacity>
340
+
341
+ <TouchableOpacity
342
+ style={styles.cardContent}
343
+ onPress={() => proposition && handleCardPress(card, proposition)}
344
+ >
345
+ {data.content.image?.url && (
346
+ <Image source={{ uri: data.content.image.url }} style={styles.cardImage} />
347
+ )}
348
+ <View style={styles.cardTextContent}>
349
+ {data.content.title?.content && (
350
+ <Text style={styles.cardTitle}>{data.content.title.content}</Text>
351
+ )}
352
+ {data.content.body?.content && (
353
+ <Text style={styles.cardBody}>{data.content.body.content}</Text>
354
+ )}
355
+ {data.content.actionUrl && (
356
+ <View style={styles.actionButton}>
357
+ <Text style={styles.actionText}>Learn More</Text>
358
+ </View>
359
+ )}
360
+ </View>
361
+ </TouchableOpacity>
362
+ </View>
363
+ );
364
+ };
365
+
366
+ if (loading) {
367
+ return (
368
+ <View style={styles.loading}>
369
+ <Text>Loading content cards...</Text>
370
+ </View>
371
+ );
372
+ }
373
+
374
+ return (
375
+ <ScrollView style={styles.container}>
376
+ {contentCards.map((card, index) => renderContentCard(card, index))}
377
+ </ScrollView>
378
+ );
379
+ };
380
+
381
+ const styles = StyleSheet.create({
382
+ container: {
383
+ flex: 1,
384
+ padding: 16,
385
+ },
386
+ cardContainer: {
387
+ backgroundColor: '#fff',
388
+ borderRadius: 8,
389
+ marginBottom: 16,
390
+ shadowColor: '#000',
391
+ shadowOffset: { width: 0, height: 2 },
392
+ shadowOpacity: 0.1,
393
+ shadowRadius: 4,
394
+ elevation: 3,
395
+ position: 'relative',
396
+ },
397
+ dismissButton: {
398
+ position: 'absolute',
399
+ top: 8,
400
+ right: 8,
401
+ width: 24,
402
+ height: 24,
403
+ borderRadius: 12,
404
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
405
+ justifyContent: 'center',
406
+ alignItems: 'center',
407
+ zIndex: 1,
408
+ },
409
+ dismissText: {
410
+ color: '#fff',
411
+ fontSize: 16,
412
+ fontWeight: 'bold',
413
+ },
414
+ cardContent: {
415
+ flex: 1,
416
+ },
417
+ cardImage: {
418
+ width: '100%',
419
+ height: 120,
420
+ borderTopLeftRadius: 8,
421
+ borderTopRightRadius: 8,
422
+ },
423
+ cardTextContent: {
424
+ padding: 16,
425
+ },
426
+ cardTitle: {
427
+ fontSize: 18,
428
+ fontWeight: 'bold',
429
+ marginBottom: 8,
430
+ },
431
+ cardBody: {
432
+ fontSize: 14,
433
+ color: '#666',
434
+ marginBottom: 12,
435
+ },
436
+ actionButton: {
437
+ backgroundColor: '#007AFF',
438
+ paddingHorizontal: 16,
439
+ paddingVertical: 8,
440
+ borderRadius: 4,
441
+ alignSelf: 'flex-start',
442
+ },
443
+ actionText: {
444
+ color: '#fff',
445
+ fontWeight: '600',
446
+ },
447
+ loading: {
448
+ flex: 1,
449
+ justifyContent: 'center',
450
+ alignItems: 'center',
451
+ },
452
+ });
453
+
454
+ export default ContentCardsWithTracking;
455
+ ```
456
+
457
+ ### Event Tracking Best Practices
458
+
459
+ 1. **Display Events**: Track display events only once per card per session to avoid duplicate analytics
460
+ 2. **Interact Events**: Track interactions immediately when they occur to ensure accurate measurement
461
+ 3. **Error Handling**: Implement proper error handling for tracking calls to prevent app crashes
462
+
463
+ ### Custom Event Tracking Hook
464
+
465
+ For reusable event tracking logic, consider creating a custom hook:
466
+
467
+ ```typescript
468
+ import { useCallback, useRef } from 'react';
469
+ import { Messaging, MessagingProposition, ContentCard } from '@adobe/react-native-aepmessaging';
470
+
471
+ interface ContentCardTracking {
472
+ trackDisplay: (proposition: MessagingProposition, contentCard: ContentCard) => void;
473
+ trackInteract: (proposition: MessagingProposition, contentCard: ContentCard) => void;
474
+ }
475
+
476
+ const useContentCardTracking = (): ContentCardTracking => {
477
+ const displayedCards = useRef<Set<string>>(new Set());
478
+
479
+ const trackDisplay = useCallback((proposition: MessagingProposition, contentCard: ContentCard): void => {
480
+ const propositionId = proposition.id;
481
+
482
+ if (!displayedCards.current.has(propositionId)) {
483
+ try {
484
+ Messaging.trackContentCardDisplay(proposition, contentCard);
485
+ displayedCards.current.add(propositionId);
486
+ console.log(`Display tracked: ${propositionId}`);
487
+ } catch (error) {
488
+ console.error('Display tracking failed:', error);
489
+ }
490
+ }
491
+ }, []);
492
+
493
+ const trackInteract = useCallback((proposition: MessagingProposition, contentCard: ContentCard): void => {
494
+ try {
495
+ Messaging.trackContentCardInteraction(proposition, contentCard);
496
+ console.log(`Interact tracked: ${proposition.id}`);
497
+ } catch (error) {
498
+ console.error('Interact tracking failed:', error);
499
+ }
500
+ }, []);
501
+
502
+ return {
503
+ trackDisplay,
504
+ trackInteract,
505
+ };
506
+ };
507
+
508
+ export default useContentCardTracking;
509
+ ```
510
+
511
+ ### Advanced Usage Example
512
+
513
+ Here's a more comprehensive example showing how to use the tracking methods in a production-ready implementation:
514
+
515
+ ```typescript
516
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
517
+ import { View, FlatList, StyleSheet, Text, Image, TouchableOpacity, ViewToken } from 'react-native';
518
+ import { Messaging, MessagingProposition, ContentCard, PersonalizationSchema } from '@adobe/react-native-aepmessaging';
519
+
520
+ interface ContentCardWithProposition extends ContentCard {
521
+ proposition: MessagingProposition;
522
+ uniqueId: string;
523
+ }
524
+
525
+ interface ContentCardsFlatListProps {
526
+ surfacePaths?: string[];
527
+ }
528
+
529
+ const ContentCardsFlatList: React.FC<ContentCardsFlatListProps> = ({
530
+ surfacePaths = ['homepage']
531
+ }) => {
532
+ const [contentCards, setContentCards] = useState<ContentCardWithProposition[]>([]);
533
+ const [loading, setLoading] = useState<boolean>(true);
534
+ const displayedCards = useRef<Set<string>>(new Set());
535
+
536
+ useEffect(() => {
537
+ fetchContentCards();
538
+ }, [surfacePaths]);
539
+
540
+ const fetchContentCards = async (): Promise<void> => {
541
+ try {
542
+ setLoading(true);
543
+
544
+ await Messaging.updatePropositionsForSurfaces(surfacePaths);
545
+ const propositionsMap: Record<string, MessagingProposition[]> = await Messaging.getPropositionsForSurfaces(surfacePaths);
546
+
547
+ const cards: ContentCardWithProposition[] = [];
548
+
549
+ Object.entries(propositionsMap).forEach(([surface, propositions]) => {
550
+ propositions.forEach((proposition, propIndex) => {
551
+ proposition.items
552
+ .filter(item => item.schema === PersonalizationSchema.CONTENT_CARD)
553
+ .forEach((item, itemIndex) => {
554
+ cards.push({
555
+ ...(item as ContentCard),
556
+ proposition,
557
+ uniqueId: `${surface}-${proposition.id}-${itemIndex}`,
558
+ });
559
+ });
560
+ });
561
+ });
562
+
563
+ setContentCards(cards);
564
+ } catch (error) {
565
+ console.error('Failed to fetch content cards:', error);
566
+ setContentCards([]);
567
+ } finally {
568
+ setLoading(false);
569
+ }
570
+ };
571
+
572
+ // Track display event when card appears in viewport
573
+ const trackDisplayEvent = useCallback((proposition: MessagingProposition, contentCard: ContentCard): void => {
574
+ const propositionId = proposition.id;
575
+
576
+ if (!displayedCards.current.has(propositionId)) {
577
+ try {
578
+ Messaging.trackContentCardDisplay(proposition, contentCard);
579
+ displayedCards.current.add(propositionId);
580
+ console.log(`Display event tracked for proposition: ${propositionId}`);
581
+ } catch (error) {
582
+ console.error('Failed to track display event:', error);
583
+ }
584
+ }
585
+ }, []);
586
+
587
+ // Track interact event
588
+ const trackInteractEvent = useCallback((proposition: MessagingProposition, contentCard: ContentCard): void => {
589
+ try {
590
+ Messaging.trackContentCardInteraction(proposition, contentCard);
591
+ console.log(`Interact event tracked for proposition: ${proposition.id}`);
592
+ } catch (error) {
593
+ console.error('Failed to track interact event:', error);
594
+ }
595
+ }, []);
596
+
597
+ // Handle viewable items changed - track display events for visible cards
598
+ const onViewableItemsChanged = useCallback(({ viewableItems }: { viewableItems: ViewToken[] }): void => {
599
+ viewableItems.forEach(({ item }) => {
600
+ if (item.proposition) {
601
+ trackDisplayEvent(item.proposition, item);
602
+ }
603
+ });
604
+ }, [trackDisplayEvent]);
605
+
606
+ const handleCardPress = useCallback((card: ContentCardWithProposition): void => {
607
+ trackInteractEvent(card.proposition, card);
608
+
609
+ if (card.data.content.actionUrl) {
610
+ console.log(`Navigating to: ${card.data.content.actionUrl}`);
611
+ // Handle navigation or custom action here
612
+ }
613
+ }, [trackInteractEvent]);
614
+
615
+ const renderContentCard = ({ item: card }: { item: ContentCardWithProposition }): JSX.Element => {
616
+ const { data } = card;
617
+
618
+ return (
619
+ <TouchableOpacity
620
+ style={styles.cardContainer}
621
+ onPress={() => handleCardPress(card)}
622
+ activeOpacity={0.8}
623
+ >
624
+ {data.content.image?.url && (
625
+ <Image
626
+ source={{ uri: data.content.image.url }}
627
+ style={styles.cardImage}
628
+ resizeMode="cover"
629
+ />
630
+ )}
631
+ <View style={styles.cardTextContent}>
632
+ {data.content.title?.content && (
633
+ <Text style={styles.cardTitle}>{data.content.title.content}</Text>
634
+ )}
635
+ {data.content.body?.content && (
636
+ <Text style={styles.cardBody}>{data.content.body.content}</Text>
637
+ )}
638
+ {data.content.actionUrl && (
639
+ <View style={styles.actionButton}>
640
+ <Text style={styles.actionText}>Learn More</Text>
641
+ </View>
642
+ )}
643
+ </View>
644
+ </TouchableOpacity>
645
+ );
646
+ };
647
+
648
+ const keyExtractor = (item: ContentCardWithProposition): string => item.uniqueId;
649
+
650
+ if (loading) {
651
+ return (
652
+ <View style={styles.loading}>
653
+ <Text>Loading content cards...</Text>
654
+ </View>
655
+ );
656
+ }
657
+
658
+ return (
659
+ <FlatList<ContentCardWithProposition>
660
+ data={contentCards}
661
+ renderItem={renderContentCard}
662
+ keyExtractor={keyExtractor}
663
+ onViewableItemsChanged={onViewableItemsChanged}
664
+ viewabilityConfig={{
665
+ itemVisiblePercentThreshold: 50,
666
+ minimumViewTime: 500,
667
+ }}
668
+ contentContainerStyle={styles.flatListContainer}
669
+ showsVerticalScrollIndicator={false}
670
+ />
671
+ );
672
+ };
673
+
674
+ const styles = StyleSheet.create({
675
+ flatListContainer: {
676
+ padding: 16,
677
+ flexGrow: 1,
678
+ },
679
+ cardContainer: {
680
+ backgroundColor: '#fff',
681
+ borderRadius: 12,
682
+ marginBottom: 16,
683
+ shadowColor: '#000',
684
+ shadowOffset: { width: 0, height: 2 },
685
+ shadowOpacity: 0.1,
686
+ shadowRadius: 8,
687
+ elevation: 4,
688
+ overflow: 'hidden',
689
+ },
690
+ cardImage: {
691
+ width: '100%',
692
+ height: 160,
693
+ },
694
+ cardTextContent: {
695
+ padding: 16,
696
+ },
697
+ cardTitle: {
698
+ fontSize: 18,
699
+ fontWeight: 'bold',
700
+ marginBottom: 8,
701
+ color: '#333',
702
+ },
703
+ cardBody: {
704
+ fontSize: 14,
705
+ color: '#666',
706
+ marginBottom: 16,
707
+ lineHeight: 20,
708
+ },
709
+ actionButton: {
710
+ backgroundColor: '#007AFF',
711
+ paddingHorizontal: 20,
712
+ paddingVertical: 10,
713
+ borderRadius: 6,
714
+ alignSelf: 'flex-start',
715
+ },
716
+ actionText: {
717
+ color: '#fff',
718
+ fontWeight: '600',
719
+ fontSize: 14,
720
+ },
721
+ loading: {
722
+ flex: 1,
723
+ justifyContent: 'center',
724
+ alignItems: 'center',
725
+ },
726
+ });
727
+
728
+ export default ContentCardsFlatList;
729
+ ```
730
+
731
+ ## Performance Considerations
732
+
733
+ 1. **Caching**: Content cards are cached in-memory by the Messaging extension and persist through the application's lifecycle
734
+ 2. **Batching**: Always batch surface requests when fetching content cards for multiple locations
735
+ 3. **Viewport Tracking**: Use FlatList with `onViewableItemsChanged` for accurate display tracking
736
+ 4. **Error Handling**: Implement proper error handling for all tracking calls
737
+
738
+ ## Wrap Up
739
+
740
+ This implementation provides a comprehensive foundation for fetching, rendering, and tracking content cards in your React Native application using the `trackContentCardDisplay` and `trackContentCardInteraction` methods. These methods provide specific tracking for content card interactions, allowing for better analytics and campaign optimization.
741
+
742
+ The key benefits of these tracking methods:
743
+ - **Simplified API**: Direct methods for content card tracking without needing to handle proposition-level details
744
+ - **Better Analytics**: More specific event data for content card interactions
745
+ - **Easier Implementation**: Cleaner code with dedicated methods for content card events
746
+
747
+ Happy coding!