@attentive-mobile/attentive-react-native-sdk 2.0.0-beta.6 → 2.0.0-beta.8

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
@@ -68,12 +68,62 @@ See [DEBUGGING.md](./DEBUGGING.md) for detailed information about debugging feat
68
68
 
69
69
  ### Initialize the SDK
70
70
 
71
+ > **Platform difference:** iOS and Android have different initialization requirements.
72
+
73
+ #### iOS — Initialize from TypeScript
74
+
75
+ On iOS, call `initialize` from TypeScript as early as possible (e.g. the root `App` component's `useEffect`):
76
+
71
77
  ```typescript
72
- // 'initialize' should be called as soon as possible after the app starts (see the example app for an example of initializing the SDK in the App element)
73
- // Note: 'initialize' should only be called once per app session - if you call it multiple times it will throw an exception
78
+ // Called once per app session, before any other SDK operations.
74
79
  Attentive.initialize(config);
75
80
  ```
76
81
 
82
+ #### Android — Initialize from Native Code
83
+
84
+ On Android, `AttentiveSdk.initialize()` **must** be called from your `Application.onCreate()` in native Kotlin/Java code. There are two reasons for this:
85
+
86
+ 1. **Lifecycle observers must be registered before the React Native bridge is ready.** Internally, the SDK creates an `AppLaunchTracker` that calls `lifecycle.addObserver()` on the `ProcessLifecycleOwner`. If initialization happens after the bridge starts, early app-launch events can be missed.
87
+ 2. **`lifecycle.addObserver()` requires the main thread.** AndroidX enforces this with an `IllegalStateException` if called from a background thread. `Application.onCreate()` is guaranteed by the Android system to run on the main thread, so calling `initialize` there satisfies this requirement automatically — no extra threading machinery needed.
88
+
89
+ > **Do not** call `AttentiveSdk.initialize()` from a background thread or a coroutine dispatcher other than `Dispatchers.Main`. Doing so will throw an `IllegalStateException` from inside the AndroidX Lifecycle library.
90
+
91
+ Add the following to your `MainApplication.kt` (or `MainApplication.java`):
92
+
93
+ ```kotlin
94
+ import android.app.Application
95
+ import com.attentive.androidsdk.AttentiveConfig
96
+ import com.attentive.androidsdk.AttentiveSdk
97
+ import com.attentive.androidsdk.AttentiveLogLevel
98
+
99
+ class MainApplication : Application(), ReactApplication {
100
+
101
+ override fun onCreate() {
102
+ super.onCreate()
103
+ // ... your existing setup ...
104
+ initAttentiveSDK()
105
+ }
106
+
107
+ private fun initAttentiveSDK() {
108
+ val config = AttentiveConfig.Builder()
109
+ .applicationContext(this)
110
+ .domain("YOUR_ATTENTIVE_DOMAIN")
111
+ .mode(AttentiveConfig.Mode.PRODUCTION) // or Mode.DEBUG for testing
112
+ .skipFatigueOnCreatives(false)
113
+ .logLevel(AttentiveLogLevel.VERBOSE)
114
+ .build()
115
+
116
+ // Application.onCreate() is always called on the main thread by the Android system,
117
+ // so no thread-switching wrapper is needed here.
118
+ AttentiveSdk.initialize(config)
119
+ }
120
+ }
121
+ ```
122
+
123
+ After the native initialization, all other SDK operations (`identify`, `recordAddToCartEvent`, `recordPurchaseEvent`, etc.) are called from TypeScript as normal on both platforms.
124
+
125
+ > **Tip:** If you see `[AttentiveSDK] recordAddToCartEvent failed — SDK may not be initialized` in your Android logcat, it means `AttentiveSdk.initialize()` was not called from native code before the event was recorded. Check your `Application.onCreate()` setup.
126
+
77
127
  ### Destroy the creative
78
128
 
79
129
  ```typescript
@@ -171,28 +221,34 @@ Attentive.identify({phone: '+15556667777'};)
171
221
 
172
222
  ### Push Notifications (iOS and Android)
173
223
 
174
- The SDK supports push notification integration on both iOS (APNs) and Android (runtime permission + optional FCM). The following sections cover iOS-specific flows and a full **App events on Android** implementation that mirrors the behavior of the [Bonni](https://github.com/attentive-mobile/attentive-react-native-sdk/tree/main/Bonni) example app.
224
+ The SDK supports push notification integration on both iOS (APNs) and Android (FCM). The following sections cover iOS-specific setup flows. On Android, push notification integration is handled entirely in native Kotlin/Java code see [App Events on Android](#app-events-on-android) for details.
225
+
226
+ > **iOS — required setup:** Your AppDelegate **must** forward notification
227
+ > responses to the SDK for push tracking to work. Add this single line to your
228
+ > `userNotificationCenter(_:didReceive:withCompletionHandler:)`:
229
+ >
230
+ > ```swift
231
+ > AttentiveSDKManager.shared.handleNotificationResponse(response)
232
+ > ```
233
+ >
234
+ > Without this, push open and foreground push events **will not be tracked** on
235
+ > iOS. See [iOS AppDelegate Integration](#ios-appdelegate-integration) for full
236
+ > details.
237
+ >
238
+ > **Migrating from an earlier version?** If you previously called
239
+ > `AttentiveSDKManager.shared.handleForegroundPush(response:authorizationStatus:)`
240
+ > or `AttentiveSDKManager.shared.handlePushOpen(response:authorizationStatus:)`
241
+ > directly from your AppDelegate, **replace** that code with the single
242
+ > `handleNotificationResponse` call above. Using both will result in
243
+ > double-tracked events. The old methods are now deprecated.
175
244
 
176
245
  ---
177
246
 
178
247
  ### App Events on Android
179
248
 
180
- This section describes how to implement Attentive app events on Android so they behave like the iOS flow: **regular app opens** (launch and resume from background) and **notification permission** are handled using the SDK’s native Android APIs. You can add FCM token registration and push open handling when your app uses Firebase Cloud Messaging.
249
+ On Android, **regular app open and foreground events are handled automatically** by the native Android SDK once `AttentiveSdk.initialize()` is called from `Application.onCreate()` (see [Android Native Initialization](#android--initialize-from-native-code)). The lifecycle observers registered during initialization (e.g. `AppLaunchTracker`) take care of this transparently there is no need to manually call `handleRegularOpen` or subscribe to `AppState` changes.
181
250
 
182
- | SDK method | Purpose on Android |
183
- |------------|--------------------|
184
- | `getPushAuthorizationStatus()` | Returns `authorized`, `denied`, or `notDetermined` (uses `POST_NOTIFICATIONS` on API 33+). Use before `handleRegularOpen` so tracking uses the correct status. |
185
- | `registerForPushNotifications()` | Requests `POST_NOTIFICATIONS` on Android 13+; no-op on older versions. |
186
- | `handleRegularOpen(authStatus)` | Tracks a regular app open (launch or return to foreground). Call after `identify()` and pass the result of `getPushAuthorizationStatus()`. |
187
- | `registerDeviceToken` / `registerDeviceTokenWithCallback` | Optional. Register your FCM token when using Firebase Cloud Messaging. |
188
- | `handlePushOpen` / `handleForegroundPush` | Optional. Call when the user opens a notification or receives one in the foreground. |
189
-
190
- #### Overview
191
-
192
- - **Regular app open** – Call `handleRegularOpen(authorizationStatus)` when the app is opened (launch or returning to foreground). The SDK uses this for tracking and the `/mtctrl` endpoint.
193
- - **Permission status** – On Android 13+ (API 33+), notification permission is `POST_NOTIFICATIONS`. The SDK exposes `getPushAuthorizationStatus()` so you can pass the correct status into `handleRegularOpen`.
194
- - **Requesting permission** – Call `registerForPushNotifications()` to trigger the system permission dialog on Android 13+; it is a no-op on older versions.
195
- - **Order of operations** – Always call `identify()` before any `handleRegularOpen()` so the SDK has user context for network requests.
251
+ The only TypeScript-side step required on Android is calling `identify()` with any available user identifiers as early as possible in your app’s lifecycle (e.g. in the root component `useEffect`).
196
252
 
197
253
  #### Prerequisites
198
254
 
@@ -205,109 +261,78 @@ This section describes how to implement Attentive app events on Android so they
205
261
  </manifest>
206
262
  ```
207
263
 
208
- 2. **Initialize and identify first** – In your app entry (e.g. root component `useEffect`), call `initialize(config)` and `identify(identifiers)` before any push or app-event logic.
209
-
210
- #### 1. On app launch (Android)
264
+ 2. **Native initialization** – The SDK must be initialized from `Application.onCreate()` on Android (see [Android Native Initialization](#android--initialize-from-native-code) above). App open and lifecycle events are then tracked automatically.
211
265
 
212
- Right after `identify()`, do the following for the Android path:
266
+ #### TypeScript setup (Android)
213
267
 
214
- 1. Get the current notification authorization status with `getPushAuthorizationStatus()`.
215
- 2. Call `handleRegularOpen(authStatus)` with that status.
216
- 3. Optionally call `registerForPushNotifications()` to prompt for permission (Android 13+).
268
+ After native initialization, the only required TypeScript call is `identify()`:
217
269
 
218
270
  ```typescript
219
271
  import { Platform } from 'react-native';
220
- import {
221
- initialize,
222
- identify,
223
- getPushAuthorizationStatus,
224
- registerForPushNotifications,
225
- handleRegularOpen,
226
- type AttentiveSdkConfiguration,
227
- type PushAuthorizationStatus,
228
- } from 'attentive-react-native-sdk';
272
+ import { initialize, identify } from 'attentive-react-native-sdk';
229
273
 
230
274
  // Inside your root component (e.g. App.tsx useEffect):
231
- initialize(config);
232
- identify({ email: 'user@example.com', clientUserId: 'id-123' });
233
-
234
- if (Platform.OS === 'android') {
235
- getPushAuthorizationStatus()
236
- .then((authStatus: PushAuthorizationStatus) => {
237
- handleRegularOpen(authStatus);
238
- })
239
- .catch(() => {
240
- handleRegularOpen('authorized'); // fallback
241
- });
242
- registerForPushNotifications(); // Shows permission dialog on Android 13+
275
+ if (Platform.OS === 'ios') {
276
+ initialize(config);
243
277
  }
278
+
279
+ identify({ email: 'user@example.com', clientUserId: 'id-123' });
244
280
  ```
245
281
 
246
- #### 2. When app returns to foreground (Android)
282
+ #### Push notifications on Android (FCM)
247
283
 
248
- Subscribe to `AppState` and, when the app becomes `active`, get the current status and call `handleRegularOpen` again:
284
+ On Android, FCM token registration and push notification handling are managed natively in Kotlin/Java. This gives you full control over the Firebase Messaging lifecycle and ensures events are tracked before the React Native bridge initialises.
249
285
 
250
- ```typescript
251
- import { AppState } from 'react-native';
252
- import { getPushAuthorizationStatus, handleRegularOpen } from 'attentive-react-native-sdk';
253
- import type { PushAuthorizationStatus } from 'attentive-react-native-sdk';
254
-
255
- const subscription = AppState.addEventListener('change', (nextAppState) => {
256
- if (nextAppState === 'active' && Platform.OS === 'android') {
257
- getPushAuthorizationStatus()
258
- .then((authStatus: PushAuthorizationStatus) => {
259
- handleRegularOpen(authStatus);
260
- })
261
- .catch(() => {
262
- handleRegularOpen('authorized');
263
- });
264
- }
265
- });
266
-
267
- // Cleanup on unmount:
268
- return () => subscription.remove();
269
- ```
286
+ Add Firebase Cloud Messaging to your app following the [Firebase Android setup guide](https://firebase.google.com/docs/cloud-messaging/android/client), then handle token registration and notification events in your native `FirebaseMessagingService`:
270
287
 
271
- #### 3. Optional: Register FCM token (Android)
288
+ ```kotlin
289
+ import com.attentive.androidsdk.AttentiveSdk
290
+ import com.google.firebase.messaging.FirebaseMessagingService
291
+ import com.google.firebase.messaging.RemoteMessage
272
292
 
273
- **Recommended:** This React Native SDK’s Android native module depends on Attentive Android SDK **2.1.1**, which exposes `AttentiveSdk.getPushTokenWithCallback`. Calling `registerForPushNotifications()` from JS triggers that API: the SDK requests permission (when needed), fetches the FCM token, and registers it with Attentive. No separate native code is required.
293
+ class AttentiveMessagingService : FirebaseMessagingService() {
274
294
 
275
- **Alternative (token from JS):** If you obtain the FCM token elsewhere (e.g. Firebase Messaging), use `registerDeviceTokenWithCallback` and then call `handleRegularOpen` in the callback:
295
+ override fun onNewToken(token: String) {
296
+ super.onNewToken(token)
297
+ // Register the FCM token with the Attentive SDK
298
+ AttentiveSdk.registerDeviceToken(token)
299
+ }
276
300
 
277
- ```typescript
278
- import { registerDeviceTokenWithCallback, handleRegularOpen } from 'attentive-react-native-sdk';
279
-
280
- getPushAuthorizationStatus().then((authStatus) => {
281
- registerDeviceTokenWithCallback(
282
- fcmToken,
283
- authStatus,
284
- (data, url, response, error) => {
285
- if (error) {
286
- console.error('Attentive token registration failed', error);
287
- }
288
- handleRegularOpen(authStatus);
301
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
302
+ super.onMessageReceived(remoteMessage)
303
+ // Handle foreground push delivery
304
+ AttentiveSdk.handleForegroundPush(remoteMessage.data)
289
305
  }
290
- );
291
- });
306
+ }
292
307
  ```
293
308
 
294
- #### 4. Optional: Handle notification opens and foreground (Android)
309
+ For notification opens (when the user taps a push notification), handle the intent in your main `Activity`:
295
310
 
296
- If you handle FCM messages (e.g. with `@react-native-firebase/messaging`), you can report notification opens and foreground receives the same way as on iOS:
311
+ ```kotlin
312
+ import com.attentive.androidsdk.AttentiveSdk
297
313
 
298
- - **User opened notification (background/inactive):** `handlePushOpen(payload, authorizationStatus)`
299
- - **Notification received while app in foreground:** `handleForegroundPush(payload, authorizationStatus)`
314
+ class MainActivity : ReactActivity() {
300
315
 
301
- Get `authorizationStatus` via `getPushAuthorizationStatus()` when handling the event.
316
+ override fun onResume() {
317
+ super.onResume()
318
+ intent?.let { AttentiveSdk.handlePushOpen(it) }
319
+ }
320
+ }
321
+ ```
302
322
 
303
- #### Complete Android flow (reference)
323
+ Declare the service in your `AndroidManifest.xml`:
304
324
 
305
- The [Bonni](https://github.com/attentive-mobile/attentive-react-native-sdk/tree/main/Bonni) example app ([App.tsx](https://github.com/attentive-mobile/attentive-react-native-sdk/blob/main/Bonni/App.tsx)) implements the full flow:
325
+ ```xml
326
+ <service
327
+ android:name=".AttentiveMessagingService"
328
+ android:exported="false">
329
+ <intent-filter>
330
+ <action android:name="com.google.firebase.MESSAGING_EVENT" />
331
+ </intent-filter>
332
+ </service>
333
+ ```
306
334
 
307
- 1. **Launch:** `initialize` `identify` (Android) `getPushAuthorizationStatus()` `handleRegularOpen(authStatus)` `registerForPushNotifications()`.
308
- 2. **Foreground:** `AppState.addEventListener('change', …)` → when `active` and Android → `getPushAuthorizationStatus()` → `handleRegularOpen(authStatus)`.
309
- 3. **Optional:** When FCM token is available → `registerDeviceTokenWithCallback(token, authStatus, callback)` → in callback call `handleRegularOpen(authStatus)`.
310
- 4. **Optional:** When user opens a notification or receives one in foreground → `handlePushOpen` / `handleForegroundPush` with payload and status from `getPushAuthorizationStatus()`.
335
+ Refer to the [Attentive Android SDK documentation](https://github.com/attentive-mobile/attentive-android-sdk) for the full list of native APIs available for push notification integration.
311
336
 
312
337
  ---
313
338
 
@@ -321,9 +346,9 @@ import { registerForPushNotifications } from 'attentive-react-native-sdk';
321
346
  registerForPushNotifications();
322
347
  ```
323
348
 
324
- #### Register Device Token (iOS: APNs / Android: FCM)
349
+ #### Register Device Token (iOS)
325
350
 
326
- When your app receives a device token (APNs on iOS, FCM on Android), register it with the Attentive backend:
351
+ When your iOS app receives an APNs device token, register it with the Attentive backend:
327
352
 
328
353
  ```typescript
329
354
  import { registerDeviceToken } from 'attentive-react-native-sdk';
@@ -340,7 +365,7 @@ The `authorizationStatus` parameter should be one of:
340
365
  - `'provisional'` - Provisional authorization (quiet notifications)
341
366
  - `'ephemeral'` - App Clip notifications
342
367
 
343
- #### Handle Push Notification Opens (iOS and Android)
368
+ #### Handle Push Notification Opens (iOS)
344
369
 
345
370
  When a user taps on a push notification, track the event:
346
371
 
@@ -356,7 +381,7 @@ handlePushOpened(
356
381
  );
357
382
  ```
358
383
 
359
- #### Handle Foreground Notifications (iOS and Android)
384
+ #### Handle Foreground Notifications (iOS)
360
385
 
361
386
  When a notification arrives while the app is in the foreground:
362
387
 
@@ -373,7 +398,40 @@ For proper push notification integration, your iOS AppDelegate needs to:
373
398
 
374
399
  1. Request notification permissions via the SDK
375
400
  2. Implement `application:didRegisterForRemoteNotificationsWithDeviceToken:` to register the token
376
- 3. Implement `UNUserNotificationCenterDelegate` methods to handle notification events
401
+ 3. **Forward notification responses to the SDK for push-open tracking**
402
+
403
+ ##### Push Open Tracking (Required)
404
+
405
+ Add **one line** to your AppDelegate's `didReceive` handler so the SDK can track
406
+ push opens and foreground push events. Without this, `handlePushOpen()` and
407
+ `handleForegroundPush()` called from JavaScript will not be able to track events
408
+ on iOS (the native SDK requires a `UNNotificationResponse` which cannot cross the
409
+ React Native bridge).
410
+
411
+ ```swift
412
+ // In AppDelegate.swift — UNUserNotificationCenterDelegate
413
+ func userNotificationCenter(
414
+ _ center: UNUserNotificationCenter,
415
+ didReceive response: UNNotificationResponse,
416
+ withCompletionHandler completionHandler: @escaping () -> Void
417
+ ) {
418
+ // Attentive push tracking (handles app-state + auth status automatically)
419
+ AttentiveSDKManager.shared.handleNotificationResponse(response)
420
+
421
+ // Forward to your push library (e.g. RNCPushNotificationIOS) for JS events
422
+ RNCPushNotificationIOS.didReceive(response)
423
+ completionHandler()
424
+ }
425
+ ```
426
+
427
+ `handleNotificationResponse` automatically:
428
+ - Detects whether the app is in the foreground or background
429
+ - Fetches the current authorization status
430
+ - Calls the correct native SDK method (`handlePushOpen` or `handleForegroundPush`)
431
+ - Caches the response so the JS-side `handlePushOpen()` / `handleForegroundPush()` calls
432
+ are fulfilled without double-tracking
433
+ - **Cold-launch safe:** If the user taps a push while the app is killed, the
434
+ response is cached and automatically tracked once the SDK initializes
377
435
 
378
436
  ##### Callback-Based Registration (Recommended)
379
437
 
@@ -390,13 +448,13 @@ func application(
390
448
  ) {
391
449
  UNUserNotificationCenter.current().getNotificationSettings { settings in
392
450
  let authStatus = settings.authorizationStatus
393
-
451
+
394
452
  // Get SDK instance with proper type
395
453
  guard let attentiveSdk = AttentiveSDKManager.shared.sdk as? ATTNNativeSDK else {
396
454
  print("[Attentive] SDK not initialized")
397
455
  return
398
456
  }
399
-
457
+
400
458
  // Register device token with callback
401
459
  attentiveSdk.registerDeviceToken(
402
460
  deviceToken,
@@ -407,7 +465,7 @@ func application(
407
465
  if let error = error {
408
466
  print("[Attentive] Registration failed: \(error.localizedDescription)")
409
467
  }
410
-
468
+
411
469
  // Trigger regular open event after registration
412
470
  attentiveSdk.handleRegularOpen(authorizationStatus: authStatus)
413
471
  }
@@ -418,9 +476,8 @@ func application(
418
476
  ```
419
477
 
420
478
  **Documentation:**
421
- - [Push Token Registration Guide](./PUSH_TOKEN_REGISTRATION_GUIDE.md) - Detailed guide for callback-based registration
422
- - [AppDelegate Callback Example](./APPDELEGATE_CALLBACK_EXAMPLE.md) - Complete AppDelegate implementation
423
- - [Push Notifications Setup](./PUSH_NOTIFICATIONS_SETUP.md) - General push notification setup
479
+ - [Push Notifications Integration Guide](./docs/PUSH_NOTIFICATIONS_INTEGRATION.md) - Callback-based registration, complete AppDelegate implementation, Android and iOS token flow
480
+ - [Push Notifications Setup](./docs/PUSH_NOTIFICATIONS_SETUP.md) - Apple Developer Portal, APNs certificates, and TestFlight configuration
424
481
  - [iOS Native SDK documentation](https://github.com/attentive-mobile/attentive-ios-sdk) - Native SDK reference
425
482
 
426
- For a full Android implementation (app launch, foreground, permission, and optional FCM), see the **[App Events on Android](#app-events-on-android)** section above.
483
+ For Android push notification integration, see the **[App Events on Android](#app-events-on-android)** section above.
@@ -85,7 +85,7 @@ dependencies {
85
85
  // Use `api` so that the Attentive Android SDK types (AttentiveSdk, CustomEvent, etc.)
86
86
  // are visible to app-level code (e.g. Bonni's AttentiveFirebaseMessagingService) that
87
87
  // depends on this library and needs to call the SDK directly from native components.
88
- api 'com.attentive:attentive-android-sdk:2.1.3-beta.1'
88
+ api 'com.attentive:attentive-android-sdk:2.1.3'
89
89
  implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.10"
90
90
  implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.10"))
91
91