Skip to content

feat: add native anonymousId functionality #822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The hassle-free way to add Segment analytics to your React-Native app.
- [Setting up the client](#setting-up-the-client)
- [Client Options](#client-options)
- [iOS Deep Link Tracking Setup](#ios-deep-link-tracking-setup)
- [Native AnonymousId](#native-anonymousid)
- [Usage with hooks](#usage-with-hooks)
- [useAnalytics()](#useanalytics)
- [Usage without hooks](#usage-without-hooks)
Expand Down Expand Up @@ -154,6 +155,56 @@ To track deep links in iOS you must add the following to your `AppDelegate.m` fi
return YES;
}
```
### Native AnonymousId

If you need to generate an `anonymousId` either natively or before the Analytics React Native package is initialized, you can send the anonymousId value from native code. The value has to be generated and stored by the caller. For reference, you can find a working example in the app and reference the code below:

**iOS**
```objc
...
#import <segment_analytics_react_native-Swift.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// generate your anonymousId value
// dispatch it across the bridge

[AnalyticsReactNative setAnonymousId: @"My-New-Native-Id"];
return yes
}
```
**Android**
```java
// MainApplication.java
...
import com.segmentanalyticsreactnative.AnalyticsReactNativePackage;

...
private AnalyticsReactNativePackage analytics = new AnalyticsReactNativePackage();

...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// AnalyticsReactNative will be autolinked by default, but to send the anonymousId before RN startup you need to manually link it to store a reference to the package
packages.add(analytics);
return packages;
}
...
@Override
public void onCreate() {
super.onCreate();
...

// generate your anonymousId value
// dispatch it across the bridge

analytics.setAnonymousId("My-New-Native-Id");
}
```

### Usage with hooks

In order to use the `useAnalytics` hook within the application, we will additionally need to wrap the application in
Expand Down
28 changes: 0 additions & 28 deletions example/android/.project

This file was deleted.

13 changes: 0 additions & 13 deletions example/android/.settings/org.eclipse.buildship.core.prefs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import com.segmentanalyticsreactnative.AnalyticsReactNativePackage;
import com.segmentanalyticsreactnative.AnalyticsReactNativeModule;
import com.analyticsreactnativepluginadvertisingid.AnalyticsReactNativePluginAdvertisingIdPackage;

import com.sovranreactnative.Sovran;
import android.util.Log;

public class MainApplication extends Application implements ReactApplication {

private AnalyticsReactNativePackage analytics = new AnalyticsReactNativePackage();
private Sovran sovran = new Sovran();
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {


@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
Expand All @@ -30,7 +36,8 @@ protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for
// AnalyticsReactNativeExample:
packages.add(new AnalyticsReactNativePackage());
packages.add(analytics);
packages.add(sovran);
packages.add(new AnalyticsReactNativePluginAdvertisingIdPackage());
return packages;
}
Expand Down Expand Up @@ -61,8 +68,11 @@ public void onCreate() {
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); //// Remove this line if you don't want
//// Flipper enabled

// Flipper enabled

// Enable for native anonymousId generation
// analytics.setAnonymousId("My-New-Native-Id");
}

/**
Expand Down
2 changes: 2 additions & 0 deletions example/ios/AnalyticsReactNativeExample/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
// Braze *braze = [BrazeReactBridge initBraze:configuration];
// AppDelegate.braze = braze;

// Enable for native anonymousId generation
// [AnalyticsReactNative setAnonymousId: @"My-New-Native-Id"];
return YES;
}

Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 148f23dc44ebce74497f5fef0651f1fea1f8a361

COCOAPODS: 1.12.0
COCOAPODS: 1.12.1
34 changes: 0 additions & 34 deletions packages/core/android/.project

This file was deleted.

13 changes: 0 additions & 13 deletions packages/core/android/.settings/org.eclipse.buildship.core.prefs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ enum class ConnectionType {
@ReactModule(name="AnalyticsReactNative")
class AnalyticsReactNativeModule : ReactContextBaseJavaModule, ActivityEventListener, LifecycleEventListener {

var onInitialized: () -> Unit = {};

constructor(reactContext: ReactApplicationContext) : super(reactContext) {
this.pInfo = reactContext.packageManager.getPackageInfo(reactContext.packageName, 0)
// Listen for new intents when app is in the background
Expand All @@ -39,6 +41,11 @@ class AnalyticsReactNativeModule : ReactContextBaseJavaModule, ActivityEventList
reactContext.addLifecycleEventListener(this)
}

override fun initialize() {
super.initialize()
onInitialized()
}

private var isColdLaunch = true
private val pInfo: PackageInfo

Expand Down Expand Up @@ -133,12 +140,12 @@ class AnalyticsReactNativeModule : ReactContextBaseJavaModule, ActivityEventList
val screenDensity = Resources.getSystem().displayMetrics.density;

val contextInfo: WritableMap = Arguments.createMap()


if (config.hasKey("collectDeviceId") && config.getBoolean("collectDeviceId")) {
val fallbackDeviceId = UUID.randomUUID().toString()
val deviceId = getUniqueId(config.hasKey("collectDeviceId") && config.getBoolean("collectDeviceId"))?: fallbackDeviceId

contextInfo.putString("deviceId", deviceId)
}

Expand Down Expand Up @@ -216,6 +223,20 @@ class AnalyticsReactNativeModule : ReactContextBaseJavaModule, ActivityEventList
?.currentReactContext
?.getNativeModule(SovranModule::class.java)
sovran?.dispatch("add-deepLink-data", properties)


}

fun setAnonymousId(anonymousId: String) {
val properties = Hashtable<String, String>()
properties["anonymousId"] = anonymousId

val currentContext = getReactApplicationContext()

if (currentContext != null) {
val sovran = currentContext.getNativeModule(SovranModule::class.java)
sovran?.dispatch("add-anonymous-id", properties)
}
}

override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,34 @@ import com.facebook.react.uimanager.ViewManager
import com.facebook.react.bridge.JavaScriptModule

class AnalyticsReactNativePackage : ReactPackage {

private var isInitialized = false
private var anonymousId: String? = null
private var module: AnalyticsReactNativeModule? = null

override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return Arrays.asList<NativeModule>(AnalyticsReactNativeModule(reactContext))
module = AnalyticsReactNativeModule(reactContext)
module?.onInitialized = {
isInitialized = true
anonymousId?.let { anonId ->
module?.setAnonymousId(anonId)
}
}
return listOf(module as NativeModule)
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList<ViewManager<*, *>>()
}

fun setAnonymousId(nativeAnonymousId: String) {
if (isInitialized) {
anonymousId = nativeAnonymousId;
anonymousId?.let { anonId ->
module?.setAnonymousId(anonId)
}
} else {
anonymousId = nativeAnonymousId
}
}
}
6 changes: 6 additions & 0 deletions packages/core/ios/AnalyticsReactNative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,10 @@ public class AnalyticsReactNative: NSObject {
Sovran.dispatch(action: "add-deepLink-data", payload: [ "referring_application": referringApp, "url":urlString])
}

@objc(setAnonymousId:)
public static func setAnonymousId(anonymousId: String) -> Void {

Sovran.dispatch(action: "add-anonymous-id", payload: ["anonymousId": anonymousId])
}

}
31 changes: 30 additions & 1 deletion packages/core/src/storage/sovranStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
} from '..';
import { getUUID } from '../uuid';
import { createGetter } from './helpers';
import { isObject } from '../util';
import { isObject, isString } from '../util';
import type {
Storage,
StorageConfig,
Expand Down Expand Up @@ -89,6 +89,28 @@ registerBridgeStore({
},
});

/**
* Action to set the anonymousId from native
* @param anonymousId native anonymousId string
*/

const addAnonymousId =
(payload: unknown) => (state: { userInfo: UserInfoState }) => {
if (isObject(payload)) {
const nativeAnonymousId = payload.anonymousId;

if (isString(nativeAnonymousId)) {
return {
userInfo: {
...state.userInfo,
anonymousId: nativeAnonymousId,
},
};
}
}
return state;
};

function createStoreGetter<
U extends Record<string, unknown>,
Z extends keyof U | undefined = undefined,
Expand Down Expand Up @@ -311,6 +333,13 @@ export class SovranStorage implements Storage {
},
};

registerBridgeStore({
store: this.userInfoStore,
actions: {
'add-anonymous-id': addAnonymousId,
},
});

this.deepLinkData = {
get: createStoreGetter(this.deepLinkStore),
onChange: (callback: (value: DeepLinkData) => void) =>
Expand Down