Skip to content

Commit 64ebe17

Browse files
authored
Merge pull request #747 from Iterable/loren/embedded/MOB-12268-android-add-click-handling-and-track
[MOB-12268] Add click handling and tracking
2 parents 257cbea + ecb2204 commit 64ebe17

File tree

18 files changed

+922
-29
lines changed

18 files changed

+922
-29
lines changed

android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,16 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
771771
}
772772
}
773773

774+
public void trackEmbeddedClick(ReadableMap messageMap, String buttonId, String clickedUrl) {
775+
IterableLogger.d(TAG, "trackEmbeddedClick: buttonId: " + buttonId + " clickedUrl: " + clickedUrl);
776+
IterableEmbeddedMessage message = Serialization.embeddedMessageFromReadableMap(messageMap);
777+
if (message != null) {
778+
IterableApi.getInstance().trackEmbeddedClick(message, buttonId, clickedUrl);
779+
} else {
780+
IterableLogger.e(TAG, "Failed to convert message map to IterableEmbeddedMessage");
781+
}
782+
}
783+
774784
// ---------------------------------------------------------------------------------------
775785
// endregion
776786
}

android/src/main/java/com/iterable/reactnative/Serialization.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,22 @@ static JSONArray serializeEmbeddedMessages(List<IterableEmbeddedMessage> embedde
149149
return embeddedMessagesJson;
150150
}
151151

152+
/**
153+
* Converts a ReadableMap to an IterableEmbeddedMessage.
154+
*
155+
* This is needed as in new arch you can only pass in basic types, which
156+
* then need to be converted in the native layer.
157+
*/
158+
static IterableEmbeddedMessage embeddedMessageFromReadableMap(ReadableMap messageMap) {
159+
try {
160+
JSONObject messageJson = convertMapToJson(messageMap);
161+
return IterableEmbeddedMessage.Companion.fromJSONObject(messageJson);
162+
} catch (JSONException e) {
163+
IterableLogger.e(TAG, "Failed to convert ReadableMap to IterableEmbeddedMessage: " + e.getLocalizedMessage());
164+
return null;
165+
}
166+
}
167+
152168
static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) {
153169
try {
154170
JSONObject iterableContextJSON = convertMapToJson(iterableContextMap);

android/src/newarch/java/com/RNIterableAPIModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
259259
moduleImpl.getEmbeddedMessages(placementIds, promise);
260260
}
261261

262+
@Override
263+
public void trackEmbeddedClick(ReadableMap message, String buttonId, String clickedUrl) {
264+
moduleImpl.trackEmbeddedClick(message, buttonId, clickedUrl);
265+
}
266+
262267
public void sendEvent(@NonNull String eventName, @Nullable Object eventData) {
263268
moduleImpl.sendEvent(eventName, eventData);
264269
}

android/src/oldarch/java/com/RNIterableAPIModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
263263
moduleImpl.getEmbeddedMessages(placementIds, promise);
264264
}
265265

266+
@ReactMethod
267+
public void trackEmbeddedClick(ReadableMap message, String buttonId, String clickedUrl) {
268+
moduleImpl.trackEmbeddedClick(message, buttonId, clickedUrl);
269+
}
270+
266271
public void sendEvent(@NonNull String eventName, @Nullable Object eventData) {
267272
moduleImpl.sendEvent(eventName, eventData);
268273
}

example/src/components/Embedded/Embedded.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
22
import { useCallback, useState } from 'react';
33
import {
44
Iterable,
5+
type IterableAction,
56
type IterableEmbeddedMessage,
67
} from '@iterable/react-native-sdk';
78
import { SafeAreaView } from 'react-native-safe-area-context';
@@ -69,6 +70,18 @@ export const Embedded = () => {
6970
[]
7071
);
7172

