Skip to content

Commit

Permalink
feat: support record items
Browse files Browse the repository at this point in the history
  • Loading branch information
zhu-xiaowei committed Dec 15, 2023
1 parent 27a6ac5 commit 0cab823
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 9 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Clickstream Flutter SDK can help you easily collect and report events from your

The SDK relies on the [Clickstream Android SDK](https://github.com/awslabs/clickstream-android) and [Clickstream Swift SDK](https://github.com/awslabs/clickstream-swift). Therefore, flutter SDK also supports automatically collect common user events and attributes (e.g., session start, first open). In addition, we've added easy-to-use APIs to simplify data collection in Flutter apps.

Visit our [Documentation site](https://awslabs.github.io/clickstream-analytics-on-aws/en/sdk-manual/flutter/) to learn more about Clickstream Flutter SDK.
Visit our [Documentation site](https://awslabs.github.io/clickstream-analytics-on-aws/en/latest/sdk-manual/flutter/) to learn more about Clickstream Flutter SDK.

## Integrate SDK

Expand Down Expand Up @@ -100,6 +100,33 @@ analytics.deleteGlobalAttributes(["level"]);

It is recommended to set global attributes after each SDK initialization, global attributes will be included in all events that occur after it is set.

#### Record event with items

You can add the following code to log an event with an item. you can add custom item attribute in `attributes` object.

**Note: Only pipelines from version 1.1+ can handle items with custom attribute.**

```dart
var itemBook = ClickstreamItem(
id: "123",
name: "Nature",
category: "book",
price: 99,
attributes: {
"book_publisher": "Nature Research"
}
);
analytics.record(
name: "view_item",
attributes: {
"currency": 'USD',
"event_category": 'recommended'
},
items: [itemBook]
);
```

#### Other configurations

In addition to the required `appId` and `endpoint`, you can configure other information to get more customized usage:
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.9.0'
implementation 'software.aws.solution:clickstream:0.10.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 @@ -32,6 +32,7 @@ import software.aws.solution.clickstream.AWSClickstreamPlugin
import software.aws.solution.clickstream.ClickstreamAnalytics
import software.aws.solution.clickstream.ClickstreamAttribute
import software.aws.solution.clickstream.ClickstreamEvent
import software.aws.solution.clickstream.ClickstreamItem
import software.aws.solution.clickstream.ClickstreamUserAttribute
import software.aws.solution.clickstream.client.util.ThreadUtil
import java.util.Objects
Expand Down Expand Up @@ -159,6 +160,7 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
arguments?.let {
val eventName = it["eventName"] as String
val attributes = it["attributes"] as HashMap<*, *>
val items = it["items"] as ArrayList<*>
val eventBuilder = ClickstreamEvent.builder().name(eventName)
for ((key, value) in attributes) {
if (value is String) {
Expand All @@ -173,6 +175,27 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
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)
}
}
clickstreamItems[index] = builder.build()
}
eventBuilder.setItems(clickstreamItems)
}
ClickstreamAnalytics.recordEvent(eventBuilder.build())
}
}
Expand Down
63 changes: 60 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import 'package:clickstream_analytics/clickstream_analytics.dart';
import 'package:clickstream_analytics/clickstream_analytics_item.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Expand Down Expand Up @@ -54,10 +55,18 @@ class _MyAppState extends State<MyApp> {
minLeadingWidth: 0,
),
ListTile(
leading: const Icon(Icons.touch_app),
title: const Text('recordEvent'),
leading: const Icon(Icons.circle),
title: const Text('recordEventWithName'),
onTap: () async {
analytics.record(name: "testEventWithName");
log("recorded testEvent with testEventWithName");
},
minLeadingWidth: 0,
),
ListTile(
leading: const Icon(Icons.touch_app),
title: const Text('recordEventWithAttributes'),
onTap: () async {
analytics.record(name: "testEvent", attributes: {
"category": 'shoes',
"currency": 'CNY',
Expand All @@ -67,7 +76,47 @@ class _MyAppState extends State<MyApp> {
"boolValue": true,
"value": 279.9
});
log("recorded testEvent and testEventWithName");
log("recorded testEvent and attributes");
},
minLeadingWidth: 0,
),
ListTile(
leading: const Icon(Icons.touch_app_outlined),
title: const Text('recordEventWithItem'),
onTap: () async {
var testItem1 = ClickstreamItem(
id: "1",
name: "testName1",
brand: "Google",
currency: "CNY",
category: "book",
locationId: "1",
attributes: {
"intValue": 21,
"longValue": 888888888813991919,
"doubleValue": 11.1234567890121213,
"boolValue": true,
"value": 279.9
});
var testItem2 = ClickstreamItem(
id: "2",
name: "testName2",
brand: "Sumsang",
currency: "USD",
category: "shoes",
locationId: "2",
attributes: {
"intValue": 13,
"longValue": 9999999913991919,
"doubleValue": 22.1234567890121213,
"boolValue": true,
"value": 379.9
});
analytics.record(
name: "testRecordItem",
attributes: {"testKey": "testValue"},
items: [testItem1, testItem2]);
log("recorded testEvent with item");
},
minLeadingWidth: 0,
),
Expand All @@ -76,6 +125,14 @@ class _MyAppState extends State<MyApp> {
title: const Text('setUserId'),
onTap: () async {
analytics.setUserId("12345");
log("setUserId");
},
minLeadingWidth: 0,
),
ListTile(
leading: const Icon(Icons.no_accounts),
title: const Text('setUserIdToNull'),
onTap: () async {
analytics.setUserId(null);
log("setUserId");
},
Expand Down
11 changes: 10 additions & 1 deletion ios/Classes/ClickstreamFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,17 @@ public class ClickstreamFlutterPlugin: NSObject, FlutterPlugin {
func recordEvent(_ arguments: [String: Any]) {
let eventName = arguments["eventName"] as! String
let attributes = arguments["attributes"] as! [String: Any]
let items = arguments["items"] as! [[String: Any]]
if attributes.count > 0 {
ClickstreamAnalytics.recordEvent(eventName, getClickstreamAttributes(attributes))
if items.count > 0 {
var clickstreamItems: [ClickstreamAttribute] = []
for itemObject in items {
clickstreamItems.append(getClickstreamAttributes(itemObject))
}
ClickstreamAnalytics.recordEvent(eventName, getClickstreamAttributes(attributes), clickstreamItems)
} else {
ClickstreamAnalytics.recordEvent(eventName, getClickstreamAttributes(attributes))
}
} else {
ClickstreamAnalytics.recordEvent(eventName)
}
Expand Down
18 changes: 15 additions & 3 deletions lib/clickstream_analytics.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'clickstream_analytics_item.dart';
import 'clickstream_analytics_platform_interface.dart';

class ClickstreamAnalytics {
Expand Down Expand Up @@ -32,9 +33,20 @@ class ClickstreamAnalytics {
}

Future<void> record(
{required String name, Map<String, Object?>? attributes}) {
return ClickstreamInterface.instance
.record({"eventName": name, "attributes": attributes ?? {}});
{required String name,
Map<String, Object?>? attributes,
List<ClickstreamItem>? items}) {
var itemArray = [];
if (items != null) {
for (ClickstreamItem item in items) {
itemArray.add(item.toMap());
}
}
return ClickstreamInterface.instance.record({
"eventName": name,
"attributes": attributes ?? {},
"items": itemArray
});
}

Future<void> setUserId(String? userId) {
Expand Down
58 changes: 58 additions & 0 deletions lib/clickstream_analytics_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
class ClickstreamItem {
ClickstreamItem({
this.id,
this.name,
this.locationId,
this.brand,
this.currency,
this.price,
this.quantity,
this.creativeName,
this.creativeSlot,
this.category,
this.category2,
this.category3,
this.category4,
this.category5,
this.attributes,
});

final String? id;
final String? name;
final String? locationId;
final String? brand;
final String? currency;
final num? price;
final int? quantity;
final String? creativeName;
final String? creativeSlot;
final String? category;
final String? category2;
final String? category3;
final String? category4;
final String? category5;
// used to add custom item attribute.
final Map<String, Object?>? attributes;

Map<String, Object?> toMap() {
return <String, Object?>{
if (id != null) 'id': id,
if (name != null) 'name': name,
if (locationId != null) 'location_id': locationId,
if (brand != null) 'brand': brand,
if (currency != null) 'currency': currency,
if (price != null) 'price': price,
if (quantity != null) 'quantity': quantity,
if (creativeName != null) 'creative_name': creativeName,
if (creativeSlot != null) 'creative_slot': creativeSlot,
if (category != null) 'category': category,
if (category2 != null) 'category2': category2,
if (category3 != null) 'category3': category3,
if (category4 != null) 'category4': category4,
if (category5 != null) 'category5': category5,
if (attributes != null) ...attributes!,
};
}
}
24 changes: 24 additions & 0 deletions test/clickstream_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import 'package:clickstream_analytics/clickstream_analytics.dart';
import 'package:clickstream_analytics/clickstream_analytics_item.dart';
import 'package:clickstream_analytics/clickstream_analytics_method_channel.dart';
import 'package:clickstream_analytics/clickstream_analytics_platform_interface.dart';
import 'package:flutter_test/flutter_test.dart';
Expand Down Expand Up @@ -77,6 +78,29 @@ void main() {
attributes: {"category": "shoes", "currency": "CNY", "value": 279.9});
expect(result, isNotNull);
});

test('record event with item', () async {
var itemBook = ClickstreamItem(
id: "123",
name: "Nature",
category: "book",
currency: "CNY",
price: 99,
attributes: {"book_publisher": "Nature Research"});
var itemShoes = ClickstreamItem(
id: "124",
name: "Nike",
category: "shoes",
price: 65,
currency: "USD",
attributes: {"place_of_origin": "USA"});
var result = analytics.record(
name: "cart_view",
attributes: {"_traffic_source_name": "Summer promotion"},
items: [itemBook, itemShoes]);
expect(result, isNotNull);
});

test('setUserId', () async {
var result = analytics.setUserId("11234");
expect(result, isNotNull);
Expand Down

0 comments on commit 0cab823

Please sign in to comment.