Skip to content
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

feat: react-native with web support #126

Merged
merged 50 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3ca0bef
feat: make storage interface async to enable react-native
bgiori Jun 24, 2022
2ea30a9
chore: merge main branch
bgiori Jun 24, 2022
7ade93c
fix: add async await to more tests
bgiori Jun 24, 2022
8e7ecb8
fix: more tests
bgiori Jun 25, 2022
9b353bb
fix: address regression caused by async methods in destination plugin…
kevinpagtakhan Jun 27, 2022
1525041
feat: react-native implementation - wip
bgiori Jun 27, 2022
1622f4c
Merge branch 'async-storage-interface' into react-native
bgiori Jun 27, 2022
3225aa4
fix: working events, need to fix native module
bgiori Jun 27, 2022
dfc4d25
fix: native context working
bgiori Jun 28, 2022
1339dfa
fix: add tests, support mobile and web
bgiori Jun 28, 2022
9fa7566
chore: re-add snippet
bgiori Jun 28, 2022
8aaebc2
chore: re-add snippet
bgiori Jun 28, 2022
df33d4f
feat: move example to examples dir
bgiori Jun 28, 2022
c08a35a
fix: clean ts build
bgiori Jun 28, 2022
3f194f9
fix: add tsconfig to react-native example
bgiori Jun 28, 2022
c6afad7
fix: remove ts ignore
bgiori Jun 28, 2022
32350f0
fix: clean up init
bgiori Jun 28, 2022
a5a981f
chore: try using es6 target to support node 12
bgiori Jun 28, 2022
786cfab
feat: add expo app example
bgiori Jun 29, 2022
42dc97c
chore: remove examples
bgiori Jun 29, 2022
e260f11
Merge branch 'main' into react-native
bgiori Jun 29, 2022
0617776
build: update podspec source repo
bgiori Jul 12, 2022
de75587
fix: remove dot from end of os version string
bgiori Jul 12, 2022
a71e9c0
fix: dont run attribution when on native
bgiori Jul 12, 2022
286b795
Merge branch 'main' into react-native
bgiori Jul 12, 2022
509515f
chore: update package json for publishing dev versions
bgiori Jul 12, 2022
267ef7a
fix: re-add dev example; update library name
bgiori Jul 12, 2022
4e44266
fix: remove conditional import in ios
bgiori Jul 12, 2022
04bde86
chore: dev 3
bgiori Jul 12, 2022
4a3303c
fix: import react in ios
bgiori Jul 12, 2022
1446c91
build: remove if around kotlin
bgiori Jul 12, 2022
0184ab5
feat: remove example from rn
bgiori Jul 13, 2022
40aafaa
build: bump version
bgiori Jul 13, 2022
a304368
fix: diff
bgiori Jul 13, 2022
b3091f8
feat: add readme
bgiori Jul 13, 2022
2feadca
chore: update README
bgiori Jul 13, 2022
71687b6
fix: revert browser readme
bgiori Jul 14, 2022
d7e472b
fix: browser readme
bgiori Jul 14, 2022
5db6fbb
fix: browser readme
bgiori Jul 14, 2022
2b84ff5
fix: pr comments
bgiori Jul 14, 2022
4c3ab8c
Merge branch 'main' into react-native
bgiori Jul 14, 2022
5b0df8a
fix: remove unused transports and setTransport func
bgiori Jul 14, 2022
e907e37
feat: react native analytics-connector (#139)
bgiori Jul 15, 2022
6c75768
chore: release 0.0.1-dev.7
bgiori Jul 15, 2022
1dd5cc2
chore: remove unused
bgiori Jul 15, 2022
be9dde6
chore: release 0.0.1-dev.8
bgiori Jul 15, 2022
dea7129
chore: publish source again
bgiori Jul 15, 2022
f1790e4
chore: workaround export
bgiori Jul 15, 2022
307aec9
fix: update rather than set user props
bgiori Jul 15, 2022
b0262c4
chore: package version 1.0.0-beta.0
bgiori Jul 15, 2022
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 .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
node_modules/

.DS_Store

lib/
*.tsbuildinfo
coverage/
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
<!-- README_SNIPPET_BLOCK -->
```html
<script type="text/javascript">
!function(){"use strict";!function(e,t){var r=e.amplitude||{_q:[]};if(r.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{r.invoked=!0;var n=t.createElement("script");n.type="text/javascript",n.integrity="sha384-GS6YJWyepBi+TL3uXx5i7xx1UTA9iHaZr9q+5uNsuhzMb8c1SfkKW4Wee/IxZOW5",n.crossOrigin="anonymous",n.async=!0,n.src="https://cdn.amplitude.com/libs/analytics-browser-1.0.0-min.js.gz",n.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var s=t.getElementsByTagName("script")[0];function v(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}}s.parentNode.insertBefore(n,s);for(var o=function(){return this._q=[],this},i=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],a=0;a<i.length;a++)v(o,i[a]);r.Identify=o;for(var u=function(){return this._q=[],this},c=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],p=0;p<c.length;p++)v(u,c[p]);r.Revenue=u;var d=["getDeviceId","setDeviceId","regenerateDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport"],l=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue"];function f(e){function t(t,r){e[t]=function(){var n={promise:new Promise((r=>{e._q.push({name:t,args:Array.prototype.slice.call(arguments,0),resolve:r})}))};if(r)return n}}for(var r=0;r<d.length;r++)t(d[r],!1);for(var n=0;n<l.length;n++)t(l[n],!0)}f(r),e.amplitude=r}}(window,document)}();
!function(){"use strict";!function(e,t){var r=e.amplitude||{_q:[]};if(r.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{r.invoked=!0;var n=t.createElement("script");n.type="text/javascript",n.integrity="sha384-LRqQslnCLN5Y5/WML9z04UjXrLxTaaAlEcWCBN5yhzUYR+3bekPziHIuZlJCwBuk",n.crossOrigin="anonymous",n.async=!0,n.src="https://cdn.amplitude.com/libs/analytics-browser-0.6.1-min.js.gz",n.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var s=t.getElementsByTagName("script")[0];function v(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}}s.parentNode.insertBefore(n,s);for(var o=function(){return this._q=[],this},i=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],a=0;a<i.length;a++)v(o,i[a]);r.Identify=o;for(var u=function(){return this._q=[],this},c=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],l=0;l<c.length;l++)v(u,c[l]);r.Revenue=u;var p=["getDeviceId","setDeviceId","regenerateDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport"],d=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue"];function f(e){function t(t,r){e[t]=function(){var n={promise:new Promise((r=>{e._q.push({name:t,args:Array.prototype.slice.call(arguments,0),resolve:r})}))};if(r)return n}}for(var r=0;r<p.length;r++)t(p[r],!1);for(var n=0;n<d.length;n++)t(d[n],!0)}f(r),e.amplitude=r}}(window,document)}();
bgiori marked this conversation as resolved.
Show resolved Hide resolved

amplitude.init("YOUR_API_KEY_HERE");
</script>
Expand Down
3 changes: 3 additions & 0 deletions packages/analytics-react-native/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.pbxproj -text
# specific for windows script files
*.bat text eol=crlf
61 changes: 61 additions & 0 deletions packages/analytics-react-native/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# OSX
#
.DS_Store

# XDE
.expo/

# VSCode
.vscode/
jsconfig.json

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

# Android/IJ
#
.idea
.gradle
local.properties
android.iml
**/*/.project

# Cocoapods
#
ios/Pods

# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log

# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore

# Expo
.expo/*

# generated by bob
lib/
152 changes: 152 additions & 0 deletions packages/analytics-react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<p align="center">
<a href="https://amplitude.com" target="_blank" align="center">
<img src="https://static.amplitude.com/lightning/46c85bfd91905de8047f1ee65c7c93d6fa9ee6ea/static/media/amplitude-logo-with-text.4fb9e463.svg" width="280">
</a>
<br />
</p>

# @amplitude/analytics-react-native

Official Amplitude SDK for React Native (Beta)

## Installation

To get started with using Amplitude React Native SDK, install the package to your project via NPM or script loader.

### Using Node package

This package is published on NPM registry and is available to be installed using npm and yarn.

```sh
# npm
npm install @amplitude/analytics-react-native

# yarn
yarn add @amplitude/analytics-react-native
```

## Usage

### Initializing SDK

Initialization is necessary before any instrumentation is done. The API key for your Amplitude project is required.

```typescript
amplitude.init(API_KEY)
```

### Tracking an Event

Events represent how users interact with your application. For example, “Button Clicked” may be an action you want to note.

```typescript
import { track } from '@amplitude/analytics-react-native';

// Track a basic event
track('Button Clicked');

// Track events with additional properties
const eventProperties = {
selectedColors: ['red', 'blue'],
};
track('Button Clicked', eventProperties);
```

### User Properties

User properties help you understand your users at the time they performed some action within your app such as their device details, their preferences, or language.

```typescript
import { Identify, identify } from '@amplitude/analytics-react-native';

const event = new Identify();

// sets the value of a user property
event.set('key1', 'value1');

// sets the value of a user property only once
event.setOnce('key1', 'value1');

// increments a user property by some numerical value.
event.add('value1', 10);

// pre inserts a value or values to a user property
event.preInsert('ab-tests', 'new-user-test');

// post inserts a value or values to a user property
event.postInsert('ab-tests', 'new-user-test');

// removes a value or values to a user property
event.remove('ab-tests', 'new-user-test')

// sends identify event
identify(event);
```

### prepend/append

* append will append a value or values to a user property array.
* prepend will prepend a value or values to a user property.

### User Groups

```typescript
import { setGroup } from '@amplitude/analytics-react-native';

// set group with single group name
setGroup('orgId', '15');

// set group with multiple group names
setGroup('sport', ['soccer', 'tennis']);
```

### Group Identify

This feature is only available to Growth and Enterprise customers who have purchased the [Accounts add-on](https://amplitude.zendesk.com/hc/en-us/articles/115001765532).

Use the Group Identify API to set or update properties of particular groups. However, these updates will only affect events going forward.

```typescript
import { Identify, groupIdentify } from '@amplitude/analytics-react-native';

const groupType = 'plan';
const groupName = 'enterprise';
const event = new Identify()
event.set('key1', 'value1');

groupIdentify(groupType, groupName, identify);
```

### Track Revenue

Revenue instances will store each revenue transaction and allow you to define several special revenue properties (such as 'revenueType', 'productIdentifier', etc.) that are used in Amplitude's Event Segmentation and Revenue LTV charts. These Revenue instance objects are then passed into `revenue` to send as revenue events to Amplitude. This allows us to automatically display data relevant to revenue in the platform. You can use this to track both in-app and non-in-app purchases.

```typescript
import { Revenue, revenue } from '@amplitude/analytics-react-native';

const event = new Revenue()
.setProductId('com.company.productId')
.setPrice(3.99)
.setQuantity(3);

revenue(event);
```

### Callback

All asynchronous API are optionally awaitable through a specific Promise interface. This also serves as callback interface.

```typescript
// Using async/await
const results = await track('Button Clicked').promise;
result.event; // {...} (The final event object sent to Amplitude)
result.code; // 200 (The HTTP response status code of the request.
result.message; // "Event tracked successfully" (The response message)

// Using promises
track('Button Clicked').promise.then((result) => {
result.event; // {...} (The final event object sent to Amplitude)
result.code; // 200 (The HTTP response status code of the request.
result.message; // "Event tracked successfully" (The response message)
});
```
21 changes: 21 additions & 0 deletions packages/analytics-react-native/amplitude-react-native.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "amplitude-react-native"
bgiori marked this conversation as resolved.
Show resolved Hide resolved
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]

s.swift_version = "5.0"

s.platforms = { :ios => "10.0", :tvos => "10.0" }
s.source = { :git => "https://github.com/amplitude/Amplitude-TypeScript.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"

s.dependency "React-Core"
end
59 changes: 59 additions & 0 deletions packages/analytics-react-native/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
buildscript {
ext.kotlinVersion = "1.5.30"
repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

android {
compileSdkVersion safeExtGet('compileSdkVersion', 29)
buildToolsVersion safeExtGet('buildToolsVersion', '29.0.2')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 29)
versionCode 1
versionName "1.0"

}

buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
google()
jcenter()
}

dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
}
3 changes: 3 additions & 0 deletions packages/analytics-react-native/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amplitude.reactnative">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.amplitude.reactnative

import com.facebook.react.bridge.Promise
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableNativeMap

const val MODULE_NAME = "AmplitudeReactNative"

@ReactModule(name = MODULE_NAME)
class AmplitudeReactNativeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

private val androidContextProvider = AndroidContextProvider(reactContext.applicationContext, false)

override fun getName(): String {
return MODULE_NAME
}

@ReactMethod
private fun getApplicationContext(promise: Promise) {
promise.resolve(WritableNativeMap().apply {
putString("version", androidContextProvider.versionName)
putString("platform", androidContextProvider.osName)
putString("language", androidContextProvider.language)
putString("os_name", androidContextProvider.osName)
putString("os_version", androidContextProvider.osVersion)
putString("device_brand", androidContextProvider.brand)
putString("device_manufacturer", androidContextProvider.manufacturer)
putString("device_model", androidContextProvider.model)
putString("carrier", androidContextProvider.carrier)
bgiori marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.amplitude.reactnative;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AmplitudeReactNativePackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AmplitudeReactNativeModule(reactContext));
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Loading