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: add record screen view api #8

Merged
merged 3 commits into from
Apr 9, 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
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
flutter pub get
flutter test --coverage --reporter github
- name: Upload Test Report
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: report
files: coverage/lcov.info
61 changes: 48 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
[![clickstream-flutter-test](https://github.com/awslabs/clickstream-flutter/actions/workflows/test.yml/badge.svg)](https://github.com/awslabs/clickstream-flutter/actions/workflows/test.yml) [![clickstream-flutter-release](https://github.com/awslabs/clickstream-flutter/actions/workflows/release.yml/badge.svg)](https://github.com/awslabs/clickstream-flutter/actions/workflows/release.yml) [![clickstream-flutter-build-android](https://github.com/awslabs/clickstream-flutter/actions/workflows/build-android.yml/badge.svg)](https://github.com/awslabs/clickstream-flutter/actions/workflows/build-android.yml) [![clickstream-flutter-build-ios](https://github.com/awslabs/clickstream-flutter/actions/workflows/build-ios.yml/badge.svg)](https://github.com/awslabs/clickstream-flutter/actions/workflows/build-ios.yml) [![pub package](https://img.shields.io/pub/v/clickstream_analytics.svg)](https://pub.dev/packages/clickstream_analytics) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)



## Introduction

Clickstream Flutter SDK can help you easily collect and report events from your mobile app to AWS. This SDK is part of an AWS solution - [Clickstream Analytics on AWS](https://github.com/awslabs/clickstream-analytics-on-aws), which provisions data pipeline to ingest and process event data into AWS services such as S3, Redshift.
Expand Down Expand Up @@ -90,14 +89,30 @@ Current login user's attributes will be cached in disk, so the next time app lau

#### Add global attribute

```dart
analytics.addGlobalAttributes({
"_traffic_source_medium": "Search engine",
"_traffic_source_name": "Summer promotion",
"level": 10
});

// delete global attribute
1. Add global attributes when initializing the SDK

```dart
analytics.init({
appId: "your appId",
endpoint: "https://example.com/collect",
globalAttributes: {
"_traffic_source_medium": "Search engine",
"_traffic_source_name": "Summer promotion",
}
});
```

2. Add global attributes after initializing the SDK
```dart
analytics.addGlobalAttributes({
"_traffic_source_medium": "Search engine",
"_traffic_source_name": "Summer promotion",
"level": 10
});
```

#### Delete global attribute
```
analytics.deleteGlobalAttributes(["level"]);
```

Expand All @@ -123,13 +138,30 @@ var itemBook = ClickstreamItem(
analytics.record(
name: "view_item",
attributes: {
"currency": 'USD',
"event_category": 'recommended'
"currency": "USD",
"event_category": "recommended"
},
items: [itemBook]
);
```

#### Record Screen View events manually

By default, SDK will automatically track the preset `_screen_view` event when Android Activity triggers `onResume` or iOS ViewController triggers `viewDidAppear`.

You can also manually record screen view events whether automatic screen view tracking is enabled, add the following code to record a screen view event with two attributes.

* `screenName` Required. Your screen's name.
* `screenUniqueId` Optional. Set the id of your Widget. If you do not set, the SDK will set a default value based on the hashcode of the current Activity or ViewController.

```dart
analytics.recordScreenView(
screenName: 'Main',
screenUniqueId: '123adf',
attributes: { ... }
);
```

#### Other configurations

In addition to the required `appId` and `endpoint`, you can configure other information to get more customized usage:
Expand All @@ -146,7 +178,10 @@ analytics.init(
isTrackUserEngagementEvents: true,
isTrackAppExceptionEvents: false,
authCookie: "your auth cookie",
sessionTimeoutDuration: 1800000
sessionTimeoutDuration: 1800000,
globalAttributes: {
"_traffic_source_medium": "Search engine",
},
);
```

Expand All @@ -162,6 +197,7 @@ Here is an explanation of each option:
- **isTrackAppExceptionEvents**: whether auto track exception event in app, default is `false`
- **authCookie**: your auth cookie for AWS application load balancer auth cookie.
- **sessionTimeoutDuration**: the duration for session timeout millisecond, default is 1800000
- **globalAttributes**: the global attributes when initializing the SDK.

#### Configuration update

Expand All @@ -177,7 +213,6 @@ analytics.updateConfigure(
isTrackScreenViewEvents: false
isTrackUserEngagementEvents: false,
isTrackAppExceptionEvents: false,
sessionTimeoutDuration: 100000,
authCookie: "test cookie");
```

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ android {
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
implementation 'software.aws.solution:clickstream:0.10.0'
implementation 'software.aws.solution:clickstream:0.12.0'
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,17 @@ package software.aws.solution.clickstream_analytics
import android.app.Activity
import com.amazonaws.logging.Log
import com.amazonaws.logging.LogFactory
import com.amplifyframework.AmplifyException
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.AmplifyConfiguration
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import org.json.JSONObject
import software.aws.solution.clickstream.AWSClickstreamPlugin
import software.aws.solution.clickstream.ClickstreamAnalytics
import software.aws.solution.clickstream.ClickstreamAttribute
import software.aws.solution.clickstream.ClickstreamConfiguration
import software.aws.solution.clickstream.ClickstreamEvent
import software.aws.solution.clickstream.ClickstreamItem
import software.aws.solution.clickstream.ClickstreamUserAttribute
Expand Down Expand Up @@ -64,53 +61,20 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
override fun onMethodCall(call: MethodCall, result: Result) {
val arguments = call.arguments() as HashMap<String, Any>?
when (call.method) {
"init" -> {
result.success(initSDK(arguments!!))
}

"record" -> {
recordEvent(arguments)
}

"setUserId" -> {
setUserId(arguments)
}

"setUserAttributes" -> {
setUserAttributes(arguments)
}

"setGlobalAttributes" -> {
setGlobalAttributes(arguments)
}

"deleteGlobalAttributes" -> {
deleteGlobalAttributes(arguments)
}

"updateConfigure" -> {
updateConfigure(arguments)
}

"flushEvents" -> {
ClickstreamAnalytics.flushEvents()
}

"disable" -> {
ClickstreamAnalytics.disable()
}

"enable" -> {
ClickstreamAnalytics.enable()
}

else -> {
result.notImplemented()
}
"init" -> result.success(initSDK(arguments!!))
"record" -> recordEvent(arguments)
"setUserId" -> setUserId(arguments)
"setUserAttributes" -> setUserAttributes(arguments)
"addGlobalAttributes" -> addGlobalAttributes(arguments)
"deleteGlobalAttributes" -> deleteGlobalAttributes(arguments)
"updateConfigure" -> updateConfigure(arguments)
"flushEvents" -> ClickstreamAnalytics.flushEvents()
"disable" -> ClickstreamAnalytics.disable()
"enable" -> ClickstreamAnalytics.enable()
else -> result.notImplemented()
}
}


private fun initSDK(arguments: HashMap<String, Any>): Boolean {
if (getIsInitialized()) return false
if (mActivity != null) {
Expand All @@ -119,28 +83,13 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
log.error("Clickstream SDK initialization failed, please initialize in the main thread")
return false
}
val amplifyObject = JSONObject()
val analyticsObject = JSONObject()
val pluginsObject = JSONObject()
val awsClickstreamPluginObject = JSONObject()
awsClickstreamPluginObject.put("appId", arguments["appId"])
awsClickstreamPluginObject.put("endpoint", arguments["endpoint"])
pluginsObject.put("awsClickstreamPlugin", awsClickstreamPluginObject)
analyticsObject.put("plugins", pluginsObject)
amplifyObject.put("analytics", analyticsObject)
val configure = AmplifyConfiguration.fromJson(amplifyObject)
try {
Amplify.addPlugin<AWSClickstreamPlugin>(AWSClickstreamPlugin(context))
Amplify.configure(configure, context)
} catch (exception: AmplifyException) {
log.error("Clickstream SDK initialization failed with error: " + exception.message)
return false
}
val sessionTimeoutDuration = arguments["sessionTimeoutDuration"]
.let { (it as? Int)?.toLong() ?: (it as Long) }
val sendEventsInterval = arguments["sendEventsInterval"]
.let { (it as? Int)?.toLong() ?: (it as Long) }
ClickstreamAnalytics.getClickStreamConfiguration()
val configuration = ClickstreamConfiguration()
.withAppId(arguments["appId"] as String)
.withEndpoint(arguments["endpoint"] as String)
.withLogEvents(arguments["isLogEvents"] as Boolean)
.withTrackScreenViewEvents(arguments["isTrackScreenViewEvents"] as Boolean)
.withTrackUserEngagementEvents(arguments["isTrackUserEngagementEvents"] as Boolean)
Expand All @@ -149,7 +98,28 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
.withSessionTimeoutDuration(sessionTimeoutDuration)
.withCompressEvents(arguments["isCompressEvents"] as Boolean)
.withAuthCookie(arguments["authCookie"] as String)
return true

(arguments["globalAttributes"] as? HashMap<*, *>)?.takeIf { it.isNotEmpty() }
?.let { attributes ->
val globalAttributes = ClickstreamAttribute.builder()
for ((key, value) in attributes) {
when (value) {
is String -> globalAttributes.add(key.toString(), value)
is Double -> globalAttributes.add(key.toString(), value)
is Boolean -> globalAttributes.add(key.toString(), value)
is Int -> globalAttributes.add(key.toString(), value)
is Long -> globalAttributes.add(key.toString(), value)
}
}
configuration.withInitialGlobalAttributes(globalAttributes.build())
}
return try {
ClickstreamAnalytics.init(context, configuration)
true
} catch (exception: Exception) {
log.error("Clickstream SDK initialization failed with error: " + exception.message)
false
}
} else {
return false
}
Expand All @@ -163,33 +133,25 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
val items = it["items"] as ArrayList<*>
val eventBuilder = ClickstreamEvent.builder().name(eventName)
for ((key, value) in attributes) {
if (value is String) {
eventBuilder.add(key.toString(), value)
} else if (value is Double) {
eventBuilder.add(key.toString(), value)
} else if (value is Boolean) {
eventBuilder.add(key.toString(), value)
} else if (value is Int) {
eventBuilder.add(key.toString(), value)
} else if (value is Long) {
eventBuilder.add(key.toString(), value)
when (value) {
is String -> eventBuilder.add(key.toString(), value)
is Double -> eventBuilder.add(key.toString(), value)
is Boolean -> eventBuilder.add(key.toString(), value)
is Int -> eventBuilder.add(key.toString(), value)
is Long -> eventBuilder.add(key.toString(), value)
}
}
if (items.size > 0) {
val clickstreamItems = arrayOfNulls<ClickstreamItem>(items.size)
for (index in 0 until items.size) {
val builder = ClickstreamItem.builder()
for ((key, value) in (items[index] as HashMap<*, *>)) {
if (value is String) {
builder.add(key.toString(), value)
} else if (value is Double) {
builder.add(key.toString(), value)
} else if (value is Boolean) {
builder.add(key.toString(), value)
} else if (value is Int) {
builder.add(key.toString(), value)
} else if (value is Long) {
builder.add(key.toString(), value)
when (value) {
is String -> builder.add(key.toString(), value)
is Double -> builder.add(key.toString(), value)
is Boolean -> builder.add(key.toString(), value)
is Int -> builder.add(key.toString(), value)
is Long -> builder.add(key.toString(), value)
}
}
clickstreamItems[index] = builder.build()
Expand Down Expand Up @@ -217,36 +179,28 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
arguments?.let {
val builder = ClickstreamUserAttribute.Builder()
for ((key, value) in arguments) {
if (value is String) {
builder.add(key, value)
} else if (value is Double) {
builder.add(key, value)
} else if (value is Boolean) {
builder.add(key, value)
} else if (value is Int) {
builder.add(key, value)
} else if (value is Long) {
builder.add(key, value)
when (value) {
is String -> builder.add(key, value)
is Double -> builder.add(key, value)
is Boolean -> builder.add(key, value)
is Int -> builder.add(key, value)
is Long -> builder.add(key, value)
}
}
ClickstreamAnalytics.addUserAttributes(builder.build())
}
}

private fun setGlobalAttributes(arguments: java.util.HashMap<String, Any>?) {
private fun addGlobalAttributes(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
val builder = ClickstreamAttribute.Builder()
for ((key, value) in arguments) {
if (value is String) {
builder.add(key, value)
} else if (value is Double) {
builder.add(key, value)
} else if (value is Boolean) {
builder.add(key, value)
} else if (value is Int) {
builder.add(key, value)
} else if (value is Long) {
builder.add(key, value)
when (value) {
is String -> builder.add(key, value)
is Double -> builder.add(key, value)
is Boolean -> builder.add(key, value)
is Int -> builder.add(key, value)
is Long -> builder.add(key, value)
}
}
ClickstreamAnalytics.addGlobalAttributes(builder.build())
Expand Down
Loading
Loading