diff --git a/.gitignore b/.gitignore index ee0f560..ea7f272 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ android/keystores/debug.keystore # generated by bob lib/ + +# testing +/coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dfe8c5..edf804b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.4.0 + +2024-02 + +- Updated ReactNative to 0.73.2 +- Deprecated `onNotificationInteracted` method. Use `onNotificationOpenedApp` instead for handling notification interaction. +- Deprecated `setBackgroundMessageHandler`Android method. Use the new method `onAndroidBackgroundMessage` to handle background messages on Android. +- Changed `onNotificationOpenedApp` signature to include the remote message object as a parameter for better compatibility with the native SDK. +- Updated Ometria native SDK to the latest versions - šŸ 1.5.1 iOS & šŸ¤– 1.6.2 Android. Both SDK support quit app state notification handling. + # 2.3.0 2023-11 diff --git a/OmetriaReactNativeSdk.h b/OmetriaReactNativeSdk.h new file mode 100644 index 0000000..18f2f29 --- /dev/null +++ b/OmetriaReactNativeSdk.h @@ -0,0 +1,12 @@ +// +// OmetriaReactNativeSdk.h +// Pods +// +// Created by Vlad on 07.02.2024. +// + +#ifndef OmetriaReactNativeSdk_h +#define OmetriaReactNativeSdk_h + + +#endif /* OmetriaReactNativeSdk_h */ diff --git a/README.md b/README.md index a806d73..e8bcf12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -1\. Why integrate Ometria in a mobile app? ------------------------------------------- +# 1. Why integrate Ometria in a mobile app? Ometria helps your marketing department understand and better engage with your customers by delivering personalised emails and push notifications. @@ -15,68 +14,54 @@ For your mobile app, this means: App developers integrating with this SDK should follow the guide below. You can also look at the Sample app we have included for a reference implementation. -2\. Before you begin ----------------------- +# 2. Before you begin See [Setting up your mobile app with Firebase credentials](https://support.ometria.com/hc/en-gb/articles/360013658478-Setting-up-your-mobile-app-with-Firebase-credentials) in the Ometria help centre and follow the steps there to get an API key. -3\. Install the library ------------------------ +# 3. Install the library -The easiest way to get Ometria into your React-Native project is by using `npm install` or `yarn add`. +The easiest way to get Ometria into your ReactNative project is by using `npm install` or `yarn add`. -1. Install Ometria React-Native package from `react-native-ometria` using `npm install react-native-ometria` or `yarn add react-native-ometria` +1. Install Ometria ReactNative package from `react-native-ometria` using `npm install react-native-ometria` or `yarn add react-native-ometria` -note: If you have issues with installing the library, please consider excluding the example from typescript config +Note: If you have issues with installing the library, please consider excluding the example from typescript config eg: + ``` { ..., "exclude": ["example"] -} +} ``` + 2. For `iOS` you need to install Pods `pod install` to create a local CocoaPods spec mirror. -3. If you encounter ```The Swift pod 'Ometria' depends upon 'FirebaseMessaging'``` error when running pod install, please consider adding ```use_frameworks! :linkage => :static```. +3. If you encounter `The Swift pod 'Ometria' depends upon 'FirebaseMessaging'` error when running pod install, please consider adding `use_frameworks! :linkage => :static`. -4\. Initialise the library --------------------------- +# 4. Initialise the library To initialise the Ometria SDK, you need to enter the API key from **[2. Before you begin](#2-before-you-begin)**. ```js -import Ometria from 'react-native-ometria' -// Ometria init +import Ometria from 'react-native-ometria'; + await Ometria.initializeWithApiToken('API_KEY', { notificationChannelName: 'Example Channel Name', // optional, only for Android + appGroupIdentifier: 'group.com.ometria.sampleRN', // optional, only for iOS }); ``` -Since version 2.3.0, the SDK allows for reinitialization of the Ometria instance. So you can call this method again later in the app if you need to. +ā„¹ļø Since version 2.3.0, the SDK allows for reinitialization of the Ometria instance. So you can call this method again later in the app if you need to. -Any other Ometria methods must be called **after** this method has been called once. +āš ļø Any other Ometria methods must be called **after** this method has been called once. Once you've called this method once, the SDK will be able to send events to Ometria. You can now access your instance throughout the rest of your application. -You can specify a custom name of the Android notification channel in the second optional options parameter. Default channel name is ``. +#### Optional parameters -#### Notifications +* You can specify a custom name of the Android notification channel in the second optional options parameter. Default channel name is ``. -Ometria uses [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) to send push notifications to the mobile devices. - -You will therefore have to add ā€˜React-Native Firebaseā€™ as a dependency of Ometria, using the following lines: - -```js -import firebase from '@react-native-firebase/app'; -import messaging from '@react-native-firebase/messaging'; -``` - -For **Android** follow the Firebase React-Native tutorial [Firebase for Android](https://rnfirebase.io/#2-android-setup) -For **iOS** follow the Firebase React-Native tutorial [Firebase for iOS](https://rnfirebase.io/#3-ios-setup) - -To use push notifications, you also need to follow the steps in [Configure push notifications in your application](#configure-push-notifications-in-your-application) - -Read the full Ometria [Push notifications guide](#6-push-notifications-guide-for-react-native-apps) +* You can also specify an app group identifier in the second optional options parameter. See [this section](#62--adding-notification-service-extension-target-ios-only) for iOS. #### Debugging @@ -88,81 +73,23 @@ You can enable advanced logging if you want more information on whatā€™s happeni Ometria.isLoggingEnabled(true); ``` -4.1\. Firebase 8.0-8.10 issue IOS ---------------------------------- - -:warning: If using firebase version [8.0 - 8.10] consider updating to firebase 8.11 in order for push notifications to work. -If you have a hard dependency on firebase [8.0 - 8.10] make sure to add the following snippet in your AppDelegate file: -```objc -- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - [FIRMessaging.messaging setAPNSToken:deviceToken]; -} -``` - -4.2\. Using Firebase 9.x on iOS ---------------------------------- - -:warning: -If you are using Firebase 9.x on iOS, make sure you have the following lines in your `/ios/Podfile` file: -```objc - pod 'FirebaseCore', :modular_headers => true - pod 'GoogleUtilities', :modular_headers => true - pod 'FirebaseMessaging', :modular_headers => true -``` - - -5\. Event tracking guide ------------------------- +# 4. Event tracking guide You need to be aware of your usersā€™ behaviour on your platforms in order to understand them. Some behaviour is automatically detectable, other events need work from the app developer to track. -Many of these methods have analogous events in a server-to-server API called the [Ometria Data API](https://support.ometria.com/hc/en-gb/articles/360011511017-Data-API-introduction), and through a separate JavaScript API. +Many of these methods have analogous events in our [website tracker](https://support.ometria.com/hc/en-gb/articles/4405356106653-JavaScript-tracker). -**Be aware:** If your business already integrates with Ometria in any way, it is very important that the values sent here correspond to those in other integrations. +āš ļø If your business already integrates with Ometria in any way, it is very important that the values sent here correspond to those in other integrations. E.g., the customer identified event takes a customer ID or an email - these identifiers must be the same here as it is in the data API. If you specify both email and customer id, both need to match. The events are merged on Ometria's side into one big cross-channel view of your customer behaviour. If you use inconsistent email/customer ids, this could result in duplicate profiles created or data loss. -### Manually tracked events - -Once the SDK is initialised, you can track an event by calling its dedicated method: - -```js -const items: OmetriaBasketItem[] = [ - { - productId: 'product-1', - sku: 'sku-product-1', - quantity: 1, - price: 12.0, - variantId: 'variant-1', - }, - { - productId: 'product-2', - sku: 'sku-product-2', - quantity: 2, - price: 9.0, - variantId: 'variant-2', - }, - { - productId: 'product-3', - sku: 'sku-product-3', - quantity: 3, - price: 20.0, - variantId: 'variant-3', - }, -]; +## Manually tracked events -Ometria.trackBasketUpdatedEvent({ - totalPrice: 12.0, - id: 'basket_id_eg', - currency: 'USD', - items, - link: 'link_eg' -}); -``` +Once the SDK is initialised, you can track an event by calling its dedicated method. -#### Profile identified +### Profile identified An app user has just identified themselves, i.e. logged in. @@ -183,11 +110,10 @@ Having a **customerId** makes profile matching more robust. Itā€™s not mutually exclusive with sending an email event; for optimal integration you should send either event as soon as you have the information. These two events are pivotal to the functioning of the SDK, so make sure you send them as early as possible. Reiterating here that these identifiers must be the same here as the ones you use in your e-commerce platform and you send to Ometria (via the data API or other ways). If you specify both email and customer id, both need to match. A typical error we see at integrations is that the app generates a new customer id on each login (that doesn't match the customer id stored in Ometria). To avoid this, generate these ids centrally on your servers and send consistent ones through the Ometria mobile SDK and the Ometria Data API. If it is impractical to generate consistent ids, we suggest only using email to identify contacts. -#### Profile deidentified +### Profile deidentified Undo a profileIdentified event. - -Use this if a user logs out, or otherwise signals that this device is no longer attached to the same person. +You can use this if an user logs out. ```js Ometria.trackProfileDeidentifiedEvent(); @@ -195,7 +121,7 @@ Ometria.trackProfileDeidentifiedEvent(); Currently this event clears the stored ids (email and/or customer id) from the phone's local storage. It has no other effect within Ometria. -#### Product viewed +### Product viewed A visitor clicks/taps/views/highlights or otherwise shows interest in a product. @@ -207,8 +133,7 @@ This event is about capturing interest from the visitor for this product. Ometria.trackProductViewedEvent('product_id'); ``` - -#### Basket viewed +### Basket viewed The visitor has viewed a dedicated page, screen or modal with the contents of the shopping basket: @@ -216,7 +141,7 @@ The visitor has viewed a dedicated page, screen or modal with the contents of th Ometria.trackBasketViewedEvent(); ``` -#### Basket updated +### Basket updated The visitor has changed their shopping basket: @@ -250,7 +175,7 @@ Ometria.trackBasketUpdatedEvent({ id: 'basket_id_eg', currency: 'USD', items, - link: 'link_eg' + link: 'link_eg', }); ``` @@ -258,15 +183,32 @@ This event takes the full current basket as a parameter - not just the updated p This helps recover from lost or out of sync basket events: the latest update is always authoritative. -#### Checkout started +**OmetriaBasketItem** is an object that describes the contents of a shopping basket item. It can have its own price and quantity based on different rules and promotions that are being applied. It has the following properties: + +>* **productId**: (`String`, required) - A string representing the unique identifier of this product. +>* **sku**: (`String`, optional) - A string representing the stock keeping unit, which allows identifying a particular item. +>* **quantity**: (`Int`, required) - The number of items that this entry represents. +>* **price**: (`Float`, required) - Float value representing the price for one item. The currency is established by the OmetriaBasket containing this item +>* **variandId**: (`String`, optional) - An identifier for a variant product associated with this line item. -Track when the user has started the checkout process: +**OmetriaBasket** is an object that describes the contents of a shopping basket and has the following properties: + +>* **id**: (`String`, optional) - A unique identifier for this basket +>* **currency**: (`String`, required) - A string representing the currency in ISO 4217 three-letter currency code, e.g. `"USD"`, `"GBP"` +>* **totalPrice**: (`float`, required) - A float value representing the pricing. +>* **items**: (`Array[OmetriaBasketItem]`) - An array containing the item entries in this basket. +>* **link**: (`String`) - A deeplink to the web or in-app page for this basket. Can be used in a notification sent to the user, e.g. "Forgot to check out? Here's your basket to continue: 'https://eg.com/basket_url'". Following that link should take them straight to the basket page. + +### Checkout started + +Track when the user has started the checkout process. +This is currently only used to count page views, and has no other effect in Ometria. ```js Ometria.trackCheckoutStartedEvent('order_id'); ``` -#### Order completed +### Order completed The order has been completed and paid for: @@ -277,7 +219,7 @@ const items: OmetriaBasketItem[] = [ sku: 'sku-product-1', quantity: 1, price: 12.0, - } + }, ]; Ometria.trackOrderCompletedEvent('order_id', { @@ -285,23 +227,19 @@ Ometria.trackOrderCompletedEvent('order_id', { id: 'basket_id_eg', currency: 'USD', items, - link: 'link_eg' + link: 'link_eg', }); ``` -#### Deep link opened - -Based on the implementation status of interaction with notifications that contain deep links, this event can be automatically tracked or not. +### Deep link opened -The default implementation automatically logs a deep link opened event every time the user interacts with a notification that has a deep link. This is possible because we know that the default implementation will open the link in a browser. - -If you chose to handle deep links yourself (using the guide for [Handling interaction with notifications that contain URLs](#handling-interaction-with-notifications-that-contain-urls)), then you should manually track this event when you have enough information regarding the screen (or other destination) that the app will open. +Use the guide for [Handling interaction with notifications that contain URLs](#handling-interaction-with-notifications-that-contain-urls) to manually track this event when you have enough information regarding the screen (or other destination) that the app will open. ```js Ometria.trackDeepLinkOpenedEvent('/profile', 'ProfileScreen'); ``` -#### View home page +### View home page The visitor views the ā€˜home pageā€™ or landing screen of your app. @@ -309,7 +247,7 @@ The visitor views the ā€˜home pageā€™ or landing screen of your app. Ometria.trackHomeScreenViewedEvent(); ``` -#### View list of products +### View list of products The visitor clicks/taps/views/highlights or otherwise shows interest in a product listing. This kind of screen includes search results, listings of products in a group, category, collection or any other screen that presents a list of products. @@ -325,7 +263,7 @@ This event should be triggered on: Ometria.trackProductListingViewedEvent(); ``` -#### Screen viewed +### Screen viewed Tracking a visitorā€™s independent screen views helps us track their engagement with the app, as well as where they are in a journey. @@ -343,7 +281,7 @@ To track these custom screens, use the _Screen viewed_ event: Ometria.trackScreenViewedEvent('OnboardingScreen', { a: '1', b: '2' }); ``` -#### Custom events +### Custom events Your app might have specific flows or pages that are of interest to the marketing team. @@ -357,40 +295,11 @@ Check with the marketing team about the specifics, and what they might need. Esp Ometria.trackCustomEvent('my_custom_type', {}); ``` -### `OmetriaBasket` - -An object that describes the contents of a shopping basket. - -#### Properties - -* `id`: (`String`, optional) - A unique identifier for this basket -* `currency`: (`String`, required) - A string representing the currency in ISO currency format. e.g. `"USD"`, `"GBP"` -* `totalPrice`: (`float`, required) - A float value representing the pricing. -* `items`: (`Array[OmetriaBasketItem]`) - An array containing the item entries in this basket. -* `link`: (`String`) - A deeplink to the web or in-app page for this basket. Can be used in - a notification sent to the user, e.g. "Forgot to check out? Here's - your basket to continue: 'https://eg.com/basket_url'". Following that link should take - them straight to the basket page. - -### `OmetriaBasketItem` - -An object that describes the contents of a shopping basket. - -It can have its own price and quantity based on different rules and promotions that are being applied. - -#### Properties - -* `productId`: (`String`, required) - A string representing the unique identifier of this product. -* `sku`: (`String`, optional) - A string representing the stock keeping unit, which allows identifying a particular item. -* `quantity`: (`Int`, required) - The number of items that this entry represents. -* `price`: (`Float`, required) - Float value representing the price for one item. The currency is established by the OmetriaBasket containing this item -* `variandId`: (`String`, optional) - An identifier for a variant product associated with this line item. - -### Automatically tracked events +## Automatically tracked events The following events are automatically tracked by the SDK. -Linking and initialising the SDK is enough to take advantage of these; no further integration is required. +Initialising the SDK is enough to take advantage of these; no further integration is required (unless mentioned otherwise). | Event| Description| | ------------- |:-------------:| @@ -398,15 +307,16 @@ Linking and initialising the SDK is enough to take advantage of these; no furthe | **Application launched** | Someone has just launched the app.| | **Application foregrounded** | The app was already launched, but it was in the background. It has just been brought to the foreground.| | **Application backgrounded** | The app was in active use and has just been sent to the background. | -| **Notification received** | A Push notification was received by the system. (only on iOS) | -| **Notification interacted** | The user has just clicked on / tapped on / opened a notification. (only on iOS) | +| **Notification received in quit state of the app** | A Push notification was received by the system while the app was in quit state. ([needs a Notification Service Extension on iOS](#62--adding-notification-service-extension-target-ios-only)) | | **Error occurred** | An error occurred on the client side. We try to detect any problems with actual notification payload on our side, so we don't expect any errors which need to be fed back to end users. | + ### Flush tracked events In order to reduce power and bandwidth consumption, the Ometria library doesnā€™t send the events one by one unless you request it to do so. Instead, it composes batches of events that are sent to the backend during application runtime when the one of the following happened: + * it has collected 10 events or * there was a firebase token refresh (`pushtokenRefreshed` event) * a `notificationReceived` event @@ -416,7 +326,7 @@ Instead, it composes batches of events that are sent to the backend during appli You can request the library to send all remaining events to the backend whenever you want by calling: ```js -Ometria.flush() +Ometria.flush(); ``` ### Clear tracked events @@ -426,27 +336,66 @@ You can completely clear all the events that have been tracked and not yet flush To do this, call the following method: ```js -Ometria.clear() +Ometria.clear(); ``` -### Debugging events +## Debugging events + To see what events were captured, you can check the logs coming from the Ometria SDK, if logging is enabled. You can filter for the word "Ometria". The SDK logs all events as they happen, and also logs the flushing i.e. when they are sent to the Ometria mobile events API. Any potential errors with the sending (API issues or event validation issues) would be visible here too. -6\. Push notifications guide for React-Native apps ----------------------------- +# 5. Push Notifications Firebase setup + +Ometria uses [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) to send push notifications to the mobile devices. + +You will therefore have to add ā€˜React-Native Firebaseā€™ as a dependency of Ometria, using the following lines: + +```js +import firebase from '@react-native-firebase/app'; +import messaging from '@react-native-firebase/messaging'; +``` + +For **Android** follow the Firebase ReactNative tutorial [Firebase for Android](https://rnfirebase.io/#2-android-setup) +For **iOS** follow the Firebase ReactNative tutorial [Firebase for iOS](https://rnfirebase.io/#3-ios-setup) + +To use push notifications, you also need to follow the steps in [Push Notifications ReactNative Guide](#6-push-notifications-reactnative-guide) + + +### Firebase 8.0-8.10 issue on iOS + +:warning: If using firebase version [8.0 - 8.10] consider updating to firebase 8.11 in order for push notifications to work. +If you have a hard dependency on firebase [8.0 - 8.10] make sure to add the following snippet in your AppDelegate file: + +```objc +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + [FIRMessaging.messaging setAPNSToken:deviceToken]; +} +``` + +### Using Firebase 9.x on iOS + +:warning: +If you are using Firebase 9.x on iOS, make sure you have the following lines in your `/ios/Podfile` file: + +```objc + pod 'FirebaseCore', :modular_headers => true + pod 'GoogleUtilities', :modular_headers => true + pod 'FirebaseMessaging', :modular_headers => true +``` + +# 6. Push Notifications ReactNative guide When correctly set up, Ometria can send personalised notifications for your mobile application. Follow these steps: -1. Enable your app to receive push notifications by creating an appId and enabling the push notifications entitlement. -2. Set up a [Firebase](https://firebase.google.com/docs/cloud-messaging) account and connect it to Ometria. -3. Enable Cloud Messaging on your Firebase account and provide your applicationā€™s **SSL push certificate**. -4. Configure push notifications in your application. -5. Add a **Notification Service Extension** to your app in order to enable receiving rich content notifications. +* Enable your app to receive push notifications by creating an appId and enabling the push notifications entitlement. +* Set up a [Firebase](https://firebase.google.com/docs/cloud-messaging) account and connect it to Ometria. +* Enable Cloud Messaging on your Firebase account and provide your applicationā€™s **SSL push certificate**. +* Configure push notifications in your application. +* Add a **Notification Service Extension** to your app in order to enable receiving rich content notifications and to track notifications received in quit state of the app on iOS. -### I. Configure push notifications in your application +## 6.1. Configure push notifications in your application (iOS and Android) Before continuing, you must have already configured: @@ -455,30 +404,12 @@ Before continuing, you must have already configured: Read more about those steps in section [4\. Initialise the library](#4-initialise-the-library) -#### - Forward the push token to Ometria -After Ometria initialisation, you **must forward the Firebase Push Notification token** (both iOS and Android). -You also have to forward the push notification token to Ometria every time it is refreshed. - -```js -import Ometria from 'react-native-ometria'; -import messaging from '@react-native-firebase/messaging'; -// ... -// Initialize the Ometria SDK -await Ometria.initializeWithApiToken('API_KEY', { - notificationChannelname: 'Example Channel Name', // optional, only for Android -}); -// ... -messaging() - .getToken() - .then(pushToken => Ometria.onNewToken(pushToken)); - -messaging().onTokenRefresh(pushToken => Ometria.onNewToken(pushToken)); -``` - -#### - Request permission to receive Push Notifications +### A. Request permission to receive Push Notifications +___ For **Android 13** (API level 33) and higher you first have to declare the permission in your AndroidManifest.xml file: + ```xml @@ -488,10 +419,8 @@ For **Android 13** (API level 33) and higher you first have to declare the permi ``` - You have to request permissions for notifications. You can use [react-native-permissions](https://github.com/zoontek/react-native-permissions). - ```js import {requestNotifications, RESULTS } from 'react-native-permissions'; ... @@ -501,101 +430,118 @@ await requestNotifications(['alert', 'sound', 'badge']).then(({ status }) => { } }); ``` -Find more about Notification runtime permissions on Android [here](https://developer.android.com/develop/ui/views/notifications/notification-permission) -#### - Handling remote messages on iOS -The Ometria SDK will automatically handle remote background messages and provide them to the backend. -This way your app will start receiving notifications from Ometria. +Find more about Notification runtime permissions on Android [here](https://developer.android.com/develop/ui/views/notifications/notification-permission). + +### B. Forward the push token to Ometria +___ + +After Ometria initialisation, you **must forward the Firebase Push Notification token** (both iOS and Android). -Additionally on iOS, you should use the Ometria.onNotificationInteracted method to capture notification interactions. +You also have to forward the push notification token to Ometria every time it is refreshed. ```js -Platform.OS === 'ios' && - Ometria.onNotificationInteracted((notificationData: OmetriaNotificationData) => - // Use the notification data that has been interacted with. - ); +import Ometria from 'react-native-ometria'; +import messaging from '@react-native-firebase/messaging'; + +await Ometria.initializeWithApiToken('API_KEY', { + notificationChannelname: 'Example Channel Name', // optional, only for Android + appGroupIdentifier: 'group.com.ometria.sampleRN', // optional, only for iOS +}); + +messaging() + .getToken() + .then((pushToken) => Ometria.onNewToken(pushToken)); + +messaging().onTokenRefresh((pushToken) => Ometria.onNewToken(pushToken)); ``` -Handling those notifications while the app is running in the foreground or additional handling is up to you. -**Notes:** -- As of version 2.3.0 `Ometria.onNotificationInteracted` is an iOS specific method. -- As of version 2.0.0 `Ometria.onNotificationInteracted` requires a callback function parameter that handles the interaction response. Usage of `.then().catch()` that was used in 1.x.x no longer works! Please use a callback instead. +### C. Handling Notifications in Foreground App State +___ +Subscribe to remote messages that your app gets while in foreground app state. You can do this by using the `onMessage` method from the `@react-native-firebase/messaging` package. -#### - Handling remote messages on Android +In the callback, you can use `Ometria.onNotificationReceived` to let the Ometria SDK know that a remote message has been received and the `notificationReceived` event will be fired. Use `Ometria.parseNotification` if you want to extract Ometria data from the remote message. -For Android, you have the freedom to implement all methods that handle the remote messages as you like. +```js +messaging().onMessage(async (remoteMessage) => { + Ometria.onNotificationReceived(remoteMessage); + const ometriaData = Ometria.parseNotification(remoteMessage); + // Use ometriaData +}); +``` + +āš ļø Keep in mind that foreground notifications are **not** shown to the user. Instead, you could trigger a local notification or update the in-app UI to signal a new notification. Read more [here](https://rnfirebase.io/messaging/usage#foreground-state-messages). + +ā„¹ļø If you implement such a custom solution, don't forget to call `Ometria.onNotificationOpenedApp` to let the SDK the notification has been interacted with when handling the notification interaction event for foreground notifications. + + +### D. Handling Notifications in Quit & Background App State on iOS šŸ +___ + +In order for Ometria to accurately track all the notifications that were received in quit and background state of the app on iOS, it needs to leverage the power of a background service, that has access to all notifications. -1. Subscribe early (in `index.js`) to remote messages that your app gets while being in quit state. -`Ometria.setBackgroundMessageHandle` will let the Ometria SDK know that a remote message has been received and the *notificationReceived* event will be fired. It needs the Ometria token in order to initialize the SDK in background. +For a complete guide on how to set up a Notification Service Extension, see [Adding Notification Service Extension Target](#62--adding-notification-service-extension-target-ios-only). + + +### E. Handling Notifications in Quit & Background App State on Android šŸ¤– +___ +In order for Ometria to accurately track all the notifications that were received in quit and background state of the app on Android you need to subscribe early (in `index.js`) to remote messages that your app gets while being in quit and background state. + +`Ometria.onAndroidBackgroundMessage` will let the Ometria SDK know that a remote message has been received and the _notificationReceived_ event will be fired. It needs the Ometria token in order to initialize the SDK in background. ```js Platform.OS === 'android' && messaging().setBackgroundMessageHandler(async (remoteMessage) => { - Ometria.setBackgroundMessageHandler({ + Ometria.onAndroidBackgroundMessage({ ometriaToken: 'OMETRIA_KEY' ometriaOptions: {}, remoteMessage, }); }); - ``` +``` -2. Subscribe to remote messages that your app gets while in foreground or background state. Use `Ometria.onNotificationReceived` to let the Ometria SDK know that a remote message has been received and the `notificationReceived` event will be fired. Use `Ometria.parseNotification` if you want to extract Ometria data from the remote message. Keep in mind that foreground notifications are **not** shown to the user. Instead, you could trigger a local notification or update the in-app UI to signal a new notification. Read more [here](https://rnfirebase.io/messaging/usage#foreground-state-messages). If you implement such a custom solution, don't forget to call `Ometria.onNotificationOpenedApp` to let the SDK the notification has been interacted with, if you want to handle the notification interaction event. +ā„¹ļø As of version 2.4.0 `Ometria.setBackgroundMessageHandler` is a deprecated method. Use `Ometria.onAndroidBackgroundMessage` instead. -```js -messaging().onMessage( - async (remoteMessage) => { - Ometria.onNotificationReceived(remoteMessage); - Ometria.parseNotification(remoteMessage).then((parsedNotification) => { - // use the parsed notification data - }) - } -); -``` +### F. Handling Notification Interaction (background and quit state) +___ +When a user interacts with a notification in background or quit state, you have to let the Ometria SDK know that the notification has been interacted with and the app has been opened. You can do this by calling `Ometria.onNotificationOpenedApp` with the remote message as a parameter. -3. Finally, you have to also listen to notifications that open the app (from background and quit state). ```js -const handleInteraction = remoteMessage => { - Ometria.onNotificationOpenedApp({ remoteMessage }); - // Parse remote message with Ometria.parseNotification(remoteMessage) and use it -}; - -// Check for background notification that opened the app +// Check if the app was opened from quit state by a notification messaging() - .getInitialNotification() - .then(remoteMessage => remoteMessage && handleInteraction(remoteMessage)); + .getInitialNotification() + .then((remoteMessage) => { + if (remoteMessage) { + Ometria.onNotificationOpenedApp(remoteMessage); + } + }); -// Subscribe to foreground notification that opens the app -messaging().onNotificationOpenedApp(remoteMessage => handleInteraction(remoteMessage)); +// Subscribe to the app being opened from background state by a notification +messaging().onNotificationOpenedApp((remoteMessage) => + Ometria.onNotificationOpenedApp(remoteMessage) +); ``` -For a complete example and use case please consult the [Sample app](example/src/App.tsx). +ā„¹ļø As of version 2.4.0 `Ometria.onNotificationInteracted` is a deprecated method. Use `Ometria.onNotificationOpenedApp` instead. -### II. Handling interaction with notifications that contain URLs -Ometria allows you to send URLs and tracking info alongside your push notifications and allows you to handle them on the device. +### Handling interaction with notifications that contain URLs +___ -- On **iOS** the Ometria SDK automatically handles any interaction with push notifications that contain URLs by opening them in a browser. However, it enables developers to handle those URLs as they see fit (e.g. take the user to a specific screen in the app). To get access to those interactions and the URLs, implement the `Ometria.onNotificationInteracted` iOS method. This method accepts a callback function that has access to the parsed Ometria notification data. +Ometria allows you to send URLs and tracking info alongside your push notifications and allows you to handle them on the device. When a notification opened the app, you can parse the notification remote message and check if it contains a deeplink URL. ```js -Platform.OS === 'ios' && - Ometria.onNotificationInteracted((response: OmetriaNotificationData) => - response.deepLinkActionUrl && Linking.openURL(response.deepLinkActionUrl) - ); +const notif = await Ometria.parseNotification(remoteMessage); +if (notif?.deepLinkActionUrl) { + Ometria.trackDeepLinkOpenedEvent(notif.deepLinkActionUrl, 'Browser'); + Linking.openURL(notif.deepLinkActionUrl); +} ``` -- On **Android** the developer handles the interactions with push notifications, as described in the previous section. When a notification opened the app, you can parse the notification remote message and check if it contains a deeplink URL. - -```js -const handleInteraction = remoteMessage => { - Ometria.onNotificationOpenedApp({ remoteMessage }); - const response = await Ometria.parseNotification(remoteMessage); - response.deepLinkActionUrl && Linking.openURL(response.deepLinkActionUrl) -}; -``` +`Ometria.parseNotification` returns an object with `OmetriaNotificationData` type that looks like this: -`OmetriaNotificationData` type looks like this: ``` type OmetriaNotificationData = { campaignType?: 'trigger; // represents automation campaigns @@ -613,86 +559,105 @@ type OmetriaNotificationData = { }; ``` -### III. Enabling rich content notifications (iOS only) +## 6.2. šŸ Adding Notification Service Extension Target (iOS only) -For **iOS** you have to integrate the rich content notification support directly in the Xcode project. +The Notification Service Extension has two purposes: -Starting with iOS 12.0, Apple enabled regular applications to receive and display notifications that contain media content such as images. +1. Starting with iOS 12.0, Apple enabled regular applications to receive and display notifications that contain media content such as images. In order to be able to display the rich content, notifications have to be processed by the Notification Service Extension before being shown to the user. +2. There are lots of users that forget to open their apps. In order for Ometria to accurately track all the notifications that were received in quit state, it needs to leverage the power of a background service, that has access to all notifications. -Ometria uses this feature to further enhance your application, but it requires you to add a new target extension that intercepts all push notifications containing 'mutable-content: 1' in the payload. +### Add a Notification Service Extension -To do this, go to **File > New > Target**, and select **Notification Service Extension > Next**. +In order to add the extension, go to **File > New > Target**, and select **Notification Service Extension > Next**. ![](https://raw.githubusercontent.com/wiki/Ometria/ometria.ios_sdk/images/notification_service_extension.png) -A new item displays in your target list: +A new item is displayed in your target list: ![](https://raw.githubusercontent.com/wiki/Ometria/ometria.ios_sdk/images/project_targets.png) Next, make sure that the Ometria SDK is also available to this new target by updating your podfile to include your newly added target and specify Ometria as a dependency. -**Warning**: If you try to run pod install and then build the extension, you will get some compilation errors. +```ruby -Since we are trying to run Ometria on an extension, there are several methods in the SDK that are not supported, although not being used. +# move this line before the App target +pod 'GoogleUtilities', : modular_headers => true -To silence those errors and get everything functional you will have to update your podfile ending up with something like this: +use_frameworks! :linkage => :static -```ruby - platform :ios, '10.0' +target 'sampleApp' do + # Pods for sampleApp +end -target 'OmetriaSample' do - use_frameworks! +# Add the following lines +target 'NotificationService' do + use_frameworks! :linkage => :static + pod 'Ometria' +end +``` - pod 'Ometria', :path => '../../SDK' +### Create an app group - target 'OmetriaSampleNotificationService' do - pod 'Ometria', :path => '../../SDK' - end +At this point the main application and the extension function as two separate entities with the only shared component being the code. In order for the extension to obtain read and write access to data that is relevant for the SDK, it requires to be in the same App Group as the main target. This will allow the main target and the extension to share data. - end +In your project navigator, select your project, then go to **Signing & Capabilities** and select **+ Capability** in the top left corner. -post_install do |installer| - installer.pods_project.targets.each do |target| - if target.name == 'Ometria' - target.build_configurations.each do |config| - config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No' - end - end - end -end -``` +![](https://github.com/Ometria/ometria.ios_sdk/assets/6207062/dd8cd6e7-ff00-41be-9573-2589436a4616) + +Once you have done so, a new section will be displayed below Signing. It will allow you to add a new app group. Make sure you select a relevant identifier (e.g.`group.[BUNDLE_IDENTIFIER]`), and retain the value, as you will need it when instantiating Ometria. +Repeat the process for the Notification Service Extension target, and you should be good to go. + +![alt text](https://github.com/Ometria/ometria.ios_sdk/assets/6207062/3d9d0500-3832-4312-a918-e5b05404886d) -Once youā€™ve done this, you can run your application and the extension you have just created. +### Update NotificationService -To finalise the implementation and allow Ometria to intercept notifications, open the `NotificationService` class and replace the content with the following: +To allow Ometria to intercept notifications, open the `NotificationService` class that was automatically created alongside the extension, and replace the content with the following: ```swift import UserNotifications import Ometria class NotificationService: OmetriaNotificationServiceExtension { - + override func instantiateOmetria() -> Ometria? { + Ometria.initializeForExtension(appGroupIdentifier: "group.[BUNDLE_IDENTIFIER]") + } } + ``` -Now you can receive notifications from Ometria and you are also able to see the images that are attached to your notifications. +### Use app group identifier when initialising Ometria + +Finally, you have to use the app group identifier when initialising Ometria in the ReactNative application. + +```js +import Ometria from 'react-native-ometria'; +// Ometria init +await Ometria.initializeWithApiToken('API_KEY', { + notificationChannelName: 'Example Channel Name', // optional, only for Android + appGroupIdentifier: 'group.[BUNDLE_IDENTIFIER]', // optional, only for iOS +}); +``` + +Now your app will emit a `notificationReceived` event every time a notification is received in quit state and you will be able to display rich content notifications on iOS. + + +šŸ’” For a complete example and use case please consult the [Sample app](example/src/App.tsx). -7\. App links guide ----------------------------- +# 7. App links guide Ometria sends personalised emails with URLs that point back to your website. In order to open these URLs inside your application, make sure you follow this guide. -### Pre-requisites +## Pre-requisites First, make sure you have an SSL-enabled Ometria tracking domain set up for your account. You may already have this for your email campaigns, but if not ask your Ometria contact to set one up, and they should provide you with the domain. -### Enable the Associated Domains entitlement for your application +## Enable the Associated Domains entitlement for your application Please follow the [ios documentation for associated domains](https://github.com/Ometria/ometria.ios_sdk/blob/master/README.md#enable-the-associated-domains-entitlement-for-your-application), then [create an apple-app-site-association file and send it to your Ometria contact](https://github.com/Ometria/ometria.ios_sdk/blob/master/README.md#create-an-apple-app-site-association-file-and-send-it-to-your-ometria-contact). -### Process App Links inside your application +## Process App Links inside your application The final step is to process the URLs in your app and take the user to the appropriate sections of the app. Note that you need to implement the mapping between your website's URLs and the screens of your app. @@ -701,7 +666,7 @@ See also [Linking push notifications to app screens](https://support.ometria.com If you are dealing with normal URLs pointing to your website, you can decompose it into different path components and parameters. This will allow you to source the required information to navigate through to the correct screen in your app. -In order for React-Native to identify that an url is opening the app you need to adjust AppDelegate.m file from ios folder +In order for ReactNative to identify that an url is opening the app you need to adjust AppDelegate.m file from ios folder add the following code to AppDelegate.m from ./ios/ProjectName ```objective-c diff --git a/android/build.gradle b/android/build.gradle index 257b11a..e9aa34d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -143,10 +143,10 @@ dependencies { if (is_production) { implementation "com.ometria:android-sdk:$ometria_android_sdk_version" } else { - implementation fileTree(dir: "libs", include: ["*.aar"]) + implementation fileTree(dir: "libs", include: ["*.aar"]) // Comment this if you want to use the local AAR in release mode implementation 'org.chromium.net:cronet-embedded:76.3809.111' compileOnly files('libs/OmetriaSDK-debug.aar') - debugImplementation files('libs/OmetriaSDK-debug.aar') + debugImplementation files('libs/OmetriaSDK-debug.aar') // Comment this if you want to use the local AAR in release mode } api 'com.facebook.react:react-native:+' diff --git a/android/gradle.properties b/android/gradle.properties index 6659d91..e3a1aab 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -4,5 +4,5 @@ OmetriaReactNativeSdk_buildToolsVersion=28.0.3 OmetriaReactNativeSdk_targetSdkVersion=28 OmetriaReactNativeSdk_minSdkVersion=23 OmetriaReactNativeSdk_gradleVersion=7.2.2 -OmetriaReactNativeSdk_ometriaAndroidSdkVersion=1.6.1 +OmetriaReactNativeSdk_ometriaAndroidSdkVersion=1.6.2 OmetriaReactNativeSdk_isProduction=1 diff --git a/android/gradlew b/android/gradlew index 1b6c787..0adc8e1 100755 --- a/android/gradlew +++ b/android/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/android/gradlew.bat b/android/gradlew.bat index 107acd3..93e3f59 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/android/src/main/java/com/ometriareactnativesdk/OmetriaReactNativeSdkModule.kt b/android/src/main/java/com/ometriareactnativesdk/OmetriaReactNativeSdkModule.kt index d359408..c91d45d 100644 --- a/android/src/main/java/com/ometriareactnativesdk/OmetriaReactNativeSdkModule.kt +++ b/android/src/main/java/com/ometriareactnativesdk/OmetriaReactNativeSdkModule.kt @@ -15,7 +15,7 @@ class OmetriaReactNativeSdkModule(private val reactContext: ReactApplicationCont var deeplinkInteractionPromise: Promise? = null init { - StorageController(reactContext.applicationContext).saveSdkVersionRN("2.3.0") + StorageController(reactContext.applicationContext).saveSdkVersionRN("2.4.0") } override fun getName(): String { diff --git a/babel.config.js b/babel.config.js index f842b77..f7b3da3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], + presets: ['module:@react-native/babel-preset'], }; diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 0000000..187894b --- /dev/null +++ b/example/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native', +}; diff --git a/example/.watchmanconfig b/example/.watchmanconfig new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/example/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/example/Gemfile b/example/Gemfile index 1142b1b..e0bb627 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby '>= 2.6.10' +ruby "2.7.5" -gem 'cocoapods', '>= 1.11.3' +gem 'cocoapods', '~> 1.13' +gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ad973c4..d0f1189 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,8 +1,9 @@ apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" -apply plugin: "com.google.gms.google-services" -import com.android.build.OutputFile +/* Added by Ometeria */ +apply plugin: "com.google.gms.google-services" /** * This is the configuration block to customize your React Native Android app. @@ -14,8 +15,8 @@ react { // root = file("../") // The folder where the react-native NPM package is. Default is ../node_modules/react-native // reactNativeDir = file("../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen - // codegenDir = file("../node_modules/react-native-codegen") + // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen + // codegenDir = file("../node_modules/@react-native/codegen") // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js // cliFile = file("../node_modules/react-native/cli.js") @@ -53,14 +54,6 @@ react { // hermesFlags = ["-O", "-output-source-map"] } -/** - * Set this to true to create four separate APKs instead of one, - * one for each native architecture. This is useful if you don't - * use App Bundles (https://developer.android.com/guide/app-bundle/) - * and want to have separate APKs to upload to the Play Store. - */ -def enableSeparateBuildPerCPUArchitecture = false - /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ @@ -79,19 +72,10 @@ def enableProguardInReleaseBuilds = false */ def jscFlavor = 'org.webkit:android-jsc:+' -/** - * Private function to get the list of Native Architectures you want to build. - * This reads the value from reactNativeArchitectures in your gradle.properties - * file and works together with the --active-arch-only flag of react-native run-android. - */ -def reactNativeArchitectures() { - def value = project.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} android { ndkVersion rootProject.ext.ndkVersion - + buildToolsVersion rootProject.ext.buildToolsVersion compileSdkVersion rootProject.ext.compileSdkVersion namespace "com.example.ometriareactnativesdk" @@ -99,18 +83,10 @@ android { applicationId "com.example.ometriareactnativesdk" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 22 - versionName "2.3.0" + versionCode 25 + versionName "2.4.0" } - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include (*reactNativeArchitectures()) - } - } signingConfigs { debug { storeFile file('debug.keystore') @@ -119,6 +95,7 @@ android { keyPassword 'android' } } + buildTypes { debug { signingConfig signingConfigs.debug @@ -132,40 +109,19 @@ android { } } - // applicationVariants are e.g. debug, release - applicationVariants.all { variant -> - variant.outputs.each { output -> - // For each separate APK per architecture, set a unique version code as described here: - // https://developer.android.com/studio/build/configure-apk-splits.html - // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. - def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] - def abi = output.getFilter(OutputFile.ABI) - if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - defaultConfig.versionCode * 1000 + versionCodes.get(abi) - } - - } - } } dependencies { - // } // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - - implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0") - - debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") - debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") - - debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") + implementation("com.facebook.react:flipper-integration") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } - + // Added by Ometeria + // implementation files('../../../android/libs/OmetriaSDK-debug.aar') // Uncomment this line if you are using the local AAR file in release implementation project(':ometriareactnativesdk') } diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 4b185bc..16a86ca 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,13 +1,9 @@ - - - - - + tools:ignore="GoogleAppIndexingWarning" + /> diff --git a/example/android/app/src/debug/java/com/sampleapp/ReactNativeFlipper.java b/example/android/app/src/debug/java/com/sampleapp/ReactNativeFlipper.java deleted file mode 100644 index e8e1e14..0000000 --- a/example/android/app/src/debug/java/com/sampleapp/ReactNativeFlipper.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - */ -package com.example.ometriareactnativesdk; - -import android.content.Context; -import com.facebook.flipper.android.AndroidFlipperClient; -import com.facebook.flipper.android.utils.FlipperUtils; -import com.facebook.flipper.core.FlipperClient; -import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; -import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; -import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; -import com.facebook.flipper.plugins.inspector.DescriptorMapping; -import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; -import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; -import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; -import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; -import com.facebook.react.ReactInstanceEventListener; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.modules.network.NetworkingModule; -import okhttp3.OkHttpClient; - -/** - * Class responsible of loading Flipper inside your React Native application. This is the debug - * flavor of it. Here you can add your own plugins and customize the Flipper setup. - */ -public class ReactNativeFlipper { - public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { - if (FlipperUtils.shouldEnableFlipper(context)) { - final FlipperClient client = AndroidFlipperClient.getInstance(context); - - client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); - client.addPlugin(new DatabasesFlipperPlugin(context)); - client.addPlugin(new SharedPreferencesFlipperPlugin(context)); - client.addPlugin(CrashReporterPlugin.getInstance()); - - NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); - NetworkingModule.setCustomClientBuilder( - new NetworkingModule.CustomClientBuilder() { - @Override - public void apply(OkHttpClient.Builder builder) { - builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); - } - }); - client.addPlugin(networkFlipperPlugin); - client.start(); - - // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized - // Hence we run if after all native modules have been initialized - ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); - if (reactContext == null) { - reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceEventListener() { - @Override - public void onReactContextInitialized(ReactContext reactContext) { - reactInstanceManager.removeReactInstanceEventListener(this); - reactContext.runOnNativeModulesQueueThread( - new Runnable() { - @Override - public void run() { - client.addPlugin(new FrescoFlipperPlugin()); - } - }); - } - }); - } else { - client.addPlugin(new FrescoFlipperPlugin()); - } - } - } -} diff --git a/example/android/app/src/main/java/com/sampleapp/MainActivity.java b/example/android/app/src/main/java/com/sampleapp/MainActivity.java deleted file mode 100644 index d614200..0000000 --- a/example/android/app/src/main/java/com/sampleapp/MainActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.example.ometriareactnativesdk; - -import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactActivityDelegate; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "OmetriaReactNativeSdkExample"; - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link - * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React - * (aka React 18) with two boolean flags. - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new DefaultReactActivityDelegate( - this, - getMainComponentName(), - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled - // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18). - DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled - ); - } -} diff --git a/example/android/app/src/main/java/com/sampleapp/MainActivity.kt b/example/android/app/src/main/java/com/sampleapp/MainActivity.kt new file mode 100644 index 0000000..47125dd --- /dev/null +++ b/example/android/app/src/main/java/com/sampleapp/MainActivity.kt @@ -0,0 +1,22 @@ +package com.example.ometriareactnativesdk + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "OmetriaReactNativeSdkExample" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/example/android/app/src/main/java/com/sampleapp/MainApplication.java b/example/android/app/src/main/java/com/sampleapp/MainApplication.java deleted file mode 100644 index 9b55a0e..0000000 --- a/example/android/app/src/main/java/com/sampleapp/MainApplication.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.ometriareactnativesdk; - -import android.app.Application; -import com.facebook.react.PackageList; -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactNativeHost; -import com.facebook.soloader.SoLoader; -import java.util.List; - -import com.ometriareactnativesdk.OmetriaReactNativeSdkPackage; - - -public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = - new DefaultReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - packages.add(new OmetriaReactNativeSdkPackage()); - - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @Override - protected boolean isNewArchEnabled() { - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - - @Override - protected Boolean isHermesEnabled() { - return BuildConfig.IS_HERMES_ENABLED; - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } - - @Override - public void onCreate() { - super.onCreate(); - SoLoader.init(this, /* native exopackage */ false); - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - DefaultNewArchitectureEntryPoint.load(); - } - ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); - } -} diff --git a/example/android/app/src/main/java/com/sampleapp/MainApplication.kt b/example/android/app/src/main/java/com/sampleapp/MainApplication.kt new file mode 100644 index 0000000..fc3e58d --- /dev/null +++ b/example/android/app/src/main/java/com/sampleapp/MainApplication.kt @@ -0,0 +1,50 @@ +package com.example.ometriareactnativesdk; + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.react.flipper.ReactNativeFlipper +import com.facebook.soloader.SoLoader + + +// Added by Ometria +import com.ometriareactnativesdk.OmetriaReactNativeSdkPackage; + + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + add(OmetriaReactNativeSdkPackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(this.applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + } +} diff --git a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml index f35d996..73b37e4 100644 --- a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -20,7 +20,7 @@ android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> -