Skip to content

feat: integrate Clickstream SDK into react-native app #8

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 2 commits into from
Jan 8, 2024
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ License: [MIT](https://github.com/Djallil14/SwiftUI-FakeShopping-App/blob/main/L
## React-Native Example
React-Native example app **v2ex** is forked from https://github.com/funnyzak/react-native-v2ex. To get started with the React-Native example, refer to the [React-Native Example README](react-native/README.md).

You can refer this [PR](https://github.com/aws-samples/clickstream-sdk-samples/pull/8/files) to learn how to integrate Clickstream Android and Swift SDK into react-native app.

License: [Apache-2.0](https://github.com/funnyzak/react-native-v2ex/blob/dev/LICENSE)

## Security
Expand Down
2 changes: 2 additions & 0 deletions react-native/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# React Native V2EX

The project is already integrated with Clickstream Android and Swift SDK. You can find more detail about [Clickstream Android SDK](https://github.com/awslabs/clickstream-android) and [Clickstream Swift SDK](https://github.com/awslabs/clickstream-swift).

The project used React Native to build a [V2EX](https://v2ex.com) mobile client application and based entirely on the [V2EX](https://v2ex.com) open API. This project is Based on RN 0.71.5.

The `Figma design draft` is open source and can be [duplicated](https://www.figma.com/community/file/1101074002447399194).
Expand Down
2 changes: 2 additions & 0 deletions react-native/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ dependencies {
} else {
implementation jscFlavor
}
implementation("software.aws.solution:clickstream:0.9.0")
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package github.funnyzak.v2ex;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import software.aws.solution.clickstream.ClickstreamAnalytics;
import software.aws.solution.clickstream.ClickstreamAttribute;
import software.aws.solution.clickstream.ClickstreamEvent;
import software.aws.solution.clickstream.ClickstreamUserAttribute;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class ClickstreamModule extends ReactContextBaseJavaModule {

public ClickstreamModule(ReactApplicationContext context) {
super(context);
}

@NonNull
@Override
public String getName() {
return "ClickstreamAnalytics";
}

@ReactMethod()
public void recordEventWithName(String eventName) {
ClickstreamAnalytics.recordEvent(eventName);
}

@ReactMethod
public void recordEvent(String eventName, ReadableMap map) {
HashMap<String, Object> attributeMap = map.toHashMap();
ClickstreamEvent.Builder builder = new ClickstreamEvent.Builder();
builder.name(eventName);
for (Map.Entry<String, Object> entry : attributeMap.entrySet()) {
if (entry.getValue() instanceof String) {
builder.add(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Integer) {
builder.add(entry.getKey(), (Integer) entry.getValue());
} else if (entry.getValue() instanceof Long) {
builder.add(entry.getKey(), (Long) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
builder.add(entry.getKey(), (Boolean) entry.getValue());
} else if (entry.getValue() instanceof Double) {
builder.add(entry.getKey(), (Double) entry.getValue());
}
}
ClickstreamAnalytics.recordEvent(builder.build());
}

@ReactMethod
public void setUserId(String userId) {
ClickstreamAnalytics.setUserId(userId);
}

@ReactMethod
public void addUserAttributes(ReadableMap map) {
HashMap<String, Object> attributeMap = map.toHashMap();
ClickstreamUserAttribute.Builder builder = new ClickstreamUserAttribute.Builder();
for (Map.Entry<String, Object> entry : attributeMap.entrySet()) {
if (entry.getValue() instanceof String) {
builder.add(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Integer) {
builder.add(entry.getKey(), (Integer) entry.getValue());
} else if (entry.getValue() instanceof Long) {
builder.add(entry.getKey(), (Long) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
builder.add(entry.getKey(), (Boolean) entry.getValue());
} else if (entry.getValue() instanceof Double) {
builder.add(entry.getKey(), (Double) entry.getValue());
}
}
ClickstreamAnalytics.addUserAttributes(builder.build());
}

@ReactMethod
public void addGlobalAttributes(ReadableMap map) {
HashMap<String, Object> attributeMap = map.toHashMap();
ClickstreamAttribute.Builder builder = new ClickstreamAttribute.Builder();
for (Map.Entry<String, Object> entry : attributeMap.entrySet()) {
if (entry.getValue() instanceof String) {
builder.add(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Integer) {
builder.add(entry.getKey(), (Integer) entry.getValue());
} else if (entry.getValue() instanceof Long) {
builder.add(entry.getKey(), (Long) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
builder.add(entry.getKey(), (Boolean) entry.getValue());
} else if (entry.getValue() instanceof Double) {
builder.add(entry.getKey(), (Double) entry.getValue());
}
}
ClickstreamAnalytics.addGlobalAttributes(builder.build());
}

@ReactMethod
public void deleteGlobalAttributes(ReadableArray attributes) {
ArrayList<String> attributeArray = new ArrayList<>();
for (Object attribute : attributes.toArrayList()) {
if (attribute instanceof String) {
attributeArray.add((String) attribute);
}
}
if (attributeArray.size() > 0) {
ClickstreamAnalytics.deleteGlobalAttributes(attributeArray.toArray(new String[0]));
}
}

@ReactMethod
public void flushEvents() {
ClickstreamAnalytics.flushEvents();
}

@ReactMethod
public void enable() {
ClickstreamAnalytics.enable();
}

@ReactMethod
public void disable() {
ClickstreamAnalytics.disable();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import software.aws.solution.clickstream.ClickstreamAnalytics;

import java.util.List;

Expand Down Expand Up @@ -63,5 +64,16 @@ public void onCreate() {
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());

// Init ClickstreamAnalytics
try {
ClickstreamAnalytics.init(getApplicationContext());

ClickstreamAnalytics.getClickStreamConfiguration()
.withLogEvents(true)
.withTrackScreenViewEvents(false)
.withTrackUserEngagementEvents(false);
} catch (AmplifyException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public List<NativeModule> createNativeModules(
@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new ClickstreamModule(reactContext));
return modules;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"UserAgent": "aws-solution/clickstream",
"Version": "1.0",
"analytics": {
"plugins": {
"awsClickstreamPlugin": {
"appId": "your appId",
"endpoint": "your endpoint",
"isCompressEvents": true,
"autoFlushEventsInterval": 10000,
"isTrackAppExceptionEvents": false
}
}
}
}
4 changes: 4 additions & 0 deletions react-native/ios/ClickstreamManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// ClickstreamManager.h
#import <React/RCTBridgeModule.h>
@interface ClickstreamManager : NSObject <RCTBridgeModule>
@end
55 changes: 55 additions & 0 deletions react-native/ios/ClickstreamManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// ClickstreamManager.m
#import "ClickstreamManager.h"
#import <Foundation/Foundation.h>
@import Clickstream;

@implementation ClickstreamManager

// To export a module named ClickstreamManager
RCT_EXPORT_MODULE(ClickstreamAnalytics);

RCT_EXPORT_METHOD(recordEventWithName:(NSString *)name)
{
[ClickstreamObjc recordEvent:name];
}

RCT_EXPORT_METHOD(recordEvent:(NSString *)name :(NSDictionary *)attributes)
{
[ClickstreamObjc recordEvent:name :attributes];
}

RCT_EXPORT_METHOD(setUserId:(NSString *)userId)
{
[ClickstreamObjc setUserId:userId];
}

RCT_EXPORT_METHOD(addUserAttributes:(NSDictionary *)attributes)
{
[ClickstreamObjc addUserAttributes:attributes];
}

RCT_EXPORT_METHOD(addGlobalAttributes:(NSDictionary *)attributes)
{
[ClickstreamObjc addGlobalAttributes:attributes];
}

RCT_EXPORT_METHOD(deleteGlobalAttributes:(NSArray *)attributes)
{
[ClickstreamObjc deleteGlobalAttributes:attributes];
}

RCT_EXPORT_METHOD(flushEvents)
{
[ClickstreamObjc flushEvents];
}

RCT_EXPORT_METHOD(enable)
{
[ClickstreamObjc enable];
}

RCT_EXPORT_METHOD(disable)
{
[ClickstreamObjc disable];
}
@end
13 changes: 13 additions & 0 deletions react-native/ios/amplifyconfiguration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"analytics": {
"plugins": {
"awsClickstreamPlugin": {
"appId": "your appId",
"endpoint": "your endpoint",
"isCompressEvents": true,
"autoFlushEventsInterval": 10000,
"isTrackAppExceptionEvents": false
}
}
}
}
4 changes: 4 additions & 0 deletions react-native/ios/app-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

26 changes: 26 additions & 0 deletions react-native/ios/app.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
3A46E88B2B26B70F0042C324 /* Clickstream in Frameworks */ = {isa = PBXBuildFile; productRef = 3A46E88A2B26B70F0042C324 /* Clickstream */; };
3A78F5E02B26DB16006001B7 /* ClickstreamManager.h in Sources */ = {isa = PBXBuildFile; fileRef = 3A78F5DF2B26DB16006001B7 /* ClickstreamManager.h */; };
3A78F5E22B26DC73006001B7 /* ClickstreamManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A78F5E12B26DC73006001B7 /* ClickstreamManager.m */; };
3A78F5E72B26E58A006001B7 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A78F5E62B26E58A006001B7 /* amplifyconfiguration.json */; };
6172F2D35A4C3AA820D92908 /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6423831EA8574132BED9D8CC /* libPods-app.a */; };
7EF68E3733C33B6898317E18 /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ABFE59519B596E51CEFDCCC0 /* libPods-app-appTests.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
Expand All @@ -37,6 +41,8 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = app/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = app/main.m; sourceTree = "<group>"; };
1D0AE47A65C8663E3B452821 /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = "<group>"; };
3A78F5DF2B26DB16006001B7 /* ClickstreamManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClickstreamManager.h; sourceTree = "<group>"; };
3A78F5E12B26DC73006001B7 /* ClickstreamManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ClickstreamManager.m; sourceTree = "<group>"; };
3A78F5E32B26E493006001B7 /* app-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "app-Bridging-Header.h"; sourceTree = "<group>"; };
3A78F5E62B26E58A006001B7 /* amplifyconfiguration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = amplifyconfiguration.json; sourceTree = "<group>"; };
6423831EA8574132BED9D8CC /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -62,6 +68,7 @@
buildActionMask = 2147483647;
files = (
6172F2D35A4C3AA820D92908 /* libPods-app.a in Frameworks */,
3A46E88B2B26B70F0042C324 /* Clickstream in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -95,6 +102,8 @@
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
3A78F5DF2B26DB16006001B7 /* ClickstreamManager.h */,
3A78F5E12B26DC73006001B7 /* ClickstreamManager.m */,
3A78F5E32B26E493006001B7 /* app-Bridging-Header.h */,
);
name = app;
Expand Down Expand Up @@ -195,6 +204,7 @@
);
name = app;
packageProductDependencies = (
3A46E88A2B26B70F0042C324 /* Clickstream */,
);
productName = app;
productReference = 13B07F961A680F5B00A75B9A /* app.app */;
Expand Down Expand Up @@ -227,6 +237,7 @@
);
mainGroup = 83CBB9F61A601CBA00E9B192;
packageReferences = (
3A46E8892B26B70F0042C324 /* XCRemoteSwiftPackageReference "clickstream-swift" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -419,8 +430,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3A78F5E22B26DC73006001B7 /* ClickstreamManager.m in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
3A78F5E02B26DB16006001B7 /* ClickstreamManager.h in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -718,9 +731,22 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
3A46E8892B26B70F0042C324 /* XCRemoteSwiftPackageReference "clickstream-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/awslabs/clickstream-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.8.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
3A46E88A2B26B70F0042C324 /* Clickstream */ = {
isa = XCSwiftPackageProductDependency;
package = 3A46E8892B26B70F0042C324 /* XCRemoteSwiftPackageReference "clickstream-swift" */;
productName = Clickstream;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
Expand Down
Loading