73+
const handleClick = useCallback(
74+
(
75+
message: IterableEmbeddedMessage,
76+
buttonId: string | null,
77+
action?: IterableAction | null
78+
) => {
79+
console.log(`handleClick:`, message);
80+
Iterable.embeddedManager.handleClick(message, buttonId, action);
81+
},
82+
[]
83+
);
84+
7285
return (
7386
<SafeAreaView style={styles.container}>
7487
<Text style={styles.text}>EMBEDDED</Text>
@@ -105,7 +118,9 @@ export const Embedded = () => {
105118
{embeddedMessages.map((message) => (
106119
<View key={message.metadata.messageId}>
107120
<View style={styles.embeddedTitleContainer}>
108-
<Text style={styles.embeddedTitle}>Embedded message | </Text>
121+
<Text style={styles.embeddedTitle}>Embedded message</Text>
122+
</View>
123+
<View style={styles.embeddedTitleContainer}>
109124
<TouchableOpacity
110125
onPress={() => startEmbeddedImpression(message)}
111126
>
@@ -117,15 +132,42 @@ export const Embedded = () => {
117132
>
118133
<Text style={styles.link}>Pause impression</Text>
119134
</TouchableOpacity>
135+
<Text style={styles.embeddedTitle}> | </Text>
136+
<TouchableOpacity
137+
onPress={() =>
138+
handleClick(message, null, message.elements?.defaultAction)
139+
}
140+
>
141+
<Text style={styles.link}>Handle click</Text>
142+
</TouchableOpacity>
120143
</View>
121144

122145
<Text>metadata.messageId: {message.metadata.messageId}</Text>
123146
<Text>metadata.placementId: {message.metadata.placementId}</Text>
124147
<Text>elements.title: {message.elements?.title}</Text>
125148
<Text>elements.body: {message.elements?.body}</Text>
149+
<Text>
150+
elements.defaultAction.data:{' '}
151+
{message.elements?.defaultAction?.data}
152+
</Text>
153+
<Text>
154+
elements.defaultAction.type:{' '}
155+
{message.elements?.defaultAction?.type}
156+
</Text>
126157
{(message.elements?.buttons ?? []).map((button, buttonIndex) => (
127158
<View key={`${button.id}-${buttonIndex}`}>
128-
<Text>Button {buttonIndex + 1}</Text>
159+
<View style={styles.embeddedTitleContainer}>
160+
<Text>Button {buttonIndex + 1}</Text>
161+
<Text style={styles.embeddedTitle}> | </Text>
162+
<TouchableOpacity
163+
onPress={() =>
164+
handleClick(message, button.id, button.action)
165+
}
166+
>
167+
<Text style={styles.link}>Handle click</Text>
168+
</TouchableOpacity>
169+
</View>
170+
129171
<Text>button.id: {button.id}</Text>
130172
<Text>button.title: {button.title}</Text>
131173
<Text>button.action?.data: {button.action?.data}</Text>

src/__mocks__/MockRNIterableAPI.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ export class MockRNIterableAPI {
182182

183183
static pauseEmbeddedImpression = jest.fn();
184184

185+
static trackEmbeddedClick = jest.fn();
186+
185187
// set messages function is to set the messages static property
186188
// this is for testing purposes only
187189
static setMessages(messages: IterableInAppMessage[]): void {

src/api/NativeRNIterableAPI.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ export interface Spec extends TurboModule {
155155
getEmbeddedMessages(
156156
placementIds: number[] | null
157157
): Promise<EmbeddedMessage[]>;
158+
trackEmbeddedClick(
159+
message: EmbeddedMessage,
160+
buttonId: string | null,
161+
clickedUrl: string | null
162+
): void;
158163

159164
// Wake app -- android only
160165
wakeApp(): void;

src/core/classes/Iterable.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Linking, NativeEventEmitter, Platform } from 'react-native';
1+
import { NativeEventEmitter, Platform } from 'react-native';
22

33
import { buildInfo } from '../../itblBuildInfo';
44

55
import { RNIterableAPI } from '../../api';
6+
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
67
import { IterableInAppManager } from '../../inApp/classes/IterableInAppManager';
78
import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage';
89
import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource';
@@ -11,6 +12,7 @@ import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation';
1112
import { IterableAuthResponseResult } from '../enums/IterableAuthResponseResult';
1213
import { IterableEventName } from '../enums/IterableEventName';
1314
import type { IterableAuthFailure } from '../types/IterableAuthFailure';
15+
import { callUrlHandler } from '../utils/callUrlHandler';
1416
import { IterableAction } from './IterableAction';
1517
import { IterableActionContext } from './IterableActionContext';
1618
import { IterableApi } from './IterableApi';
@@ -20,10 +22,11 @@ import { IterableAuthResponse } from './IterableAuthResponse';
2022
import type { IterableCommerceItem } from './IterableCommerceItem';
2123
import { IterableConfig } from './IterableConfig';
2224
import { IterableLogger } from './IterableLogger';
23-
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
2425

2526
const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
2627

28+
const defaultConfig = new IterableConfig();
29+
2730
/**
2831
* Checks if the response is an IterableAuthResponse
2932
*/
@@ -63,7 +66,7 @@ export class Iterable {
6366
/**
6467
* Current configuration of the Iterable SDK
6568
*/
66-
static savedConfig: IterableConfig = new IterableConfig();
69+
static savedConfig: IterableConfig = defaultConfig;
6770

6871
/**
6972
* In-app message manager for the current user.
@@ -115,8 +118,9 @@ export class Iterable {
115118
* });
116119
* ```
117120
*/
118-
static embeddedManager: IterableEmbeddedManager =
119-
new IterableEmbeddedManager();
121+
static embeddedManager: IterableEmbeddedManager = new IterableEmbeddedManager(
122+
defaultConfig
123+
);
120124

121125
/**
122126
* Initializes the Iterable React Native SDK in your app's Javascript or Typescript code.
@@ -195,9 +199,7 @@ export class Iterable {
195199
IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true);
196200
IterableLogger.setLogLevel(config.logLevel);
197201

198-
Iterable.embeddedManager.setEnabled(
199-
config.enableEmbeddedMessaging ?? false
200-
);
202+
Iterable.embeddedManager = new IterableEmbeddedManager(config);
201203
}
202204

203205
this.setupEventHandlers();
@@ -983,10 +985,10 @@ export class Iterable {
983985
if (Platform.OS === 'android') {
984986
//Give enough time for Activity to wake up.
985987
setTimeout(() => {
986-
callUrlHandler(url, context);
988+
callUrlHandler(Iterable.savedConfig, url, context);
987989
}, 1000);
988990
} else {
989-
callUrlHandler(url, context);
991+
callUrlHandler(Iterable.savedConfig, url, context);
990992
}
991993
});
992994
}
@@ -1081,22 +1083,6 @@ export class Iterable {
10811083
}
10821084
);
10831085
}
1084-
1085-
function callUrlHandler(url: string, context: IterableActionContext) {
1086-
// MOB-10424: Figure out if this is purposeful
1087-
// eslint-disable-next-line eqeqeq
1088-
if (Iterable.savedConfig.urlHandler?.(url, context) == false) {
1089-
Linking.canOpenURL(url)
1090-
.then((canOpen) => {
1091-
if (canOpen) {
1092-
Linking.openURL(url);
1093-
}
1094-
})
1095-
.catch((reason) => {
1096-
IterableLogger?.log('could not open url: ' + reason);
1097-
});
1098-
}
1099-
}
11001086
}
11011087

11021088
/**

src/core/classes/IterableApi.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,18 @@ export class IterableApi {
571571
return RNIterableAPI.getEmbeddedMessages(placementIds);
572572
}
573573

574+
/**
575+
* Track an embedded click.
576+
*/
577+
static trackEmbeddedClick(
578+
message: IterableEmbeddedMessage,
579+
buttonId: string | null,
580+
clickedUrl: string | null
581+
) {
582+
IterableLogger.log('trackEmbeddedClick: ', message, buttonId, clickedUrl);
583+
return RNIterableAPI.trackEmbeddedClick(message, buttonId, clickedUrl);
584+
}
585+
574586
// ---- End EMBEDDED ---- //
575587

576588
// ====================================================== //

src/core/enums/IterableActionSource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ export enum IterableActionSource {
88
appLink = 1,
99
/** The action source was an in-app message */
1010
inApp = 2,
11+
/** The action source was an embedded message */
12+
embedded = 3,
1113
}

0 commit comments

Comments
 (0)